原图地址: Javascript Object Hierarchy (mollypages.org)
prototype
每个函数都有一个prototype
当你创建函数时,JS会为这个函数自动添加prototype属性,值是一个有 constructor 属性(指向这个函数本身)的对象,不是空对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的__proto__指向承构造函数的prototype来实现这种继承)
function Person() {}
console.log(Person.prototype); // {constructor: ƒ...}
console.log(Person.prototype.constructor === Person); // true
对象__proto__
每个对象都有一个__proto__
function Person() {}
Person.prototype.name = 'zxx';
Person.prototype.sayName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // zxx
每个对象都有一个隐藏的属性__proto__,这个属性指向创建它的构造函数的原型对象。即:
person1.__proto__ === Person.prototype
__proto__是一个隐藏的属性,且不是一个规范属性,该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它。__proto__属性已在ECMAScript 6语言规范中标准化,用于确保Web浏览器的兼容性,因此它未来将被支持。它已被不推荐使用,现在更推荐使用Object.getPrototypeOf/Reflect.getPrototypeOf和Object.setPrototypeOf/Reflect.setPrototypeOf。
var obj = {};
obj.__proto__ === Object.getPrototypeOf(obj); // true
看obj.__proto__里面有什么:
表面上看,上图的对象里存在一个__proto__ 属性。实际上,它只是开发者工具为了方便让开发者查看原型,故意渲染出来的虚拟节点。虽然跟对象的其它属性并列,但并不在该对象中。
__proto__属性既不能被 for in 遍历出来,也不能被 Object.keys(obj) 查找出来。
访问对象的 obj.__proto__属性,默认走的是 Object.prototype 对象上__proto__属性的 get/set 方法。
Object.defineProperty(Object.prototype, '__proto__', {
get() {
console.log('get __proto__');
}
});
var obj = {};
console.log(obj.__proto__); // get __proto__
通过覆盖 Object.prototype.__proto__我们可以看到,访问普通对象的__proto__触发了 Object.prototype 上的 __proto__的 get 方法。
因此,普通对象创建时,只需要将它内部的隐式引用指向 Object.prototype 对象,就能兼容__proto__ 属性访问行为,不需要将原型隐式挂载到对象的__proto__属性。
总结: 每个对象都有一个__proto__属性,指向创建该对象的函数的prototype,但Object.prototype确实一个特例——它的__proto__指向的是null
Object.prototype.__proto__ === null; // true
原型链
function Person() {}
Person.prototype.name = 'zxx';
Person.prototype.sayName = function() {
console.log(this.name);
}
var person1 = new Person();
person1.sayName(); // zxx
以上代码中,person1是Person函数new出来的对象,person1.sayName方法是怎么来的呢?——从Person.prototype得来,因为person1.__proto__指向的是Person.prototype
访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。
操作符instanceof正是通过探测obj的__proto__的__proto__ … === Constructor.prototype来验证obj是否是Constructor的实例
以下代码是模拟instanceof
function instanceofFn(left, right) {
if (left === null || typeof left !== "object") {
return false;
}
// 获得右边对象的prototype
var prototype = right.prototype;
// 获得左边对象的原型(内部[[Prototype]]属性的值)
var proto = Object.getPrototypeOf(right);
while (true) {
if (proto === null) {
return false;
}
if (proto === prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
}
Object.prototype是原型链的顶端,所有对象从它继承了包括toString等方法和属性。
Object本身是构造函数,继承了Function.prototype
Function也是对象,继承了Object.prototype。这里就有一个鸡和蛋的问题:
Object instanceof Function // true
Function instanceof Object // true
看ES规范是怎么说的?
Function本身就是函数,Function.__proto__是标准的内置对象Function.prototype。
Function.prototype.__proto__是标准的内置对象Object.prototype。
// 原型链到Object.prototype终止
console.log(Object.prototype.__proto__ === null); // true
// Function.prototype直接继承Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype); // true
// Object/Array/String等等构造函数本质上和Function一样,均继承于Function.prototype。
console.log(Function.prototype === Function.__proto__); // true
console.log(Function.prototype === Array.__proto__); // true
console.log(Function.prototype === Object.__proto__); // true
总结: 先有Object.prototype(原型链顶端),Function.prototype继承Object.prototype而产生,最后,Function和Object和其它构造函数继承Function.prototype而产生
参考资料:
从__proto__和prototype来深入理解JS对象和原型链