JavaScript六种继承方式记录
1.原型链
利用原型让一个引用类型B继承另一个引用类型A的属性和方法
function A(){ this.Aa = 'Aaaa'; } A.prototype.getAaValue = function(){ return this.Aa; }; function B(){ this.Bb = 'Bbbb'; }
//B继承了A.原来存在于 A 实例中的所有属性和方法,现在也存在于 B.prototype 中了
B.prototype = new A();
//在继承A的属性和方法的基础上,B.prototype添加新的方法 B.prototype.getBbValue = function(){ return this.Bb; }; var C = new B(); alert(C.getAaValue()); //Aaaa
C继承B,B继承A,由此形成了一个原型链。
给原型添加方法的代码一定要放在替换原型的语句之后。在通过原型链实现继承时,不能使用对象字面量创建原型方法。因为这样做就会重写原型链。
function A(){ this.Aa = 'Aaaa'; } A.prototype.getAaValue = function(){ return this.Aa; }; function B(){ this.Bb = 'Bbbb'; } B.prototype = new A(); //使用字面量添加新方法,会导致上一行代码无效 B.prototype = { getBbValue : function (){ return this.Bb; }, OtherMethod : function (){ return '1111'; } }; var C = new B(); alert(C.getAaValue()); //报错
原型链问题
function A1(){ this.colors = ["red", "blue", "green"]; } function A2(){} //继承了 A1 A2.prototype = new A1(); var A3 = new A2(); A3.colors.push("black"); console.log(A3.colors); //"red,blue,green,black" var A4 = new A2(); console.log(A4.colors); //"red,blue,green,black"
所有A2的实例都会共享同一个属性;无法在不影响所有对象实例的情况下,像A2传递参数;因此实践中很少会单独使用原型链
2.借用构造函数(又称伪造对象或经典继承)
在B构造函数内部调用A构造函数,可通过apply()或call()
function A1(name){ this.colors = ["red", "blue", "green"]; this.name = name; } function A2(){ //继承了A1,同时可传递参数 A1.call(this,'Tom'); //还可定义自己的属性 this.age = 18; } var A3 = new A2(); A3.colors.push("black"); console.log(A3.colors); //"red,blue,green,black" var A4 = new A2(); console.log(A4.colors); //"red,blue,green" console.log(A4.name); //Tom console.log(A4.age); //18
借用构造函数问题:方法都在构造函数中定义,函数复用无从谈起;且在原型中定义的方法,对子类型而言也是不可见的(这点不是很明白什么意思),导致所有类型只能使用构造函数模式;因此借用构造函数很少单独使用。
3.组合继承(伪经典继承)
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
function A1(name){ this.name = name; this.colors = ["red", "blue", "green"]; } A1.prototype.sayName = function(){ console.log(this.name); }; function A2(name, age){ //借用构造函数继承实例属性 A1.call(this, name); this.age = age; } //原型链继承原型属性和方法 A2.prototype = new A1(); A2.prototype.constructor = A2; //为了让A2的构造函数重新指回这个类本身,否则的话会变成A1的构造函数 A2.prototype.sayAge = function(){ console.log(this.age); }; var A3 = new A2("Nicholas", 29); A3.colors.push("black"); console.log(A3.colors); //"red,blue,green,black" A3.sayName(); //"Nicholas"; A3.sayAge(); //29 var A4 = new A2("Greg", 27); console.log(A4.colors); //"red,blue,green" A4.sayName(); //"Greg"; A4.sayAge(); //27
组合继承避免了原型链和借用构造函数的缺陷,融合了优点,成为JavaScript中最常用的继承模式。而且,instanceof 和 isPrototypeOf()也能够用于识别基于组合继承创建的对象。
4.原型式继承
ES5 通过新增 Object.create()方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create()与 object()方法的行为相同。
//若使用object()需先定义 function object(o){ function F(){} F.prototype = o; return new F(); } var person1 = { name: "B1", friends: ["A1", "A2", "A3"] }; //一个参数,Object.create()与object()结果一样 var person2 = object(person1); // var person2 = Object.create(person1);
person2.name = "B2";
person2.friends.push("A4");
var person3 = object(person1);
person3.name = "B3";
person3.friends.push("A5");
console.log(person1.name); //"B1"
console.log(person1.friends); //"A1,A2,A3,A4,A5"
Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同:每个属性都是通过自己的描述符定义的。以这种方式指定的任何属性都会覆盖原型对象上的同名属性。
var person = { name: "Nicholas", friends: ["A1", "A2", "A3"] }; var person2 = Object.create(person, { friends: { value: "A4" } }); var person3 = Object.create(person); console.log(person2.friends); //"A4" console.log(person3.friends); //"A1,A2,A3"
支持 Object.create()方法的浏览器有 IE9+、Firefox 4+、Safari 5+、Opera 12+和 Chrome。在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型式继承是完全可以胜任的。
不过,包含引用类型值的属性始终都会共享相应的值,就像使用原型模式一样
5.寄生式继承
使用寄生式继承,会由于不能做到函数复用而降低效率;与借用构造函数类似
function A2(original){ var clone = Object.create(original); //通过调用函数创建一个新对象 clone.sayHi = function(){ //以某种方式来增强这个对象 console.log("hi"); }; return clone; //返回这个对象 } var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var Person2 = A2(person); console.log(Person2.friends); //"Shelby", "Court", "Van" Person2.sayHi(); //"hi"
6.寄生组合继承
function A3(A2, A1){ var obj = Object.create(A1.prototype); //创建对象 obj.constructor = A2; //增强对象 A2.prototype = obj; //指定对象 } function A1(name){ this.name = name; this.colors = ["red", "blue", "green"]; } A1.prototype.sayName = function(){ console.log(this.name); }; function A2(name, age){ //继承A1 A1.call(this, name); this.age = age; } A3(A2, A1); A2.prototype.sayAge = function(){ console.log(this.age); }; var A3 = new A2("Nicholas", 29); A3.colors.push("black"); console.log(A3.colors); //"red,blue,green,black" A3.sayName(); //"Nicholas"; A3.sayAge(); //29 var A4 = new A2("Greg", 27); console.log(A4.colors); //"red,blue,green" A4.sayName(); //"Greg"; A4.sayAge(); //27
这个例子的高效率体现在它只调用了一次 A1构造函数(组合继承调用了两次A1),并且因此避免了在 A2. prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变,能正常使用instanceof 和 isPrototypeOf()。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
所有引用类型默认都继承了 Object,而这个继承也是通过原型链实现的。
所有函数的默认原型都是 Object 的实例,因此默认原型都会包含一个内部指针,指向 Object.prototype。这也正是所有自定义类型都会继承 toString()、valueOf()等默认方法的根本原因。
本文为原创文章,转载请保留原出处,方便溯源,如有错误地方,谢谢指正。