在 JavaScript 中,大部分东西都是对象,数组是对象,函数也是对象,对象更加是对象。不管我们给数组和函数定义什么内容,它们总是有一些相同的方法和属性。比如说hasOwnProperty(),toString()等。
让我们通过一个例子由浅到深地理解原型链:
function Foo(_name) {
this.name = _name;
}
Foo.prototype.show = function() {
console.log('I am ', this.name);
};
var f1 = new Foo('obj1');
var f2 = new Foo('obj2');
f1.show(); // I am obj1
f2.show(); // I am obj2
这是我们经常使用的创建对象的方式,将共同的方法放到 Foo.prototype 中,所有实例都共有这个方法了。
这是怎么实现的呢?我们看下面这张图:
我们先只用看第一行
我们定义的show
函数在Foo.prototype
中,当我们执行f1.show()
时,JavaScript 发现f1
本身没有show
这个属性,所以它就到f1
的原型(也就是__proto__
指向的对象)去找,找到了就可以调用。
注:每个对象都有一个方法 hasOwnProperty() 来检查对象本身是否有某个属性,如果有则返回 true ;如果这个属性在它的原型链上或原型链上都没有,则返回 false ;
图片第一行告诉了我们4点:
- 所有函数都有一个
prototype
指针,指向原型对象,如图中的Foo
的prototype
指针。prototype
指针的意义是,当我们使用这个构造函数new
出新对象的时候,新对象的原型是谁。 - 构造函数的
prototype
所指向的原型对象有一个constructor
指针,指回构造函数。如图中Foo.prototype
的constructor
指针指向Foo
。constructor
指针有助于我们找到一个对象的构造函数是谁。 __proto__
每个对象都有,JavaScript 在new
一个对象的时候,会将它的__proto__
指向构造函数的prototype
指向的那个对象。在上图中,f1
、f2
这些实例对象的__proto__
都指向了Foo.prototype
。- 如果一个对象的
__proto__
指向了另一个对象,那么前者就继承了后者的所有属性。
注:__proto__ 与 prototype 的区别!__proto__ 才是真正连接原型链的东西,而 prototype 只是构造函数的一个指针属性而已。
图片后面展示了 JavaScript 原生对象的继承关系
先看Foo
的原型。Foo
是一个函数,它的构造函数是 JavaScript 内部的function Function()
,Function
的prototype
指向了一个对象Function.prototype
,因此Foo
的__proto__
就指向了Function.prototype
。
注:所有的函数都以 function Function() 为构造函数,因此,所有函数(包括 function Function() 和 function Object() )的 __proto__ 都指向 Function.prototype 这个对象,这个对象中定义了所有函数都共有的方法,比如 call() 、 apply() 等。
Function.prototype
这个对象,是一个普通的对象,它的构造函数是 JavaScript 内置的function Object()
,function Object()
的prototype
指向Object.prototype
,因此Function.prototype.__proto__
就指向Object.prototype
,这个对象中定义了所有对象共有的属性,比如我们之前说的hasOwnProperty()
和toString()
等
注:同理,Foo.prototype 和其他自定义的对象也是 __proto__ 指向 Object.prototype 对象,就不需要说明了。
Object.prototype
就是原型链的终点了,它的__proto__
是null
,JavaScript 查找属性时,如果到这里还没有找到,那就是undefined
了。
注:到这里就不难理解为什么我们说在 JavaScript 中,函数也是对象了,它就是继承自对象的。