细说JS原型链

什么是原型链

在JavaScript中,每个对象都有一个指向它的原型(prototype)对象的内部链接。这个原型对象又有自己的原型,直到某个对象的原型为null为止(也就是不再有原型指向),组成这条链的最后一环。这种一级一级的链结构称为原型链(prototype chain)。

prototype的由来

为什么JS的设计者要设计一个prototype呢,就是为了让JS能够实现继承机制。那为什么不像C++或者Java那样引入“父类”或者“子类”的概念呢,因为最初JS的发明者认为JS只是一门简单的语言,不想让JS正式的成为一门非常标准的面向对象编程语言,会增加初学者的入门难度。(不过随着互联网的快速发展,JS越来越重要,这个最初想让其看起来简单的原型链,却适得其反的增加了很多初学者的入门难度,ES6的很多语法糖让其看起来更容易理解,不过JS继承的本质还是基于原型链)

__proto__ VS prototype

根据 ECMAScript 标准,someObject.[[Prototype]] 符号是用于指派 someObject 的原型。这个等同于 JavaScript 的 proto 属性(现已弃用)。从 ECMAScript 6 开始, [[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()访问器来访问。

__proto__已经被弃用,但是为了方便讲解,还是用它来表示一下原型指向)

简单来说:
__proto__实际上是对象用来查找其原型链上的方法、属性等等的引用对象;
prototype 是当你用 new 关键字来创建实例对象的时候,被用来构建实例对象的 __proto__ 的。
如果你觉得难以理解,不妨看看下面的描述:
prototype 是函数对象(Function object)的一个属性,它是由这个函数对象创建的所有的实例对象的原型(公用);
__proto__是一个对象的内置属性,指向构造它的函数的 prototype__proto__存在于每个对象中,而 prototype 只有函数对象才有。

原型链指向

举个例子:

1
2
function Foobar(){}
var foobar = new Foobar()

请分别描述 Foobarfoobar 的原型链。

Foobar:

1
2
3
Foobar.__proto__ => Function.prototype
Function.prototype.__proto__ => Object.prototype
Object.prototype.__proto__ => null

foobar:

1
2
3
foobar.__proto__ => Foo.prototype
Foo.prototype.__proto__ => Object.prototype
Object.prototype.__proto__ => null

原型链大致可以分为两类(是否经过Function.prototype),

一个是普通实例对象的原型链,会沿着它的构造函数原型一直到头(不会经过Function.prototype)
一个是函数的原型链,会经过Function.prototype一直到头

引用来自 Jichao Ouyang 的博客的一张图:

这里有个很让人困惑的东西,Object.__proto__ 指向了 Function.prototypeFunction.prototype.__proto__ 又指向了 Object.prototype
为了避免混乱,你只需要记住,JS中所有的对象都是由Object衍生的对象,所有的对象都继承了Object.prototype的方法和属性。
也就是说 new Object() 中的 Object 实际上是 Function 构造出来的,这就会产生理解上的歧义,刚才还说所有的对象都是Object衍生的,为啥这里的Object是Function构造出来的呢,我个人理解为,Object和Object.prototype不是一回事。
你也会发现,Function instanceof ObjectObject instanceof Function 结果都为true,也就是说,他们都能够在各自的原型链中找到对方

instanceof VS typeof

第三部分提到了 instanceof ,其用法如下所示:
object instanceof constructor
MDN官方解释为,instanceof 运算符用来测试object在其原型链中是否存在constructor的prototype属性。
因此这个值不是一成不变的,比如

1
2
3
4
var foo = new Foo();
foo instanceof Foo //true
foo.__proto__ = {}
foo instanceof Foo //false

JS中还有一个操作符,typeof
该操作符返回一个字符串,指示未经计算的操作数的类型

其返回结果只有表格中的几种
这里需要注意

1
2
3
4
5
typeof Object
typeof Function
typeof String
typeof Boolean
typeof Number

他们的返回结果都是 'function'

1
2
3
4
var s1 = new String('abc')
var s2 = 'abc'
typeof s1 //'object'
typeof s2 //‘string’

所以通过JS的内置对象new出来的类型,诸如new Stringnew Number他们还是一个引用类型,typeof 返回的结果依然是 “object”而不是其对应的基本类型
只有通过直接量赋值的变量,诸如var a = 1,var s = 'abc'
typeof 才会返回其基本类型

参考资料

MDN
Jichao Ouyang的个人博客
阮一峰老师的博客