关于原型和实例、构造函数的关系:
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。
(1)原型:
-
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
-
”无论什么时候,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。”在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。例如一个函数叫Person,
-
(Person.prototype.constructor指向Person)。而通过这个构造函数,我们还可以继续为原型对象添加其他属性和方法。
-
原型上面的属性和方法都是共享的。每个Object(对象)都继承了原型(proto)。在原型上修改的属性和方法可以在global(全局)对象上访问。
创建自定义类型(也就是封装)的最常见方式,就是组合使用构造函数模式与原型模式。“构造函数模式”用于定义实例属性,而“原型模式”用于定义方法和共享的属性。
(2)原型链:
一句话,子类继承了父类(基类或者超类),而父类继承了对象(Object)。当调用子类的toString()时,实际上调用的是保存在对象的原型(Object.prototype)中的那个方法。这条关系链就是原型链。
(3)继承:
- 许多OO语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承则继承实际的方法。由于函数没有签名,在ecmascript中无法实现接口继承。ecmascript只支持实现继承,而且其实现继承主要依靠原型链来实现的。
- 如何实现继承,创建的父类(基类或者超类)和子类,子类(SubType)的原型进行对父类(SuperType)的实例,从而达到子类继承了父类。( SubType.prototype = new SuperType(); )
- 实例化后的子类,(var instance = new SubType();),他的构造函数(constructor)是指向于父类的,因为子类(SubType)的原型指向了另一个对象父类(SuperType)的原型,而这个原型对象的构造函数(constructor)属性指向的是父类(SuperType)。子类如果使用字面量的定义添加方法会切断子类和父类的联系。
- 在直接运用原型模式来实现继承,子类的属性和父类的属性是绑定在原型链上面的,从而子类的属性发生改变,父类的属性也相对变化。为了松散属性间的耦合性,就利用了构造函数来实现继承。
- 借用构造函数来实现继承,运用call()方法和apply()方法改变子类的作用域,从而子类的实例里面的属性和方法就不会被原型链给共享。(在子类构造函数里面添加:SuperType.call(this);)
- 将原型链和构造函数的技术组合到一块,便发挥了两者的优势,这种继承叫做“组合继承“。有时候也叫做伪经典继承。继承属性:
function SubType(name, age) {
//继承属性
SuperType.call(this, name); // call()方法改变了属性的作用域。
this.age = age; // 子类新建的属性不会被共享
}
继承方法:
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; //子类的构造函数指向自己
//后面的实例的构造函数就不在是指向父类的构造函数。
组合继承全部代码示例:
//构造父类
function SuperType(name) {
this.name = name;
this.colors = ['blue', 'black'];
}
//运用原型添加共享方法
SuperType.prototype.sayName = function() {
console.log(this.name);
};
//构造子类
function SubType(name, age) {
//继承属性
SuperType.call(this, name); // call()方法改变了属性的作用域。
this.age = age; // 子类新建的属性不会被共享
}
//继承方法:
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType; //子类的构造函数指向自己,后面的实例的构造函数就不在是指向父类的构造函数。
SubType.prototype.sayAge = function() {
console.log(this.age);
}
//创建子类的两个实例化
var instance1 = new SubType('Jobin', 18);
instance1.colors.push('red');
console.log(instance1.colors); // “['blue', 'black', 'red']”
instance1.sayName(); // “Jobin”
instance1.sayAge(); // 18
var instance2 = new SubType('Jiabing Yu', 23);
console.log(instance2.colors); // “['blue', 'black',]”
instance2.sayName(); // “Jiabing Yu”
instance2.sayAge(); // 23
从上面代码看出,这样就可以让两个不同的子类的实例即分别拥有自己的属性又可以拥有相同的方法,从而降低了耦合度。
-
因为组合继承最大的问题就是无论什么情况下,都会调用两次父类的构造函数: 一次是在创建子类原型的时候 (SubType.prototype = new SuperType();), 另一次就是在子类构造函数的内部(SuperType.call(this, name)😉。我们打印了一下instance1,发现子类最终会包含超类对象的全部实例属性,但我们不得不在调用子类构造函数时重写这些属性。
-
上面的代码,在控制台打印实例(instance1)和子类的原型(SubType.prototype)也是就是父类,会发现我们的属性name和colors,一组在实例上,一组在父类上(子类的原型)。
-
为了更高的效率和避免在子类的原型上面创建不必要的、多余的属性。因此我们使用了最理想的继承方式:“寄生组合式继承”。
-
所谓的寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。其背后的基本思路是:不必为了指定子类的原型而调用父类的构造函数,我们所需要的无非就是父类原型的一个副本而已。本质上,就是使用寄生式继承来继承父类的原型,然后再将结果指定给子类的原型。
寄生组合式继承:
//寄生组合式继承实现
function inheritPrototype(subType, superType){
var prototype = Object(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象
}
//构造父类
function SuperType(name) {
this.name = name;
this.colors = ['blue', 'black'];
}
//运用原型添加共享方法
SuperType.prototype.sayName = function() {
console.log(this.name);
};
//构造子类
function SubType(name, age) {
//继承属性
SuperType.call(this, name); // call()方法改变了属性的作用域。
this.age = age; // 子类新建的属性不会被共享
}
//继承方法:
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
console.log(this.age);
}
//创建子类的两个实例化
var instance1 = new SubType('Jobin', 18);
instance1.colors.push('red');
console.log(instance1.colors); // “['blue', 'black', 'red']”
instance1.sayName(); // “Jobin”
instance1.sayAge(); // 18
var instance2 = new SubType('Jiabing Yu', 23);
console.log(instance2.colors); // “['blue', 'black',]”
instance2.sayName(); // “Jiabing Yu”
instance2.sayAge(); // 23
现在上述代码的实例包含着属性和方法, 子类的原型(也就是父类)就不会再存在实例中的属性,只存在共享的方法。