什么是“原型”?当我们仔细观察时,会注意到Javascript中的每一个对象都有一个名为 prototype 的属性。 打开Chrome控制台,创建一个新对象并自行检查:
当我们查看[[Prototype]]属性是什么时,可以看到,它似乎是一个对象,里面也有很多值。若我们采用刚才创建的对象并尝试调用它的toString()方法,会发生什么呢?
你可能会认为:由于在对象上没有定义toString()方法,因此本次调用可能会失败,但奇怪的是它可以奏效,并且在控制台上打印了对象的值。如果仔细观察,就可以在[[Prototype]]键中找到一些线索,就像我们之前看到的那样,可以看到 Prototype 对象中有一个 toString() 方法。
但是这些是如何一起工作的呢?我们尝试将每个部分都放在一起,可以看到,当我们在JavaScript中创建一个对象时,该对象会自动添加一个称为"Prototype"的附加属性。该属性充当新创建的对象与其父对象之间的链接。
因此,当我们创建新的空白对象时,[[Prototype]]键始终是对"Object.prototype"的引用。而Object.prototype是JavaScript中的一个内置对象,其中包含一些实用程序,这正是我们看到的对象。
接下来是一个示例:
我们创建一个名为"parent"的新对象,其中包含两个属性:名为"species"的字符串和名为"breathe"的函数。
接下来用Object.create()的实用方法,如MDN文档中所述:
我们创建一个新对象"child",将"parent"作为其原型:
当我们展开''child''的[[Prototype]]时,可以看到,它有着与"parent"对象完全相同的属性:
由此可得,"child"是一个对"parent"的引用。我们甚至可以通过访问"child"的"species"属性来证明这一点:
这种方法也能帮助我们验证"原型是引用而不是副本":
我们给"parent"对象添加新属性,并发现该属性立即显示在"child"对象的"Prototype"中,这意味着我们现在甚至可以通过访问子元素上的新属性键来获得相应的值。
当我们试着去调用"child"的toString()方法时,可以发现它成功地执行了,并且在控制台上打印了相同的'[object Object]'
这个过程是怎么样的呢?我们可以看到,"child"的"prototype"中没有toString()这个方法,却能成功执行child.toString(),这就需要 原型链 来发挥作用了。
当我们调用JavaScript对象的任何一个属性时,引擎都会在后台执行很多操作,它首先检查该属性是否存在于当前对象上,若不存在,则访问该对象的原型,然后检查该属性是否存在于该原型对象上,如果仍然没有找到该属性,引擎将会再次访问该(原型)对象的原型,如此重复,这个过程称为 "沿着原型链向上移动"(Going up the Prototype chain)。就上个例子而言,当引擎找到toString方法时,原型链就以 Object.prototype 结束。
还有一点需要 注意的是,如果我们访问了一些不存在的属性,例如:child.xyz,引擎将再次重复相同的步骤,即从"child"到"parent"再到"Object.prototype"地遍历原型链 ,此时它会看到 Prototype 属性为"null",又因为原型链中没有更多的对象可供遍历,因此我们得到了"undefined"。