原文出自:https://www.pandashen.com
概述
在 JavaScript 中有种说法叫 “万物皆对象”,就是说无论是构造函数创建的实例,构造函数本身、原型对象、数组、函数本质上都是对象,都拥有 __proto__
属性,即隐式原型,所有函数都拥有 prototype
属性,即显式原型(仅限函数),原型对象(prototype
属性指向的对象),在定义函数时就被创建。
原型链指向概述
在 JavaScript 中整个原型链及查找机制用下图可以完整的表示出来:
原型链指向:
1、通过字面量和 new Object()
所创建的对象,他们是构造函数是 function Object()
的实例,Object
构造函数的 prototype
指向原型对象 Object.prototype
,Object.prototype
的 constructor
指向构造函数 Object
,而实例的 __proto__
也指向 Object.prototype
,Object.prototype
的 __proto__
指向 null
,所以 Object.prototype
也叫做顶级原型对象。
2、上图中 new Foo()
创建的对象是构造函数 function Foo()
的实例,Foo
的 prototype
指向原型对象 Foo.prototype
,Foo.prototype
的 constructor
指向构造函数 Foo
,而实例的 __proto__
也指向 Foo.prototype
,并且 Foo.prototype
虽然是原型对象,但也是对象,所以是构造函数 Object
的实例,__proto__
指向顶级原型对象 Object.prototype
。
3、数组的构造函数是 function Array()
原型链的指向与其他除 Object
以外的构造函数相同,Array.prototype
的 __proto__
也指向顶级原型对象 Object.prototype
,每一个数组都是 Array
的实例,__proto__
都指向 Array.prototype
。
4、Object
、Array
、Foo
等构造函数的本质也是对象,他们的构造函数是 function Function()
,Function
的 prototype
指向 Function.prototype
,Function.prototype
的 constructor
指向 Function
,所有的构造函数的 __proto__
都指向 Function.prototype
,包括 Function
本身,也就是说构造函数 Function
是由自己构造的,Function.prototype
的 __proto__
同样指向顶级原型对象 Object.prototype
。
prototype 原型对象
prototype
是函数的一个属性,属性的值指向了一个对象,所以,只有函数才有 prototype
原型对象。
function Person(name, age) {
this.name = name;
this.age = age;
}
typeof Person.prototype; // object
Person.prototype.constructor; // Person {}
Person.prototype.job = "qianduan";
var p1 = new Person("panda", 18);
var p2 = new Person("shen", 20);
p1.constructor.prototype; // 实例对象查找构造函数原型对象的方法
一般会把对象共有的属性和方法都放在构造函数的原型对象上。
实例、构造函数、原型对象的关系
构造函数的原型 prototype
属性指向一个原型对象,实例也可以通过 __proto__
指向原型对象,但本质上实例和构造函数之间是没有关系的。
function Person(name, age) {
this.name = name;
this.age = age;
}
var p = new Person("nihao", 16);
p.constructor = { name: "haha" };
p.name; // nihao
上面的代码中改变了构造函数的值为一个对象,对象中的属性 name
并没有影响实例的 name
属性值。
实例属性 __proto__
上面访问实例 p
的原型,实际使用 p.constructor.prototype
去找原型对象,当构造函数的值改变后是找不到原型对象的,所以实例并不是通过 constructor.prototype
去查找原型对象的,而是通过每一个实例都有的 __proto__
属性,这个属性指向创建实例的构造函数原本的原型对象,这个属性不是标准,在 IE 下不存在。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.job = "qianduan";
var p = new Person("nihao", 16);
p.__proto__.job; // qianduan
当构造函数的 prototype
属性值被改变之后,在之前创建的实例的 __proto__
属性值的仍然引用原型对象,所以对构造函数改变前创建的实例是没有影响的,会影响后面创建的实例。
原型链查找机制
实例对象在调用了一个属性或方法时,如果对象本身没有这个属性或方法,会去自己的原型对象查找,也就是 __proto__
中查找,如果原型对象中没有,去原型对象的原型对象查找,一般(原型链没有被修改)情况下就是去 __proto__
的 __proto__
中查找,即顶级原型对象 Object.prototype
,如果实例对象本身有这个属性,则直接输出,不再向上查找,如果对象本身和原型对象具有同名属性,则会屏蔽掉原型对象的属性。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.job = "qianduan";
var p = new Person("nihao", 16);
p.job; // qianduan
p.job = "houtai";
p.job; // houtai
p.__proto__.job; // qianduan
总结
原型链的指向及原型链的查找机制是 JavaScript 中非常重要的基础知识,理解原型链是更深入了解继承和面向对象编程的必经之路。