原型链

  1. 原型链继承
  2. 借用构造函数实现继承
  3. 组合继承(结合使用两种继承模式)
  4. 寄生式组合继承
  5. class 实现继承
  6. 确定原型和实例的关系

原型链图

构造函数,原型和实例的关系:

每个构造函数(constructor)都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针,而实例(instance)都包含一个指向原型对象的内部指针.

如果试图引用对象(实例instance)的某个属性,会首先在对象内部寻找该属性,直至找不到,然后才在该对象的原型(instance.prototype)里去找这个属性.

原型链继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
问题1:原型中包含的引用类型属性将被所有实例共享;
问题2:子类在实例化的时候不能给父类构造函数传参;
function Animal() {
this.colors = ['black', 'white']
}
Animal.prototype.getColor = function() {
return this.colors
}
function Dog() {}
Dog.prototype = new Animal()

let dog1 = new Dog()
dog1.colors.push('brown')
let dog2 = new Dog()
console.log(dog2.colors) // ['black', 'white', 'brown']

借用构造函数实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//父类:人
function Person () {
this.head = '脑袋瓜子';
this.emotion = ['喜', '怒', '哀', '乐']; //人都有喜怒哀乐
this.eat = function () {
console.log('吃吃喝喝');
}
this.sleep = function () {
console.log('睡觉');
}
this.run = function () {
console.log('快跑');
}
}
//子类:学生,继承了“人”这个类
function Student(studentID) {
this.studentID = studentID;
Person.call(this);
}

//Student.prototype = new Person();

var stu1 = new Student(1001);
console.log(stu1.emotion); //['喜', '怒', '哀', '乐']

stu1.emotion.push('愁');
console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]
们通过借用构造函数继承,保证了 stu1 和 stu2 都有各自的父类属性副本,从而使得各自 emotion 互不影响。但同时带来的问题是,stu1 和 stu2 都拷贝了 Person 类中的所有属性和方法,而在 Person 类中,像 eat ( ), sleep ( ), run ( ) 这类方法应该是公用的,而不需要添加到每个实例上去,增大内存,尤其是这类方法较多的时候。

var stu2 = new Student(1002);
console.log(stu2.emotion); //["喜", "怒", "哀", "乐"]

组合继承(结合使用两种继承模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
方法挂载到父类的原型对象上去,实现方法复用,然后子类通过原型链继承
//父类:人
function Person () {
this.head = '脑袋瓜子';
this.emotion = ['喜', '怒', '哀', '乐']; //人都有喜怒哀乐
}
//将 Person 类中需共享的方法放到 prototype 中,实现复用
Person.prototype.eat = function () {
console.log('吃吃喝喝');
}
Person.prototype.sleep = function () {
console.log('睡觉');
}
Person.prototype.run = function () {
console.log('快跑');
}
//子类:学生,继承了“人”这个类
function Student(studentID) {
this.studentID = studentID;
Person.call(this);
}

Student.prototype = new Person(); //此时 Student.prototype 中的 constructor 被重写了,会导致 stu1.constructor === Person
Student.prototype.constructor = Student; //将 Student 原型对象的 constructor 指针重新指向 Student 本身

var stu1 = new Student(1001);
console.log(stu1.emotion); //['喜', '怒', '哀', '乐']

stu1.emotion.push('愁');
console.log(stu1.emotion); //["喜", "怒", "哀", "乐", "愁"]

var stu2 = new Student(1002);
console.log(stu2.emotion); //["喜", "怒", "哀", "乐"]

stu1.eat(); //吃吃喝喝
stu2.run(); //快跑
console.log(stu1.constructor); //Student

首先,我们将 Person 类中需要复用的方法提取到 Person.prototype 中,然后设置 Student 的原型对象为 Person 类的一个实例,这样 stu1 就能访问到 Person 原型对象上的属性和方法了。其次,为保证 stu1 和 stu2 拥有各自的父类属性副本,我们在 Student 构造函数中,还是使用了 Person.call ( this ) 方法。如此,结合原型链继承和借用构造函数继承,就完美地解决了之前这二者各自表现出来的缺点。

寄生式组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}

Parent.prototype.getName = function () {
console.log(this.name)
}

function Child (name, age) {
Parent.call(this, name);
this.age = age;
}

function object(o) {
function F() {}
F.prototype = o;
return new F();
// 通过构造一个介于 Parent 与 Child 之间的对象,并使该对象的 prototype 属性指向 Parent 的 prototype对象,
// 来避开通过调用 Parent 构造函数的方式来产生一个 prototype 指向Parent prototype对象的对象。
}

function prototype(child, parent) {
// 不直接child.prototype=parent.prototype呢?
// 原因 : 当我们想给 Child 的prototype里面添加共享属性或者方法时,如果其 prototype 指向的是 Parent 的 prototype,那么在 Child 的 prototype 里添加的属性和方法也会反映在 Parent 的 prototype 里面,
// 这明显是不合理的,这样做的后果是当我们只想使用 Parent 时,也能看见 Child 往里面扔的方法和属性。
// 所以需要每个构造函数都需要持有自己专用的prototype对象
var prototype = object(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}

prototype(Child, Parent);

var child1 = new Child('kevin', '18');

console.log(child1);

class 实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Animal {
constructor(name) {
this.name = name
}
getName() {
return this.name
}
}
class Dog extends Animal {
constructor(name, age) {
super(name)
this.age = age
}
}

确定原型和实例的关系

第一种是使用 instanceof 操作符, 只要用这个操作符来测试实例(instance)与原型链中出现过的构造函数,结果就会返回

1
2
3
var d = new Date();
d instanceof Date;//=>true d是Date的实例
d instanceof Object;//=>true 所有对象都是Object的实例

第二种是使用 isPrototypeOf() 方法, 同样只要是原型链中出现过的原型,isPrototypeOf() 方法就会返回true,

1
2
3
var d = new Date();
Date.prototype.isPrototypeOf(d);//=>true
Object.prototype.isPrototypeOf(d);//=>true

---