Javascript中的每一个函数都有一个prototype属性,其值为指向该函数的原型对象(简称原型)的引用。对象的原型,是由创建该对象的构造函数定义的。本文将记录与prototype相关的一些概念,弄清楚这些基本概念是理解prototype的关键。
理解原型
函数一旦创建,就会同时为该函数创建一个prototype属性,默认该prototype属性会自动获得一个constructor属性指向这个函数的引用,看一个例子:
function Site(){} console.log(Site.prototype.constructor === Site); // true
上面创建了一个空函数Site,Site函数的prototype属性的constructor属性正指向Site函数。
同时,Site的原型属性还会从Object对象继承方法,如:
console.log("valueOf" in Site.prototype); // true
无论把Site当作函数或者构造函数,上面的prototype行为都是成立的。当通过new实例化一个Site对象实例时(即Site此时作为构造函数),此时会创建一个实例的内部属性[[Prototype]](通常浏览器中表现为__proto__),这个属性用于连接实例和构造函数的原型属性,即它指向构造函数的原型。如:
Site.prototype.name = "csser"; Site.prototype.url = "http://www.csser.com/"; Site.prototype.go = function(){ location.href = this.url; } var site1= new Site(); var site2 = new Site(); console.log(Site.prototype.isPrototypeOf(site1)); // true
上面的代码通过isPrototypeOf()方法确定了Site.prototype是site1实例的原型。下面的图例直观的说明了构造函数、实例和原型之间的关系。
同时我们知道,函数也是对象,也应该具有内部属性[[Prototype]],这个留在本文最后的测试题中供读者研究。
实例成员与原型成员
实例成员会分配成员的一份副本给实例,原型成员则分配一个引用的指针。原型的成员是共享给每一个实例的:
site1.__proto__.name = "popcg"; // 请在非IE的浏览器下测试 console.log(site2.name); // "popcg"
因为site1和site2的内部属性__proto__都指向同一个原型,原型的name属性被修改,结果也会反映在site2上。下面不修改原型:
site1.name = "popcg"; console.log(site1.name); // popcg console.log(site2.name); // csser
上面的示例中,给site1实例增加了实例属性name,在读取site1的name属性时,屏蔽了原型中保存的同名name属性。site2的实例中不存在name属性,所以返回了原型中的name属性。这里涉及到对象成员的搜索顺序,首先搜索实例成员,如果找不到便搜索原型中的同名成员。
可以使用hasOwnProperty()方法检测一个成员是存在于实例中还是原型中:
console.log(site1.hasOwnProperty("name")); // true console.log(site2.hasOwnProperty("name")); // false
可以使用in操作符检测成员是否可以被访问到(无论存在于实例或者原型中):
"name" in site1; // true "name" in site2; // true "test" in site1; // false, test是一个不存在的成员
prototype属性与对象的内部属性[[Prototype]]
- 每一个函数都有显式的prototype属性,它表示通过该函数创建的对象的原型。
- 每一个对象都具有一个隐式的内部属性[[Prototype]],它指向其对应的原型,对象的原型也会有[[Prototype]]属性指向它所对应的原型,这便是原型链的结构。
- Javascript中的对象都可以通过原型链关联起来,原型链的最顶层为Object.prototype,其[[Prototype]]为null
Object与Function
Object和Function各为其自身的实例,也互为对方的实例,如:
Object instanceof Function; // true Object instanceof Object; // true Function instanceof Function; // true Function instanceof Object; // true
Object和Function对象都是Function构造的,如:
Object.__proto__.constructor; // function Function() { [native code] } Function.__proto__.constructor; // function Function() { [native code] } Function.__proto__.constructor == Function; // true Object.__proto__.constructor == Function; // true
原型链
通过前面的理解,每个对象都有一个内部属性[[Prototype]]指向其构造函数的原型对象,原型对象也会含有这个属性,于是,这种层层指向父原型的关系便形成了原型链。
如图所示,红色部分展示了本文例子的原型链结构。
测试题
测试题目来源:http://jsfox.cn/blog/javascript/understanding-javascript-prototype-chain.html
题目大意:
var str = "string"; var Fn = function() {var i;}; var f = new Fn();
请说出以下语句执行结果的值:
str.__proto__ str.prototype str.constructor str.__proto__.constructor Fn.__proto__ Fn.prototype Fn.constructor Fn.__proto__.constructor Fn.__proto__.__proto__ f.__proto__ f.prototype f.constructor f.__proto__.constructor f.__proto__.__proto__
如果理解了原型链,这道题目不难,笔者刚开始就在Fn的原型上出现了判断失误,答错了2道题,经过研究画出了下面的原型关系图,供参考: