原型和原型链
# 原型和原型链
# 构造函数
# 什么是构造函数
constructor 返回创建实例对象时构造函数的引用。此属性的值是对函数的本身的引用,而不是一个包含函数名称的字符串。
function Person(name, age) {
this.name = name;
this.age = age;
}
p = new Person('zhang san', 18)
console.log(p.constructor === Person); // true
console.log(p.constructor === Object); // false
构造函数本身就是一个函数,和普通函数没有任何区别,不过为了规范一般将首字母大写。 构造函数和普通函数的区别在于:使用new 生成实例的函数就是构造函数,直接调用的就是普通函数。 那这样是不是意味着普通的函数创建的实例没有 constructor 属性呢? 不一定。
function score() {
return {
name: 'lisi'
}
}
var s = score()
console.log(s.constructor === Object);
# Symbol 是构造函数吗
Symbol 是基本数据类型,但是作为构造函数来说它并不完整,因为它不支持语法new Symbol(),Chrome 认为其不是构造函数,如果要生成实例直接使用 Symbol()即可。
console.log(new Symbol(123)); // Symbol is not a constructor
console.log(Symbol(123)); // Symbol(123)
虽然 Symbol是基本数据类型,但是Symbol(123)实例可以获取constructor属性值。
var s = Symbol(123)
console.log(s.constructor); // f Symbol(){ [native code] }
这里的 constructor 属性来自哪里? 其实是 Symbol 原型上的,即Symbol.prototype.constructor 返回创建实例原型的函数,默认为 Symbol 函数。
# constructor值只读吗
这个要分情况来看的,对于引用类型来讲 constructor 属性值是可以修改的,但是对u基本类型来说是只读的。 引用类型中constructor值可以修改,比如在原型链继承方案中,需要对constructor重新赋值进行修改。
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype = {
method: function () { }
};
function Teacher() {
}
Teacher.prototype = new Person('zhang san', 19)
Teacher.prototype.fs = 'Hello Person';
console.log(Teacher.prototype.constructor === Object); // true
Teacher.prototype.constructor = Person;
console.log(Teacher.prototype); // {name: "zhang san", age: 19, fs: "Hello Person", constructor: ƒ}
var t = new Teacher('zhang san', 10)
console.log(t); // Teacher {}
console.log(t.name); // zhang san
console.log(t.age); // 19
对于基本类型来说 constructor 是只读的 如 1、‘hello world’、true、Symbol 当然 null 和 undefined 是没有 constructor 属性的。
function Type() { };
var types = [1, "hello world", true, Symbol(11)];
for(var i = 0; i < types.length; i++) {
types[i].constructor = Type;
types[i] = [ types[i].constructor, types[i] instanceof Type, types[i].toString() ];
};
console.log( types.join("\n") );
// function Number() { [native code] }, false, 1
// function String() { [native code] }, false, muyiy
// function Boolean() { [native code] }, false, true
// function Symbol() { [native code] }, false, Symbol(123)
为什么呢? 因为创建他们的是只读的原生构造函数(native constructors), 这个例子也说明了依赖一个对象的 constructor 属性并不安全。
# 模拟new的实现
function myNEW() {
// 1、创建一个空对象
var obj = Object.create(null)
// 2、拿到我们的构造函数并删除arguments中的第一项
var handler = Array.prototype.shift.call(arguments)
// 3、链接到原型,obj可以访问构造函数原型中的属性
Object.setPrototypeOf(obj, handler.prototype)
// 4、绑定 this 实现继承,obj可以访问到构造函数中的属性
var ret = handler.apply(obj, arguments)
// 5、优先返回构造函数中的返回的对象
return ret instanceof Object ? ret : obj
}
function Person(name, age) {
this.name = name;
this.age = age;
}
console.log(myNEW(Person, 'zhang san', 19));
console.log(new Person('zhang san', 19));
# prototype
Javascript 是一种基于原型的语言(prototype-based language),这个和 Java 等基于类的语言不同。
每个对象拥有一个原型对象,对象以其原型为模板,从原型继承方法和属性,这些属性和方法定义在对象的构造器函数的 prototype 属性上,而非对象实例本身。
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person('zhang san', 18)
console.dir(Person)
console.log(Person.prototype.constructor === Person);
console.log(Person.prototype);
// 打印结果如下
ƒ Person(name, age)
arguments: null
caller: null
length: 2
name: "Person"
prototype:
constructor: ƒ Person(name, age)
__proto__: Object
__proto__: ƒ ()
// 2
{constructor: ƒ}
constructor: ƒ Person(name, age)
__proto__: Object
// 3
true
我们写了一个构造函数,然后通过 console.dir 打印出了 Person 通过执行结果 我们可以看到 构造函数Person 有一个原型对象 prototype, 其上有两个属性,分别是 constructor 和 proto
构造函数Person 有一个指向原型的指针(Person.prototype),原型 Person.prototype 有一个指向构造函数的指针,通过上面的代码我们会发现,这是一个循环引用。
# proto
上面代码可以看到 Person 原型(Person.prototype) 上有 proto 属性, 这个是一个访问器属性(即 getter 函数 和 setter 函数), 通过它可以访问到对象的内部[[Prototype]] 这个[[Prototype]]可以是一个对象 也可能是 null。 对象 或 null。
proto 发音 (dunder proto),最先被 Firefox 使用,后来在 ES6 被列为 Javascript 的标准内建属性。
[[Prototype]] 是对象的一个内部属性,外部代码无法直接访问。 --》 遵循 ECMAScript 标准,someObject.[[Prototype]] 符号用于指向 someObject 的原型。 proto dunder proto 是每个实例上都会有的属性, 但是 prototype 是构造函数的属性, 这两个并不相同,但是他们指向同一个对象。
function Person(name, age) {
this.name = name;
this.age = age;
}
var person = new Person('zhang san', 18)
console.log(person.__proto__ === Person.prototype); // true
这里获取 person 的对象的原型 与 Person 的原型对象进行比较
那么 构造函数、实例对象、对象原型之间的关系:
首先构造函数的 dunder proto(proto)指向的是 对象原型 [CONSTRUCTOR -- 构造函数 ].prototype ,然后对象原型中有属性 constructor 指向构造函数,然后构造函数的 prototype 指向对象原型。
由于工作环境无法上传图片 就给各位老板手绘一波关系图
.__proto__ .constructor
实例 person ---------------- > 对象原型 Person.prototype --------------------- > 构造函数 - Person
^ |
|________________________________________________|
.prototype
注意: proto 属性在 ES6 时才被标准化,以确保 Web 浏览器的兼容性,但是不推荐使用,除了标准化的原因之外还有性能问题。为了更好的支持,推荐使用 Object.getPrototypeOf().
通过改变一个对象的 [[Prototype]] 属性来改变和继承属性会对性能造成非常严重的影响,并且性能消耗的时间也不是简单的花费在 obj.__proto__ = ... 语句上, 它还会影响到所有继承自该 [[Prototype]] 的对象,如果你关心性能,你就不应该修改一个对象的 [[Prototype]]。
如果要读取或修改对象的 [[Prototype]] 属性,建议使用如下方案,但是此时设置对象的 [[Prototype]] 依旧是一个缓慢的操作,如果性能是一个问题,就要避免这种操作。
var obj = {
name: 'Person',
age: 18
}
var teacher = {
type: 'teacher',
gender: 'man'
}
Object.setPrototypeOf(obj, teacher)
console.log(obj);
console.log(Object.getPrototypeOf(obj));
如果要创建一个新对象,同时继承另一个对象的[[Prototype]],推荐使用 Object.create().
var teacher = {
type: 'teacher',
gender: 'man'
}
var s = Object.create(teacher)
console.log(s);
优化 new 的实现
正如上面介绍的不建议使用 proto 修改原型,所以我们使用 Object.create() 来实现
我们只需要把之前 链接原型 和创建空对象 放在一起进行 使对象直接继承我们构造函数的原型。
如下:
function myNEW() {
var handler = Array.prototype.shift.call(arguments);
var obj = Object.create(handler.prototype);
var ret = handler.apply(obj, arguments)
return ret instanceof Object ? ret : obj
}
# 原型链
每个对象都拥有一个原型对象,通过 proto 指向上一个原型,并从中继承方法和属性, 同时原型对象也可能拥有原型,这样一层一层,最终指向 null。这种关系被称为原型链(prototype chain), 通过原型链,一个对象会拥有定义在其他对象中的属性和方法。
function Person(name) {
this.name = name;
}
var p = new Person('zhang san');
console.log(p.constructor === Person); // true
这段代码打印出的结果是 true 是意味着 实例中存在 constructor 属性指向构造函数吗? 当然不是 我们之前打印除了 实例的结构 发现 实例 p 本身没有 constructor,是通过原型链向上查找 proto,最终找到 constructor 属性,该属性指向 Person。 于是 我们的原型链 如下:
function Person(name) {
this.name = name;
}
var p = new Person('zhang san');
console.log(p.__proto__ === Person.prototype);
console.log(p.__proto__.__proto__ === Object.prototype);
console.log(p.__proto__.__proto__.__proto__ === null);
__proto__属性指向的是上一个原型,也就是说 我们一直在取上一个原型,只要我们的取出来的不是null (最顶层的原型)
# 小结
- Symbol 作为构造函数来说并不完整,因为不支持语法 new Symbol(),但其原型上拥有 constructor 属性,即 Symbol.prototype.constructor。
- 引用类型 constructor 属性值是可以修改的, 但是对于基本类型来说是只读的, null 和 undefined 没有 constructor 属性。
- proto 是每个实例上都有的属性, prototype 是构造函数的属性,这两个并不一样,但 p.proto 和 Person.prototype 指向同一个对象。
- proto 属性在 ES6 时被标准化,但因为性能问题并不推荐使用,推荐使用Object.getPrototypeOf()
- 每个对象拥有一个原型对象,通过 proto 指针指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向null,这就是原型链。
注意:对象是通过 proto 指向上一个原型,并继承方法和属性 用到的是继承而不是复制
function Person(name) {
this.name = name;
}
Person.prototype.length = 101;
var p = new Person('zhang san');
console.log(p.__proto__);
我们会发现 结果中存在 length 属性。
原型上的属性和方法定义在 prototype 对象上,而非对象实例本身/当访问一个对象的属性/方法时,它不仅仅在该对象上查找,还会查找该对象的原型,以及该对象的原型的原型,一层一层往上找, 直到找到一个名字匹配的属性/方法或者到达原型的最顶层 null 。
思考如果调用 person.valueOf() 会发生什么?
- 首先会检查 person 对象上是否具有可用的 valueOf 方法。
- 如果没有,则会向上查找 person 对象的原型对象 (person.proto 即 Person.prototype) 是否具有可用的 valueOf 方法。
- 如果没有,则继续向上查找 person 的原型对象的原型对象 (person.proto.proto 即 Object.prototype) 看是否存在可用的 valueOf 方法。此时找到了 valueOf 方法执行
function Person(name) {
this.name = name;
}
var p = new Person('zhang san');
console.log(p.valueOf(),p);
# prototype 和 proto
区别: 原型对象 prototype 是构造函数上的属性, proto 是每个实例上都有的属性,两个属性不同但是指向的是同一个对象。
那么原型链的构建是依赖于 prototype 还是 proto 呢? 如图 (opens new window) 如上图所示,我们会发现在原型链中 Foo.prototype 中的 prototype 并没有狗哼一条原型链,它只是指向了原型链中的某一处位置。 原型链的构建依赖于 proto, 如上图中 foo.proto 指向 Foo.prototype , foo.proto.proto 指向 Bichon.prototype 如此一层一层最终链接到 null 。
注意: 不要使用 Bar.prototype = Foo ,因为这不会执行 Foo 的原型,而是指向函数 Foo ,因此原型链将会链接到 Function.prototype 而不是 Foo.prototype ,因此 methods 方法不会再 Bar 的原型链上。
function Foo() {
return 'foo'
}
Foo.prototype.methods = function () {
return 'method'
}
function Bar() {
return 'bar'
}
Bar.prototype = Foo
let bar = new Bar()
console.log(bar);
# instanceOf 的原理
instanceOf 运算符用来检测 constructor.prototype 是否在参数的原型链上
function Person() {
}
function People() {
}
var p = new Person()
console.log(p instanceof Person); // true Object.getPrototypeOf(p) === Person.prototype
console.log(p instanceof People); // false
console.log(Object.getPrototypeOf(p) === Person.prototype);
instanceOf原理就是:一层一层查找 proto,如果和 constructor.prototype 相等 则返回 true ,如果一直没有查找成功则返回 false
instance.[__proto__...] === instance.constructor.prototype
那么我们知道了原理就来浅试一下 实现 instanceOf:
function myInstanceOf(A, B) {
var target = B.prototype;
while (A.__proto__) {
if (A.__proto__ === target) {
return true;
}
A = A.__proto__;
}
return false;
}
function Person() {
}
function People() {
}
var p = new Person();
console.log(p instanceof Person); // true Object.getPrototypeOf(p) === Person.prototype
console.log(p instanceof People); // false
console.log(Object.getPrototypeOf(p) === Person.prototype);
console.log(myInstanceOf(p, Person)); // true
console.log(myInstanceOf(p, People)); // false
# 原型链继承
原型链继承的本质就是重写原型对象,用一个新的要继承的实例来替代。 新的原型 Solder 不仅有 new Person() 实例上的全部属性和方法,并且由于指向了 Person 原型,所以还拥有 Person 原型上的所有方法和属性。
function Person() {
this.name = 'zhang san';
}
Person.prototype.run = function () {
console.log(this.name + ' is running!');
}
function Solder() {
}
Solder.prototype = new Person()
var s = new Solder()
console.log(s);
s.name = 'li si'
s.run()
原型链继承方案有以下缺点:
- 多个实例对引用类型的操作会被篡改
- 子类型的原型上的 constructor 属性被重写了
- 给子类型原型添加属性和方法必须在替换原型之后
- 创建子类型实例时无法向父类型的构造函数传参
** 一**
我们先来看第一个
在原型链继承方案中,我们的原型会变成另一个类型的实例,如下数代码, Solder.prototype 变成了 Person 的一个实例,所以 Person的实例属性 hobbies 就变成了 Solder 的属性。
在原型属性上的引用类型数据会被所有的实例共享,每个实例都能对其进行修改,所以多个实例对引用类型的操作会篡改原数据,如下 我们修改了 s1 实例中的实例属性后,影响到了 s2 中实例属性的值。
function Person() {
this.hobbies = ['football', 'basketball', 'swimming'];
}
Person.prototype.run = function () {
console.log(this.name + ' is running!');
}
function Solder() {
}
Solder.prototype = new Person()
var s1 = new Solder()
var s2 = new Solder()
console.log(s1.hobbies, s2.hobbies);
s1.hobbies.shift()
console.log(s1.hobbies, s2.hobbies);
** 二**
首先看下面代码
function Person() {
this.hobbies = ['football', 'basketball', 'swimming'];
}
Person.prototype.run = function () {
console.log(this.name + ' is running!');
}
function Solder() {
}
Solder.prototype = new Person()
var p = new Solder()
console.log(p.constructor);
/
*ƒ Person() {
this.hobbies = ['football', 'basketball', 'swimming'];
}
*/
观察打印结果 我们发现通过 new Solder() 出的实例的构造函数,不是 Solder 而是 Person 因为这个 Solder 的 原型对象指向的是 Person 的实例 这个实例的构造函数 constructor 是 Person 解决方法就是 重写 我们这个 Solder 的 constructor 让他指向 Solder
function Person() {
this.hobbies = ['football', 'basketball', 'swimming'];
}
Person.prototype.run = function () {
console.log(this.name + ' is running!');
}
function Solder() {
}
Solder.prototype = new Person()
Solder.prototype.constructor = Solder; // 新增代码 将 Solder 原型对象的 constructor 指向自己的构造函数 Solder
var p = new Solder()
console.log(p.constructor);
/*
ƒ Solder() {
}
*/
** 三**
给子类型原型添加属性和方法要在替换原型之后,因为我们会替换子类型的原型 替换之后指向了 Person 的 实例。
function Person() {
this.hobbies = ['football', 'basketball', 'swimming'];
}
Person.prototype.say = function () {
return this.hobbies.join(' ') // 修改方法
}
function Solder() {
}
Solder.prototype = new Person()
Solder.prototype.constructor = Solder;
Solder.prototype.say = function () {
return this.hobbies.join(','); // 新增方法
}
var p = new Solder()
console.log(p.say());
我们会发现,现在实例能访问两个 say 方法 ,但是 实际上运行的是我们新增在 Solder 原型对象上的 say 方法 而 Person.prototype 上也有一个 say 方法,但是它不会访问到。这种情况称为 属性遮蔽。 如果我们要访问 Person 上的 say 方法要如何操作? 通过 proto // console.log(p.proto.proto.say);
# 其他继承方案
由于 ES6 封装了 class 日常工作中我们使用 ES6 Class Extends (模拟原型)继承方案即可,更多可阅读 木易杨大神的 Javascript 8种继承方案 (opens new window)
# 练习:
有三个判断数组的方法,请分别介绍他们之间的区别和优劣
- Object.prototype.toString.call()
- instanceOf
- Array.isArray()
# 小结
- 每个对象拥有一个原型对象,通过 proto 指向上一个原型,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null,这种关系被称为 原型链
- 当访问一个对象的属性或方法时,它不止在对象本身查找,还会查找该对象的原型,如果对象的原型也有原型,还会再该对象的原型的原型,一层一层,向上查找,直到找到名称完全匹配的属性或方法,或到达原型链的顶层 null 。
- 原型链的构建依赖于 proto,一层一层最终链接到null。
- instanceOf 的原理就是一层一层查找 proto,如果和 constructor.prototype 相等则返回 true ,如果一直没有查找成功,则返回 false
- 原型链继承的本质就是重写原型对象,用一个新类型的实例来代替。
# 深入探究 Function & Object 鸡蛋问题
上一部分在介绍原型链继承的过程中,给大家描述了原型链运作机制以及属性遮蔽等知识,这一节我们就来探究下 Function.proto === Function.prototype 引起的鸡生蛋,蛋生鸡问题,并在这个过程中深入了解 Object.prototype、Function.prototype、function Object、function Function 之间的关系。
我们可以先来看看 ECMAScript 上的定义。
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.
Object原型对象的【[Prototype】]内部属性的值为null,[[Class]]内部属性的值为"Object",[[可扩展]]内部属性的初始值为true。
Object.prototype 表示 Object 的原型对象,其 [[Prototype]] 属性值是 null ,访问器属性 proto 暴露了一个对象的内部 [[Prototype]]。 Object.prototype 并不是通过 Object 函数创建的,为什么呢? 看如下代码
function Person() {
this.val = 'person'
}
var person1 = new Person();
person1.__proto__ = Person.prototype;
实例对象的 __proto__ 指向 构造函数的 prototype 即 person1.__proto___ = Person.prototype,但是 Object.prototype.__proto__ = null 所以 Object.prototype 并不是通过 Object 函数创建的,那他是如何生成的? 其实 Object.prototype 是浏览器底层根据 ECMAScript 规范创造的一个对象。
Object.prototype 就是原型链的顶端 (不考虑 null),所有对象纪恒了它的 toString 等方法和属性。
# Function.prototype
首先来看下 ECMAScript 上的定义:
The Function prototype object is itself a Function object (its [[Class]] is "Function").
The value of the [[Prototype]] internal property of the Function prototype object is the standard built-in Object prototype object.
The Function prototype object does not have a valueOf property of its own; however, it inherits the valueOf property from the Object prototype Object.
Function.prototype 对象是一个函数(对象),其[[Prototype]] 内部属性值指向内建对象 Object.prototype. Function.prototype 对象自身没有 valueOf 属性,其从 Object.prototype 对象继承了 valueOf 属性。
Function.prototype 的 [[Class]] 属性是 Function,所以这是一个函数,但又不大一样。为什么这么说呢?因为我们直到只有函数才有 prototype 属性,但并不是所有函数都有这个属性,因为 Function.prototype 这个函数就没有。
console.log(Function.prototype) // f(){[native code]}
console.log(Function.prototype.prototype); // undefined
为什么 会没有 prototype 呢? Function.prototype 是引擎创建出来的函数,引擎认为不需要给这个函数对象添加 prototype 属性,不然 Function.prototype.prototype... 将无休无止并且没有意义。
# function Object
先来看 ECMAScript 的定义
The value of the [[Prototype]] internal property of the Object constructor is the standard built-in Function prototype object.
Object 作为构造函数时,其 [[Prototype]] 内部属性值指向 Function.prototype, 即
Object.__proto__ = Function.prototype
使用 new Object() 创建新对象时,这个新对象的 [[Prototype]] 内部属性指向构造函数的 prototype 属性,即 Object.prototype 属性,对应 Object.prototype
也可以通过对象字面量等方式创建对象。
- 使用对象字面量创建的对象,其 [[Prototype]] 值是 Object.prototype。
- 使用数组字面量创建的对象,其 [[Prototype]] 值是 Array.prototype。
- 使用 function f(){} 函数创建的对象, 其 [[Prototype]] 值是 Function.prototype。
- 使用 new fun() 创建的对象,其中 fun 是由 Javascript 提供的内建构造器函数之一 (Object, Function, Array, Boolean, Date, Number, String 等等),其 [[Prototype]] 值是 fun.prototype
- 使用其他 Javascript 构造器函数创建的对象,其 [[Prototype]] 值就是该构造器函数的 prototype 属性。
// 1 -- 对象字面量方式
var obj = { name: 'zhang san' }
console.log(obj.__proto__ === Object.prototype); // true obj -- > Object.prototype --> null
// 2 -- 数组字面量方式
var arr = ['zhangsan', 'lisi', 'wangwu'];
console.log(arr.__proto__ === Array.prototype); // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true arr --> Array.prototype --> Object.prototype --> null
// 3 -- function f(){} 方式
function f() {
return 's'
}
console.log(f.__proto__ === Function.prototype); // true
console.log(f.__proto__.__proto__ === Object.prototype); // true f --> Function.prototype --> Object.prototype --> null
// 4 -- new Function() 方式
var fun = new Function()
console.log(fun.__proto__ === Function.prototype); // true
console.log(fun.__proto__.__proto__ === Object.prototype); // true fun --> Function.prototype --> Object.prototype --> null
var ob = new Object() // ob --> Object.prototype --> null
// 5 -- 其他构造器函数
function Person() {
return {}
}
var p = new Person()
console.log(p.__proto__ === Person.prototype); // false
console.log(p.__proto__ === Object.prototype); // true p --> Object.prototype --> null
# function Function
定义
The Function constructor is itself a Function object and its [[Class]] is "Function". The value of the [[Prototype]] internal property of the Function constructor is the standard built-in Function prototype object.
Fcuntion 构造函数是一个函数对象,其 [[Class]] 属性是 Function。Function 的 [[Prototype]] 属性指向了 Function.prototype ,即
Function.__proto__ === Function.prototype
哦吼? 是不是有点意思了。
# Function & Object 鸡蛋问题
ok 我们先看下面的代码
Object instanceof Function // true
Function instanceof Object // true
Object instanceof Object // true
Function instanceof Function // true
Object 构造函数继承了 Function.prototype,同时 Function 构造函数继承了 Object.prototype。这里就产生了鸡和蛋的问题。 为什么会出现这种问题,因为 Function.prototype 和 Function.proto 都指向 Function.prototype。
console.log(Object.__proto__ === Function.prototype); // true
console.log(Object.__proto__.__proto__ === Object.prototype); // true
console.log(Function.__proto__.__proto__ === Object.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
而对于 Function.proto === Function.prototype 这一现象有两种解释,争论点就在于 Function 对象是不是由 Function 构造函数创建的一个实例?
解释1、YES: 根据 Javascript 中对实例的定义, a 是 b 的实例,即 a instanceof b 为 true,默认判断条件就是 b.prototype 在 a 的原型链上。而 Function instanceof Function 为 true,本质上即 Object.getPrototypeOf(Function) === Function.prototype, 符合定义
解释2、NO:
Function 是 built-in 的对象(内置对象),也就是说并不存在 Function 对象由 Function 构造函数创建,这样显然会造成鸡蛋问题。实际上,当你直接写一个函数是(如 function f(){} 或 x => x),也不存在调用 Function 构造器,只有在显示调用 Function 构造器时 (如 new Function('x','return x')) 才有。
个人比较偏向第二种吧,现有 Function.prototype 然后有的 function Function (){}, 所以就不存在鸡蛋问题,把 Function.proto 指向 Function.prototype 是为了保证原型链的完整, 让 Function 可以获取定义在 Object.prototype 上的方法。
# 内置类型构建过程
Javascript 内置类型是浏览器内核自带的,浏览器底层对 Javascript 的实现基于 C/C++,那么浏览器在初始化 Javascript 环境时发生了什么?
- 用 C/C++ 构建内部数据结构创建一个 OP (Object.prototype)以及初始化其内部属性但不包括行为。
- 用 C/C++ 构建内部数据结构创建一个 FP (Function.prototype) 以及初始化其内部属性但不包括行为。
- 将 FP 的 [[Prototype]] 指向 OP。
- 用 C/C++ 构造内部数据结构创建各种内置引用类型。
- 将各个内置引用类型的 [[Prototype]] 指向 FP。
- 将 Function 的 prototype 指向 FP
- 将 Object 的 prototype 指向 OP
- 用 Function 实例化出 OP,FP,以及 Object 的行为并挂载。
- 实例化内置对象 Math 和 Global 至此,所有内置类型构建完成。