彻底弄清楚JavaScript的原型机制

本文主要阐述 JavaScript 的原型机制,剖析清楚 Function 和 Object 的之间的关系

JS 函数声明方式

方式1:使用 function 加函数名来声明

1
2
3
function Person() {
console.log('zzg');
}

方式2:使用 Function 构造函数声明

1
var person = new Function(undefined, 'console.log("zzg")');

JS 函数调用方式

普通函数调用

1
Person(); // zzg

使用构造函数调用

1
new Person(); // zzg

JavaScript 的原型

1
2
3
4
function Person(name) {
console.log('zzg');
}
const p1 = new Person('zzg')

每一个函数都有一个 prototype 属性,指向他的原型对象原型对象里面有一个 constructor 属性,指向函数本身。

1
2
3
console.log(Person.prototype) // // {constructor: ƒ}
console.log(p1.__proto__.constructor); // ƒ Person() {}
console.log(p1.__proto__.constructor === Person); // true

当使用一个函数当做构造函数来调用后,其实例对象有一个内部对象 __proto__ 指向其原型对象

1
2
3
4
// 输出 p1 对象的原型对象
// 输出 p1 对象的构造函数
console.log(p1.__proto__); // {constructor: ƒ}
console.log(p1.__proto__.constructor); // ƒ Person() {}

此时我们可以画出以下的原理图:
img

由于 p1 对象的原型对象 Person.prototype 也是对象,那么他谁构造的实例呢?他的构造函数又是谁?

1
2
3
4
// 输出 Person.prototype 的原型对象
// 输出 Person.prototype 的原型对象的构造函数
console.log(Person.prototype.__proto__); // {constructor: ƒ, __defineGetter__: ƒ …}
console.log(Person.prototype.__proto__.constructor); // ƒ Object() { [native code] }

从上面可以知道,函数的原型对象 Person.prototype 是由 Object 构造出来的

1
console.log(Person.prototype.__proto__ === Object.prototype); // true

那么 Object 的原型对象 Object.prototype 又是谁呢?

1
console.log(Object.prototype.__proto__); // null

于是我可以得到以下的关系:

1
2
3
console.log( p1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true

此时我们可以画出一下的原理图:
img
此时的链条就是原型链

Function 和 Object 的关系

上面的方式二中 Function 可以使用 new Function 调用,那说明 Function 也可以当做一个构造函数来使用,Object 也是如此。

1
2
console.log(Function); // ƒ Function() { [native code] }
console.log(Object); // ƒ Object() { [native code] }

可以发现他们两个是 JS 内置的原生的函数,既然是函数按照上面的规则是不是也得有原型对象呢?

1
2
3
4
// Function 的原型对象 Function.prototype 是一个函数
console.log(Function.prototype); // ƒ () { [native code] }
// Object 的原型对象 Object.prototype 是一个对象
console.log(Object.prototype); // {constructor: ƒ, __defineGetter__: ƒ …}

上面知道 Object 的原型对象Object.prototype,是对象,他的构造函数又是谁?

1
console.log(Object.prototype.constructor); // ƒ Object() { [native code] }

从上面可以知道 Object 的原型对象 Object.prototype 的构造函数是 ƒ Object() { [native code] },那么这个内置的 Object 构造函数又是谁构造的呢?

1
console.log(Object.prototype.constructor.constructor); // ƒ Function() { [native code] }

至此我们总结一下:

内置的 Function 函数是 Object 的构造函数,那么 Object 函数是 Function 函数的一个实例,我们判断一下

1
console.log(Object instanceof Function); // true

既然 ObjectFunction 的实例,那么他内部的 __proto__ 属性指向谁呢?

1
2
console.log(Object.__proto__); // ƒ () { [native code] }
console.log(Object.__proto__.constructor); // ƒ Function() { [native code] }

根据上面的规则,那么 Function 原型对象肯定和 Object 这个实例肯定也是有关联的

1
console.log(Function.prototype === Object.__proto__); // true

至此我们弄明白了 ObjectFunction 的关系,以及他们内部是如何关联的,但是还剩下一个点,Function 这个内置是由谁构造的呢?

1
2
3
4
5
6
7
8
// 谁是 Function 的构造器
// Function 自身构造了自己
console.log(Function.__proto__.constructor) // ƒ Function() { [native code] }
// 那么根据构造函数规则
// 原型对象的 constuctor 属性指向构造函数
consoel.log(Function.prototype.constuctor); // ƒ Function() { [native code] }
// 实例有一个属性__proto__指向构造自己的原型对象
console.log(Function.__proto__ === Function.prototype); // true

从上面的代码可以知道,Function 自己创造了自己,Function 的原型对象 Function.prototype 的构造函数指向自身

至此我们弄明白了 Function 是由谁创造的,还剩下 Function 的原型对象又是谁创造的呢?

1
2
3
4
5
6
// 发现他是一个内置的函数
console.log(Function.prototype); // ƒ () { [native code] }
// 既然是内置的函数,我们找一下他的构造函数
console.log(Function.prototype.__proto__); // {constructor: ƒ, __defineGetter__: ƒ …}
// 上面的结构发现与 Object.prototype 是一样的
console.log(Function.prototype.__proto__ === Object.prototype);

由此我们可以分析出来:Function.prototype 指向了 Object.prototype,所有我们可以在函数上使用toString 这些方法
所以基于 instaceof 的规则下面的判断是成立的,因为 Function__proto__ 指向了 Function.prototype,而 Function.prototype 又是指向Object.prototype,就像 p1__proto__ 属性指向 Person.prototype

1
console.log(Function instanceof Object); // true

此时我们可以画出以下的原理图:
img

Person 和 Function 的关系

其实从函数的声明 方式2 我们就猜测的到,Person 函数肯定是由 Function 创造的,我们不妨验证一下,Person 的 __proto__ 属性指向谁

1
console.log(Person.__proto__ === Function.prototype); // true

此时我们可以画出以下的原理图:
img

总结

上面的分析非常的饶人,最后总结一下:

  • 从构造函数的角度上来说,Object 继承了 FunctionObjectFunction 构造)
  • 从原型的角度上来说 Function 继承了 ObjectFunction 的原型对象指向了 Function 的原型对象)