在JavaScript中,万物皆对象。每个对象都有一个特殊的内部属性,[[Prototype]]
(原型)。它是对于其他对象的引用,也就是说它关联到别的对象(如果它不为空)。
在日常中,我们除了经常听到原型
这个词之外,还经常会听到原型链
这个词,那么这两个词到底有什么关系呢,下面我们就来探究一下。
那首先我们来看一下[[Prototype]]
这个特殊的内部属性有什么用呢?我们先来讲一个关于属性访问的一些知识。看下面的代码:
var myObject = {
a:1;
};
myObject.a; //1
在这里,我们可以清楚的看到Object.a
是一个属性访问(访问了Object的a属性),那么深入一下,上面的代码实际上在Object上执行了一个[[Get]]
操作。对一个对象执行默认的[[Get]]
操作,会首先检查对象,寻找请求的属性,如果找到,就返回相应的值。如果没找到,这个时候对象的[[Prototype]]
属性以及它的[[Prototype]]
链就需要派上用场了。
先看代码:
var newObject = {
a:1;
};
var myObject = Object.create(newObject);
myObject.a; //1
首先我们可以看到在上面的代码中,a这个属性是不存在myObject这个对象里的,但是最后我们还是得到了a这个属性的值。
var myObject = Object.create(newObject);
这行代码就是关键了。它创建了一个对象myObject关联到newObject。也就是说myObject的[[Prototype]]
属性关联到了newObject这个对象。[[Get]]
操作在对象本身没找到需要的属性后,便顺着这个关联去newObject这个对象里进行查找。
所以在对属性进行查询的时候,就会顺着这个一层又一层的关联,也就是所谓的[[Prototype]]
链,对需要的属性进行查找。在[[Prototype]]
链找到了就返回属性值或者在链条的末尾都没找到就返回undefined。那么链条的末尾是什么呢?每个普通的[[Prototype]]
链的最顶端,是内建的Object.prototype,也就是null。
开头讲到,[[Prototype]]
这个属性是把一个对象关联到另一个对象。那么这个关联有什么用呢?
常见的用法那就是所谓的原型继承
和委托
了。
我们通过代码来理解一下:
function Foo(a) {
this.a = a;
}
Foo.prototype.myA = function() {
return this.a;
};
function Bar(a,b) {
Foo.call(this,a);
this.b = b;
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.myB = function() {
return this.b;
};
var Baz = new Bar( "1", "obj 1" );
Baz.myA(); // "1"
Baz.myB(); // "obj 1"
在上面的代码中,最重要的部分就是Bar.prototype = Object.create(Foo.prototype);
。这个代码应该会比较眼熟,因为在上面的例子中我们也用到了相似的代码,并且解释说是把一个对象关联到另一个对象上。那么这里我们是把新创建的Bar对象的内部的[[Prototype]]
链接到了指定的对象Foo.prototype
。
Bar和Foo在这里既是对象也是函数。函数有一个性质:所有的函数默认都会得到一个可以指向任何对象的属性prototype
。
指向的对象往往被称为“函数的原型”。每个由调用new Foo()而创建的对象将最终被[[Prototype]]
链接到所谓的“函数的原型”。
看段代码来深入了解一下:
function Foo(a){
this.a = a;
}
var bar = new Foo(2);
bar.a; //2
Object.getPrototypeOf(bar) === Foo.prototype; //true
那么为什么调用new Foo()创建的对象可以链接到函数原型呢?这就跟它的实现有关了。
当函数前面加new
调用时,会有以下事情完成:
- 一个全新的对象被创建
- 这个新的对象会被接入到原型链
- 这个新的对象被设置为函数调用的this绑定
- 除非函数有返回对象,否则这个被new调用的函数将自动返回这个新的对象
主要我们要看的是第二点,其他的会在this绑定的文章中讲到。
所以在上面的例子中,调用new Foo()创建bar的时候,bar会得到一个内部的[[Prototype]]
链接,链接到Foo.prototype所指向的对象。
在“原型继承”的例子代码中var Baz = new Bar( "1", "obj 1" );
也是使用了new来创建对象,从而使baz对象链接到bar.prototype
上.
到目前为止,我们提到了2种方法来创建新对象并与其他对象关联:
- Object.create()
- 函数前面加new调用
表面上看好像都可以达到我们的目的,但是第二种方法可能会有意想不到的副作用产生。假如说Foo()这个函数还可以改变状态或者向this添加数据属性等,那么在对象在被创建的时候就会有这些我们可能本身并不需要的东西。
最后用一张图来总结一下原型继承:(图来源:You-Dont-Know-JS)
从图中可以看出,箭头由右至左,由下至上。而[[Prototype]]
机制,就是一种存在于对象上的内部链接,指向另一个对象。
事实上,“原型继承”这个词很容易让人产生误会,因为JavaScript中没有类,也就没有继承。实际上,Bar与Foo不能说是继承的关系,用准确点的术语来说,是委托。所以接下来我们就要来了解一下委托
了。
很简单,委托实际上就是原型链。比如说新建对象a链接到对象b,a可以使用b的方法,也就是a委托了b(来实现它想要实现的效果)。这样的话直接用Object.create就可以了,不需要再调用new函数了。var a = Object.create(a);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。