原型和原型链
首先认识一下原型的定义
Every JavaScript object has a second JavaScript object (or null, but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.
每一个 JavaScript 对象都和另一个对象相关联,另一个对象就是原型,每一个对象都从原型继承属性。
具体实现中,每个对象都有一个 __proto__
属性,指向一个名为原型(Prototype)的对象,由此产生了“关联”。原型本身也有 __proto__
属性,指向原型的原型,直到最后不再需要继承任何属性,这个链状结构名为原型链(Prototype Chain)。
同时,需要知道原型属性正式名称为 [[Prototype]] 而非 __proto__
,可以理解为 __proto__
是 [[Prototype]] 插槽的一种实现,用于访问原型对象。
遵循 ECMAScript 标准,符号
obj.[[Prototype]]
用于标识 obj 的原型。内部插槽 [[Prototype]] 可以通过Object.getPrototypeOf()
和Object.setPrototypeOf()
函数来访问。这个等同于 JavaScript 的非标准但被许多 JavaScript 引擎实现的属性__proto__
访问器。
对象可再分为函数对象和普通对象,但是只有函数对象才有 prototype
属性。普通对象和函数对象的原型指向也不太一样,但是都是指向构造他们的函数对象的 prototype
。
基于原型链的继承
JavaScript 通过沿着原型链向上查找的方式寻找属性及方法,实现属性和方法的继承。同时,原型的指向可以随时修改。
JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
构造函数
构造函数用于创建对象。当函数被用作构造函数的时候,其 prototype
属性将分配给 所有 对象实例的 __proto__
,作为对象实例的原型。
除了通过 new
创建实例,还可以通过字面量的隐式构造函数创建对象实例。如对象 { a: 1 }
、数组 []
、正则 /^hello$/
都使用了隐式构造函数。
构造函数的 prototype
默认具有一个 constructor
属性,指向构造函数本身,用于记录对象的构造函数。
Object
和 Function
的鸡和蛋的问题
这个标题来自于 @creeperyang,用来描述 Object
和 Function
谁创造了对方的问题很贴切。
JavaScript 的原型链层级图可以表示如下
JavaScript Object Layout from mollypages.org
函数对象的原型指向了 Function.prototype
,普通对象的原型指向了 Object.prototype
。根据 ECMA 定义,Function.prototype
也是一个标准的对象,以 Object.prototype
为原型。
The initial value of Function.prototype is the standard built-in Function prototype object (15.3.4).
所有对象的原型最后都指向了 Object.prototype
。但是 Object.prototype
的原型呢?ECMA 规定,Object.prototype
原型为 null
,它不需要从其他原型中继承属性。
The value of the [[Prototype]] internal property of the Object prototype object is null, the value of the [[Class]] internal property is "Object", and the initial value of the [[Extensible]] internal property is true.
如果我们直接使用 null
作为原型创建对象,新对象不会继承任何来自 Object.prototype
的属性,如 toString()
。
“🤔 悖论”:Object
是构造函数,原型为 Function.prototype
,Function.prototype
的原型又是 Object.prototype
。Function
和 Object
互为对方的上层原型。是谁创造了谁呢?
ECMA 对于 Function prototype 对象的说明
The Function prototype object is itself a Function object (its [[Class]] is "Function") that, when invoked, accepts any arguments and returns undefined.
The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object (15.2.4). The initial value of the [[Extensible]] internal property of the Function prototype object is true.
Function.prototype
是一个函数对象,而且可以被调用Function.prototype
是一个标准的、Object.prototype
为原型的对象
可以理解为 Function.prototype
是一个特殊的函数,它是所有函数的原型。对于 Object
而言,本身就是一个构造函数,所以原型指向 Function.prototype
。
然后就可以解释通了,Object.prototype
是原型链的顶端,它创建了 Function.prototype
,Function.prototype
又创建了 Object
、Function
等构造函数。
改变原型的方法
Object.create()
- (👍 推荐)使用
__proto__
访问器 - 使用
Object.setPrototypeOf()
修改
在需要修改原型指向的时候,推荐使用 __proto__
直接修改。
__proto__
被所有的现代引擎所支持。与Object.prototype.__proto__
setter 相反,对象字面量初始化器中的__proto__
是标准化,被优化的。甚至可以比Object.create
更高效。