昨天看了大牛的这篇关于模拟bind函数实现的文章,深受启发。其中有一处涉及到关于bind方法返回的函数作为构造函数的问题令我百思不得其解。于是决定补补基础,探究构造函数和new操作究竟是个什么东西。
The new Operator
首先我去翻了翻MDN关于New操作详解,简单地摘一些比较关键信息:
When the code new Foo(...) is executed, the following things happen:
- A new object is created, inheriting from Foo.prototype
- The constructor function Foo is called with the specified arguments, and with this bound to the newly created object.
用代码表示如下:
// 模拟var foo = new Foo('foo')的过程
// A new object is created
var foo = {}
// inheriting from Foo.prototype
foo.__proto__ = Foo.prototype
// The constructor function Foo is called with the specified arguments, and with this bound to the newly created object.
Foo.call(foo, 'foo')
为了更好的理解为什么是foo.__proto__ = Foo.prototype
,我需要对__proto__
和prototype
之间的关系的作更深入探究。
prototype
-
prototype
是什么?- 构造函数的一个属性
- 包涵
constructor
字段的一个Object
- 谁有
prototype
?- 所有非原生函数与原生构造函数
// 原生函数不能成为构造函数,没有prototype属性
var nativeFunction = Function.prototype.call
console.log(nativeFunction.__proto__) // function () {}
console.log(nativeFunction.prototype) // undefined
new nativeFunction() // Uncaught TypeError: nativeFunction is not a constructor
prototype
是构造函数才具备的属性,称呼其“原型”,其实是相当不严谨甚至严重误导的说法。prototype
本质是JavaScript这门语言为开发人员操作原型对外暴露的一个接口。开发人员对prototype
的操作,最终都会反映到该改构造函数的实例的__proto__
上。
热爱面向对象的朋友一定很熟悉下面这段代码。
var Foo = function () {}
Foo.prototype = {
constructor: Foo,
// ...
}
var foo = new Foo()
我们重写了Foo.prototype
,都必须手动添加constructor
。如果不这样做的话,实例的原型foo.__proto__
将找不到constructor
。
[[proto]]
-
__proto__
是什么?- 原型链上的一个原型
- 谁有
__proto__
?- 所有Object都有(null除外)
var Foo = function () {}
var foo = new Foo()
// 构造函数 Foo => function () {} => Object {} => null
console.log(Foo.__proto__) // function () {}
console.log(Foo.__proto__.__proto__) // Object {} | 注意与构造函数Object区别
console.log(Foo.__proto__.__proto__.__proto__) // null | 是不是很意外?typeof null === Object并非空穴来风
// 实例 foo => { constructor: (...), __proto__: (...) } => Object {} => null
console.log(foo.__proto__) // { constructor, __proto__,... } | 这是由Foo的prototype属性决定的
console.log(foo.__proto__.__proto__) // Object {}
console.log(foo.__proto__.__proto__.__proto__) // null
总结
prototype
的意义在于开发人员可以以此定义实例的原型,这也是JavaScript
面向对象的基础。
__proto__
在原型的维度上自成一列,构建了JavaScript
强大的原型体系,与prototype
不是一个层次上的概念。
如果一定要说二者有什么关系的话,我觉得应该说毫无关系。
// 这不是代码
Foo.prototype.constructor => Foo
Foo.prototype.constructor.prototype.constructor => Foo
Foo.prototype.constructor.prototype.constructor.prototype.constructor => Foo
Foo.prototype.constructor.prototype.constructor.prototype.constructor.prototype.constructor => Foo
// 无穷无尽... 我仿佛看见了循环链表的数据结构