一、原型链继承:基本思想是新建父类的实例,将父类的实例赋给子类的原型对象。这样,子类的原型对象中的 __proto__ 指针就指向父类的原型对象。再新建一个子类的实例,那么子类的实例就同时继承了子类和父类的原型对象中的属性和方法,从而实现了继承。所有引用类型都默认继承了 Object。具体实现:
function Super() { this.property = true; } Super.prototype.getSuperValue = function() { return this.property; } function Sub() { this.subProperty = false; } // 继承了父类 Sub.prototype = new Super();
原型链继承的缺点:引用类型的属性会被所有实例所共享,那么,修改一个实例的引用类型属性,也会影响另一个实例的引用类型属性。这个缺点出现的原因在于:Sub.prototype = new Super(); 子类实例指向的原型对象是 Sub.prototype,此时 Sub.prototype 已经是 Super 的实例了,如果父类的构造函数里面有 color 这个属性,那么 instance1 和 instance2 就共享这个 color 属性。所以,通过 instance1 修改这个属性,也会影响到 instance2。但是修改实例的值属性时,并不会影响原型和其他实例的值属性。
function Super() { this.colors = ["red", "blue"]; } function Sub() { } // 继承了父类 Sub.prototype = new Super(); var instance1 = new Sub(); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "black"] var instance2 = new Sub(); console.log(instance2.colors); // ["red", "blue", "black"]
解决方法,借用构造函数继承和组合继承
二、借用构造函数继承:基本思想是,在子类构造函数的内部,使用 call 方法调用超类构造函数。我们在将来要新建的 Sub 实例环境中调用了 Super 构造函数,这样一来,所有的Sub 实例都会有一份属于自己的 color 属性。
function Super() { this.colors = ["red", "blue"]; } function Sub() { Super.call(this); } var instance1 = new Sub(); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "black"] var instance2 = new Sub(); console.log(instance2.colors); // ["red", "blue"]
借用构造函数继承的缺点:方法都在构造函数中定义,那么函数复用就无从谈起了。解决办法,用组合继承(伪经典继承)
三、组合继承:基本思想,把原型继承和构造函数继承结合到一起,使用原型链对共有方法和属性的继承,使用构造函数对不共有属性的继承。是 JS 中常见的继承模式。
function Super() { this.colors = ["red", "blue"]; } Super.prototype.sayHello = function () { console.log("hello") } function Sub() { // 继承不共享的属性和方法 Super.call(this) } // 继承共享的属性和方法 Sub.prototype = new Super(); Sub.prototype.constructor = Sub; Sub.prototype.sayHi = function() { console.log("hi") } var instance1 = new Sub(); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "black"] instance1.sayHi(); instance1.sayHello(); var instance2 = new Sub(); console.log(instance2.colors); // ["red", "blue"]
组合式继承的缺点:会调用两次父类的构造函数。第一次是在子类构造函数中用 call 的方式调用,第二次是在继承原型链上的属性和方法,使用 new 来调用父类的构造函数。
四、组合继承的优化:基本思想是在子类的构造函数中依旧要用 call 方法,调用一遍父类的构造函数,依次来继承父类构造函数中的方法。但在继承父类原型对象中的方法就采用了,在子类和父类中间利用一个空对象来继承。把这个空对象的 prototype 属性复制成父类的构造函数,把子类的 prototype 属性复制成空对象的实例。
function SuperType() { this.color = ["red", "yellow", "blue"]; } SuperType.prototype.sayHello = function() { console.log("hello") } // 继承父类的构造函数里面的属性 function SubType() { SuperType.call(this); this.friends = ["a", "b", "c"]; } // 继承父类原型对象的属性 // 道格拉斯的方法 // function object(o) { // function F() {}; // F.prototype = o; // return new F(); // } // es5 把上面的方法规范化了,变成了 Object.create(o) SubType.prototype = Object.create(SuperType); // 把子类的 constructor 属性指向变回 SubType SubType.prototype.constructor = SubType;
Object.create()和 new object()的区别
1. Object.create()方法是ECMAScript5中新增的,用来规范化原型式继承的。这个方法接收两个参数,一个是用作新对象原型的对象,和一个为新对象定义额外属性的(可选)对象。
new Object()方法的实质是,使用引用类型Object的构造函数创建了一个新的实例,这个实例拥有Object默认的方法如toString、toLocaleString等。
2. Object.create创建对象是创建一个拥有指定原型和若干个指定属性的对象,也就是说可以任意指定原型,甚至是null, 而new Object()只是创建了一个以Object.prototype为原型的对象。