在JavaScript中,每一个函数都有一个prototype属性,这个属性是一个指针,指向该函数的原型对象。这个原型对象为全部该实例所共享。在默认状况下,原型对象包含一个属性叫作constructor,它指向prototype属性所在的函数指针。html
图片和例子来自《JavaScript高级程序设计(第三版)》。app
function Person () {} Person.prototype.name = 'Nicholas'; Person.prototype.age = 29; Person.prototype.job = 'Software Enginner'; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(), person2 = new Person();
可是改变原型时,可能会改变constructor,好比:函数
Dog.prototype = new Animal();
此时Dog.prototype.constructor指向构造函数Animal,若是有须要,能够重写constructor,好比this
Dog.prototype.constructor = Dog;
经过让子类型的原型指向父类型的实例来实现基于原型链的继承。其本质是原型搜索机制:当访问一个实例的数据或方法时,首先在实例中寻找,实例中找不到后天然会沿着原型链寻找。这样继承以后,子类型的实例能够共享父类型的数据和方法。spa
function SuperType (name) { this.name = name; this.age = 24; this.foo = ['bar1', 'bar2']; } SuperType.prototype.say = function () { console.log(this.name, this.age, this.foo); }; function SubType () {} SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; var sub1 = new SubType('zdy1'); var sub2 = new SubType('zdy2'); sub1.age = 23; sub1.foo.push('bar3'); sub1.say(); // undefined 23 ["bar1", "bar2", "bar3"] sub2.say(); // undefined 24 ["bar1", "bar2", "bar3"]
在上面代码中,SubType经过原型继承了SuperTpe,可是同时也暴露了两个问题:prototype
其中有一句须要说明一下:设计
SubType.prototype.constructor = SubType;
在“SubType.prototype = new SuperType();”以后,“SubType.prototype.constructor”是指向SuperType的,须要把它更正回来。不然每个SubType的实例的constructor都指向了SuperType,这显然是不科学的。指针
基于原型链的继承方式不多单独使用。htm
在子类型构造函数中调用父类型的构造函数,叫作“借用构造函数”,也能够实现继承。它的思想是在子类型中从新调用一遍父类型的构造函数来初始化数据和方法。对象
function SuperType (name) { this.name = name; this.age = 24; this.foo = ['bar1', 'bar2']; } SuperType.prototype.say = function () { alert(this.name); }; function SubType () { // 继承了SuperType SuperType.apply(this, arguments); } var sub = new SubType('zdy'); console.log(sub) //SubType {name: "zdy", age: 24, foo: Array[2]} sub.say(); // Uncaught TypeError: Object #<SubType> has no method 'say'
借用构造函数的模式能够在构造函数中传入参数,但子类型不能共享父类型在原型上的数据和方法。因此,它也不多单独使用。
组合继承可谓整合了上面两种方法的特色:
function SuperType (name) { this.name = name; this.age = 24; this.foo = ['bar1', 'bar2']; this.sex = 'other'; } SuperType.prototype.say = function () { console.log(this.name, this.age, this.foo); }; function SubType (name, sex) { SuperType.apply(this, arguments); this.sex = sex; } SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; var sub1 = new SubType('zdy1', 'male'); var sub2 = new SubType('zdy2', 'female'); sub1.age = 23; sub1.foo.push('bar3'); sub1.say(); // zdy1 23 ["bar1", "bar2", "bar3"] sub2.say(); // zdy2 24 ["bar1", "bar2"] console.log(sub1.sex, sub2.sex); // male female
组合式继承也有不足之处,就是它实际调用了两次父类型的构造函数(第10行和第14行),而且在子类型的构造函数中重写(覆盖)了原型中的属性。因此改进的思路是,如何在不添加多余的、被覆盖的属性的同时,得到父类型的原型?请看最后一种继承方法。
《JavaScript高级程序设计(第3版)》对这种继承方式给予了确定:
开发人员广泛认为寄生组合式继承是引用类型最理想的继承范式。
主要是它只调用了一次父类型的构造函数,因此避免了子类型在prototype上建立没必要要的、多余的属性。
function inheritPrototype (subType, superType) { function F () {} F.prototype = superType.prototype; var proto = new F(); proto.constructor = subType; subType.prototype = proto; } function SuperType (name) { this.name = name; this.colors = ['red', 'blue', 'green']; } SuperType.prototype.sayName = function () { alert(this.name); }; function SubType (name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); var instance = new SubType('Nicholas', 29); // SubType {name: "Nicholas", colors: Array[3], // age: 29, constructor: function, sayName: function} console.log(instance);
以上代码改自《JavaScript高级程序设计(第3版)》,说说我我的对这种方法思路的理解。
在inheritPrototype函数中,建立一个临时构造函数F()并实例化,来得到父类型原型对象的一个副本。由于它只复制了父类型的原型对象,从而并无包括父类型构造函数中的属性与方法。至关于在继承过程当中,绕了一个弯来躲避再一次初始化父类型的构造函数,因此不会在子类型的原型中存在多余的父类型属性。