理解 prototype 和 __proto__
一个对象的真正原型是被对象内部的
[[Prototype]]属性所持有。ECMAScript 引入了标准对象原型访问器Object.getPrototype(object),到目前为止只有 Firefox 和 Chrome 实现了此访问器。除了IE,其他的浏览器支持非标准的访问器__proto__
什么是 prototype 和 __proto__ ?
prototype 为构造器(构造函数)的原型,是一个 javascript 的原生对象,它是通过调用构造函数而创建的那个对象实例的原型对象。 其他的对象可以通过他实现属性继承。
__proto__ 是对象的内部原型,所有对象的__proto__都指向该对象的构造器的prototype。
在调用对象的方法时,会通过该对象__proto__在原型链中查找。而构造器的prototype 会用于创建通过new关键字创建的对象的__proto__。
- 所有构造器(函数)的
__proto__都指向Function.prototype1
2
3
4
5
6
7
8
9Number.__proto__ === Function.prototype; // true
Boolean.__proto__ === Function.prototype; // true
String.__proto__ === Function.prototype; // true
Object.__proto__ === Function.prototype; // true
Function.__proto__ === Function.prototype; // true
Array.__proto__ === Function.prototype; // true
RegExp.__proto__ === Function.prototype; // true
Error.__proto__ === Function.prototype; // true
Date.__proto__ === Function.prototype; // true
注意: JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math、JSON是以对象形式存在的,无需new。它们的__proto__是Object.prototype。如下:1
2Math.__proto__ === Object.prototype; // true
JSON.__proto__ === Object.prototype; // true
以上介绍都是原生的 javascript 的构造器(函数),当然自定义的构造器也满足:1
2
3
4function Person() {}
var Man = function() {};
Person.__proto__ === Function.prototype; // true
Man.__proto__ === Function.prototype; // true

- 所有对象的
__proto__都指向其构造函数的prototype1
2
3
4
5
6
7
8
9
10var obj = {name: 'aikin'};
var arr = [1,2,3];
var reg = /hello/g;
var date = new Date;
var err = new Error('exception');
obj.__proto__ === Object.prototype; // true
arr.__proto__ === Array.prototype; // true
reg.__proto__ === RegExp.prototype; // true
date.__proto__ === Date.prototype; // true
err.__proto__ === Error.prototype; // true
自定义构造函数的实例对象也是如此:1
2
3
4
5function Person(name) {
this.name = name
}
var person = new Person('aikin');
person.__proto__ === Person.prototype; // true

new 关键字做了什么?
new 运算符接受一个函数 F 及其参数:
new F(arguments...)。这一过程分为三步:
- 创建类的实例。这步是把一个空的对象的
__proto__属性设置为F.prototype。- 初始化实例。函数 F 被传入参数并调用,关键字
this指向该实例。- 返回实例 当然你也可以
return自定义的对象。
- new 函数的伪实现:
1
2
3
4
5
6
7function new (f) {
var ins = { '__proto__': f.prototype }; /*第一步*/
return function() {
f.apply(ins, arguments); /*第二步*/
return ins; /*第三步*/
};
}
干货呈上(来几道 quiz 吧)
- quiz-1
1 | function Person() { |
- quiz-2
1 | function Person() { |
quiz-1和quiz-2运行结果分别是:ulaijn和aikin。这两个测试题主要考察的是对new关键字的理解。- 在
quiz-1中,在调用new Person()时,Person构造函数内的this指向是Person的实例,由于Person构造函数返回了一个对象类型的{ name: 'ulaijn' },导致new Person()返回的不是Person构造函数的实例,而是{name: ulaijn},因为只要当构造函数自定义return的值是对象类型(不为null)时,这样将导致使用new关键字调用构造函数后的返回值,替换掉本该返回的构造函数的实例,所以这里的me为{name: 'ulaijn'}。 - 在
quiz-2中,Person构造函数内的this,在调用new Person()时和quiz-1一样都是指向Person的实例,但是由于Person构造函数返回了一个不是对象类型,而是字符串类型的ulaijn,导致new Person()生成的实例无法被替换,所以me.name为aikin。
- quiz-3
1 | function Person() {} |
- quiz-4
1 | function Person() {} |
- quiz-5
1 | function Person() { |
- quiz-6
1 | function Person() { |
- quiz-3、quiz-5 和 quiz-6 的运行结果是:
luna和tom,而 quiz-4 的运行结果是:tom和tom。这四道题主要考察的是对prototype理解。 - quiz-3 中
aMe.name = 'luna'是赋值的过程,会在aMe中创建name属性,不会修改aMe.__proto__.name,当然bMe也一样。 - 对于 quiz-4 来说,
aMe.info.name = 'luna'是在对aMe.info赋值的过程,所以要先查找出aMe.info对象,因为aMe对象自己没有info属性,所以会从aMe.__proto__获取到info对象,并修改掉info的name属性。而bMe.info.name = tom也是这个过程,由于aMe.__proto__ === bMe.__proto__,所以bMe.info.name = tom执行后会修改掉bMe.__proto__.info.name,从而导致aMe.info.name也被修改成tom。 - quiz-5 和 quiz-6 是考察对原型链查找的规则理解。当实例对象自己有相应的属性,就不会去获取原型链上的属性。就像 quiz-6 里面的
aMe实例对象,aMe对象拥有info属性,同时在aMe.__proto__也用拥有info属性,但是aMe.info.name,不会获取aMe.__proto__.info.name,因为在原型链获取相应对象或者函数时,会从对象(实例)本身开始沿着原型链向上查找,只要找到了,就好停止继续查找。
使用 console.dir(Function) 打印出你的原型链。