1. 原型链继承
我们从上一章知道,在 new 运算符操作的时候,会将实例原型(Child.prototype)指向构造函数的原型(Parent.prototype),这样 child1 就可以访问到构造函数原型中的属性,实现了继承。
缺点:
- 多个实例对引用类型的操作会被篡改。这个理解一下原型链就明白了,就不费篇幅写例子了。
- 在创建 Child 的实例时,不能向Parent传参。 ```javascript function Parent () { this.name = ‘name’; }
Parent.prototype.getName = function () { console.log(this.name); }
function Child () {}
Child.prototype = new Parent();
const child1 = new Child();
console.log(child1.getName()) // name
我们可以打印出来验证一下:<br /><a name="IvuEn"></a># 2. 借用构造函数使用父类的构造函数来实现继承,等同于复制父类的实例给子类(不使用原型)。但在创建子类实例时,会调用 Perent 构造函数,所以 Child 的每个实例都会将 Parent 中的属性复制一份优点:1. 避免了引用类型的属性被所有实例共享2. 可以在 Child 中向 Parent 传参缺点:1. 方法都在构造函数中定义,每次创建实例都会创建一遍方法,影响性能2. 只能继承父类的实例属性和方法,不能继承原型属性/方法```javascriptfunction Parent (name) {this.name = name;}function Child (name) {// 继承于 ParentParent.call(this, name);}var child1 = new Child("kevin");console.log(child1.name); // kevinvar child2 = new Child("yayu");console.log(child2.name); // yayu
3. 组合继承
原型链继承和经典继承双剑合璧,用原型链实现对原型属性和方法的继承,用借用构造函数的方法来实现实例属性的继承。
优点:
- 融合原型链继承和构造函数的优点。
缺点:
- 在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。 ```javascript function Parent (name) { this.name = name; this.colors = [‘red’, ‘blue’, ‘green’]; }
Parent.prototype.getName = function () { console.log(this.name) }
function Child (name, age) { // 继承属性,第二次调用 Parent Parent.call(this, name); this.age = age; }
// 继承方法,构建原型链,第一次调用 Parent Child.prototype = new Parent(); // 重写 Child.prototype 的 constructor 属性,指向自己的构造函数 Child Child.prototype.constructor = Child;
var child1 = new Child(‘kevin’, ‘18’); child1.colors.push(‘black’); console.log(child1.name); // kevin console.log(child1.age); // 18 console.log(child1.colors); // [“red”, “blue”, “green”, “black”]
var child2 = new Child(‘daisy’, ‘20’); console.log(child2.name); // daisy console.log(child2.age); // 20 console.log(child2.colors); // [“red”, “blue”, “green”]
同样,我们可以把 Child 的实例打印出来看看:<br /><a name="wBomx"></a># 4. 原型式继承利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。缺点:1. 包含引用类型的属性值始终都会共享相应的值,跟原型链继承一样。2. 无法传递参数```javascriptfunction createObj(o) {function F(){}F.prototype = o;return new F();}
Object.create() 对传入其中的对象执行了一次浅复制,将构造函数F的原型直接指向传入的对象。
var person = {name: "Nicholas",friends: ["Shelby", "Court", "Van"]};var anotherPerson = Object.create(person);anotherPerson.name = "Greg";anotherPerson.friends.push("Rob");var yetAnotherPerson = object(person);yetAnotherPerson.name = "Linda";yetAnotherPerson.friends.push("Barbie");console.log(person.friends); // "Shelby,Court,Van,Rob,Barbie"
5. 寄生式继承
在原型式继承的基础上,增强对象,返回构造函数。
缺点(同原型式继承)
function createObj (o) {var clone = Object.create(o);// 函数的主要作用是为构造函数新增属性和方法,以增强函数clone.sayName = function () {console.log('hi');}return clone;}var person = {name: "Nicholas",friends: ["Shelby", "Court", "Van"]};var p1 = createObj(person);p1.sayHi(); // "hi"
6. 寄生组合式继承
结合借用构造函数传递参数和寄生模式实现继承,这种方式的高效率体现它只调用了一次 SuperType 构造函数,并且因此避免了在 SuperType.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
function inheritPrototype(subType, superType){// 创建对象,创建父类原型的一个副本var prototype = Object.create(superType.prototype);// 增强对象,弥补因重写原型而失去的默认的constructor 属性prototype.constructor = subType;// 指定对象,将新创建的对象赋值给子类的原型subType.prototype = prototype;}// 父类初始化实例属性和原型属性function SuperType(name){this.name = name;this.colors = ["red", "blue", "green"];}SuperType.prototype.sayName = function(){console.log(this.name);};// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)function SubType(name, age){SuperType.call(this, name);this.age = age;}// 将父类原型指向子类inheritPrototype(SubType, SuperType);// 新增子类原型属性SubType.prototype.sayAge = function(){console.log(this.age);}var instance1 = new SubType("xyc", 23);var instance2 = new SubType("lxy", 23);instance1.colors.push("2"); // ["red", "blue", "green", "2"]instance1.colors.push("3"); // ["red", "blue", "green", "3"]
