1. 原型链继承
过程:①定义父类构造函数 ➡ ②给父类的原型添加方法 ➡ ③定义子类型的构造函数 ➡④将创建的父类型对象赋值给子类型的原型 ➡ ⑤将子类型的构造属性设置为子类型 ➡ ⑥给子类型的原型添加方法 ➡ ⑦创建子类型的对象,可以调用父类型的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function People () { this.pName = '人类' } People.prototype.getPName = function () { console.log(this.pName);
}
function Student () { this.sName = '学生' }
Student.prototype = new People() Student.prototype.constructor = Student
Student.prototype.getSName = function () { console.log(this.sName); }
var s = new Student() console.log(s);
var p = new People() console.log(p)
|
执行结果:
存在的问题
- 引用类型的属性被所有实例共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function Parent () { this.names = ['kevin', 'daisy']; }
function Child () { }
Child.prototype = new Parent();
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names);
var child2 = new Child();
console.log(child2.names);
|
- 在创建子类型实例时,不能向父类型传参
2.借用构造函数继承(假继承)
过程:①定义父类型构造函数 ➡ ②定义子类型的构造函数 ➡ ③在子类型构造函数中调用父类型构造
关键点:在子类型构造函数中通过call调用父类型的构造函数
1 2 3 4 5 6 7 8 9 10 11 12
| function People (name, age) { this.name = name; this.age = age; }
function Student (name, age, price) { People.call(this, name, age); this.price = price; }
var s = new Student('Tom', 12, 13000); console.log(s);
|
优点
- 避免了引用类型的属性被所有实例共享
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Parent () { this.names = ['kevin', 'daisy']; }
function Child () { Parent.call(this); }
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names);
var child2 = new Child();
console.log(child2.names);
|
- 在创建子类型实例时,可以向父类型传参
缺点
方法在构造函数中定义,每次创建实例都会创建一遍
3.组合继承(原型链继承 + 借用构造函数继承)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function People (name, age) { this.name = name; this.age = age; } People.prototype.setName = function (name) { this.name = name; }
function Student (name, age, price) { People.call(this, name, age); this.price = price; } Student.prototype = new People(); Student.prototype.constructor = Student;
Student.prototype.setPrice = function (price) { this.price = price; }
var s = new Student('Tom', 12, 3500); s.setName('Jack'); s.setPrice(1500); console.log(s);
|
优点
融合继承原型链继承和构造函数继承的优点,是JavaScript中最常用的继承模式。
4.原型式继承
原型式继承适用于这种情况:你有一个对象,想在它的基础上再创建一个新的对象。你需要把这个对象先传给object()
,然后再对返回的对象进行适当的修改。
1 2 3 4 5
| function object(o){ function F(){} F.prototype = o; return new F(); }
|
※ 本质上,object()
是对传入的对象执行了一次浅复制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function object (o) { function F () { } F.prototype = o; return new F(); }
var person = { name: 'Jack', frineds: ['Daming', 'Lingling'] }
var p1 = object(person); var p2 = object(person);
p1.name = 'Sam'; console.log(p2.name);
p1.frineds.push('Amy'); console.log(p2.frineds);
|
※ 修改p1.name
的值,p2.name
的值并未发生改变,并不是因为p1
和p2
有独立的name
值,而是因为p1.name= 'Sam'
是给p1
添加了name
的属性,并赋值为Sam
,并非修改了原型上name
的值。
缺点
包含引用类型的属性值会被所有实例共享(与原型链继承相同)。
5.寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来做增强对象,最后返回对象。
1 2 3 4 5 6 7
| function createAnother(original){ let clone = object(original); clone.sayHi = function(){ console.log('Hi'); } return clone; }
|
缺点
与借用构造函数继承模式相同,每次创建对象都会创建一遍方法。
6.寄生组合式继承
组合继承的方式也存在效率的问题,最主要的效率问题就是父类构造函数始终会被调用两次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; }
Child.prototype = new Parent();
var child1 = new Child('kevin', '18');
console.log(child1)
|
该如何精益求精,避免重复调用呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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; }
var F = function () {}; F.prototype = Parent.prototype; Child.prototype = new F();
var child1 = new Child('kevin', '18'); console.log(child1);
|
封装一下这个继承方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function object(o) { function F() {} F.prototype = o; return new F(); }
function prototype(child, parent) { var prototype = object(parent.prototype); prototype.constructor = child; child.prototype = prototype; }
prototype(Child, Parent);
|
这种方式的高效率体现它只调用了一次 Parent
构造函数,并且因此避免了在 Parent.prototype
上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof
和 isPrototypeOf
。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。 ——《JavaScript高级程序设计》