JS 原型以及原型链
/** * 原型对象 * 无论什么时,只要创建一个新函数,就会根据一组特定的规则为该函数创建一个 prototype 属性 * 这个属性指向该函数的原型对象。默认情况下,所有原型对象都会自动获得一个 constructor(构造函数) * 属性,这个属性包含一个指向 prototype 属性所在函数的指针。当构造函数创建一个新实例后, * 该实例的内部包含一个指针[[Prototype]](内部属性),指向构造函数的原型对象。 */ /** * 如: * 创建一个函数 A:function A(){} * function A(){} 会有一个属性: prototype 属性 * function A(){} 的原型对象为: A.prototype * function A(){} 的原型对象会自动获得一个 constructor 属性: A.prototype.constructor * 那么: * function A(){} 中的 prototype 属性 ---(指向)---> A.prototype (function A(){} 的原型对象) * A.prototype.constructor (constructor属性) ---(指向)---> function A(){} (函数 A) */ function A(){} console.log(A.prototype.constructor==A);//true 可见原型对象中的 constructor 指向函数A /** * 如: * 使用 new 关键字结合 A() 函数来创建实例 obj * 则 obj 这个实例会有一个 [[Prototype]] 内部属性指向 A 的原型对象 A.prototype * Firefox、Safari 和Chrome 在每个对象上都支持一个属性 __proto__ ,它就相当于 [[Prototype]] */ var obj=new A(); console.log(obj.__proto__==A.prototype);//true 可见实例中的 [[Prototype]] 指向函数原型 A.prototype A.prototype.name="guang";//给A的原型对象增加一个name:"guang"属性 A.prototype.sayName=function(){return this.name};//给A的原型对象增加一个sayName方法 var obj=new A(); console.log(obj.sayName());//guang
// 上图展示了A 构造函数、A 的原型属性以及A 现有的实例 obj之间的关系。 // 在此,A.prototype 指向了原型对象,而A.prototype.constructor 又指回了A。 // 原型对象中除了包含constructor 属性之外,还包括后来添加的其他属性。A 的每个实例— // 都包含一个内部属性,该属性仅仅指向了 A.prototype原型对象,而构造函数没有直接的关系。 // 虽然这 obj实例不包含属性和方法,但却可调用 A.sayName(),这是通过查找对象属性的过程来实现的。 // 每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先 // 从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到, // 则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这 // 个属性,则返回该属性的值。 // 对于上面的例子而言,在调用A.sayName()的时候,会先后执行两次搜索 // 首先,解析器会问:“实例A 有sayName 属性吗?”答:“没有。”然后,它继续搜索,再 // 问:“A 的原型有sayName 属性吗?”答:“有。”于是,它就读取那个保存在原型对象中的函数。 // 而这正是多个对象实例共享原型所保存的属性和方法的基本原理。
/** * 原型链 * 每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。 * 当一个原型对象是另一个原型对象的实例时,该原型对象将包含一个指向另一个原型的指针。相应地,另一个原型中也包含着一个指向 * 另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条 */ function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); console.log(instance.getSuperValue()); //true
console.log(instance.constructor == SubType,instance.constructor == SuperType); //false true
//以上代码定义两个类型:SuperType、SubType。通过创建 SuperType的实例,原型对象 SubType.prototype 继承了SuperType的属性 //本质上是通过重写原型对象实现继承。如图:
// 在上面的代码中,并没有使用SubType 默认提供的原型,而是给它换了一个新原型;这个新原型 // 就是SuperType 的实例。于是,新原型不仅具有作为一个SuperType 的实例所拥有的全部属性和方法, // 而且其内部还有一个指针,指向了SuperType 的原型。 // 最终结果就是这样的:instance 指向SubType的原型, SubType 的原型又指向SuperType 的原型。 // getSuperValue() 方法仍然还在 SuperType.prototype 中,但property 则位于SubType.prototype 中。 // 这是因为property 是一个实例属性,而getSuperValue()则是一个原型方法。既然SubType.prototype 现在是SuperType // 的实例,那么property 当然就位于该实例中了。此外,要注意instance.constructor 现在指向的 // 是SuperType,这是因为SubType 的原型指向了另一个对象——>SuperType 的原型,而这个原型对象的constructor 属性指向的是SuperType。 // 在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。就拿上面的例子来说,调用 // instance.getSuperValue()会经历三个搜索步骤:1>搜索实例;2>搜索SubType.prototype;3>搜索SuperType.prototype, // 4>最后一步才会找到该方法。在找不到属性或方法的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。
// 事实上,因为所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。 // 因此默认原型都会包含一个内部指针,指向Object.prototype。这也正是所有自定义类型都会继承toString()、valueOf()等方法的根本原因。对该例子来说完整的原型链应该如下: