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道题,经过研究画出了下面的原型关系图,供参考: