原型链,原型对象,这个原型到底如何理解?我们通过几个问题慢慢展开
【问题1】声明一个函数Person,函数调用前面加个new,它做了什么呢?
function Person(){}
const zs = new Person()
- 创建一个‘空对象’
- 这个对象会被关联到Person.prototype指向的对象上(关联对象)
- 函数的this被绑定到新对象上
- 返回这个新对象
-
我这里空对象打了引号,是因为它不是纯的空对象,它有关联对象,意思是当我用zs这个对象访问某属性,调用某方法时,它在自身上找不到,会委托关联对象帮它找,关联对象找不到,那么关联对象又会委托它的关联对象去寻找,直到找到最顶层的Object.prototype,如果还找不到,返回undefined,这就是原型链
-
让我们来验证一下,Object.getPrototypeOf() 方法,就是获取zs的关联对象,我喜欢用关联对象代替原型对象,这更加直观,便于我理解。
console.log(Object.getPrototypeOf(zs) === Person.prototype); // true
很多人看原型链的时候,都是使用了下划线proto来验证
console.log(zs.__proto__ === Person.prototype); // true
【问题2】函数的prototype属性从何而来
function Person(){}
console.log(Object.getOwnPropertyNames(Person)); // ['length', 'name', 'arguments', 'caller', 'prototype']
console.log(Object.getOwnPropertyNames(Person.prototype)); // ['constructor']
可以看到当我声明一个函数时,js自动为这个函数添加了5个属性,函数的prototype属性所指向的对象上又有一个consructor属性
【问题3】对象的下划线proto属性从何而来
console.log(Object.getOwnPropertyNames(zs)); // []
可以看见新创建的对象zs上压根就没有我们期望看到的下划线proto属性,我们看下它的源码
Object.defineProperty(Object.prototype, '__proto__', {
get() {
let _thisObj = Object(this);
return Object.getPrototypeOf(_thisObj);
},
set(proto) {
if (this === undefined || this === null) {
throw new TypeError();
}
if (!isObject(this)) {
return undefined;
}
if (!isObject(proto)) {
return undefined;
}
let status = Reflect.setPrototypeOf(this, proto);
if (!status) {
throw new TypeError();
}
},
});
所以这个属性其实是Object.prototype的,而且我们通过zs访问此属性时就是通过原型链找到的,调用的是getter方法,其实还是用了Object.getPrototypeOf方法
【问题4】constructor属性?
console.log(zs.constructor === Person.prototype.constructor); // true
以上代码又是我们看原型链时,人家经常会讲到
对象实例的构造函数指向函数原型对象的构造函数啊
刚刚我们已经打印过了,zs其实压根没有constructor 属性,而它的关联对象Person.prototype上有,所以委托关联对象帮它访问到constructor属性,自然这个是相等的。所以大家不要死记硬背,zs上面没有下划线proto和constructor属性!
console.log(Person === Person.prototype.constructor); // true
至于上方代码,这也是js自己干的:函数的prototype属性所指向的对象上面的constructor属性指向函数本身
【问题5】继承?
- 其实js没有继承这种东西,只是我们喜欢用这种说法帮助我们理解,而且ES6的class语法糖直接用上了extends这个关键字,让我们更加以为有继承这个概念存在。其实js只是帮我们建立了对象之间的关联关系而已!
- 下面我们用es5的方式写上经典的函数继承
function Foo(name) {
this.name = name;
}
Foo.prototype.myName = function() {
return this.name;
};
function Bar(name, label) {
Foo.call( this, name );
this.label = label;
}
// 创建一个新的对象,并设置关联对象为 Foo.prototype,返回这个新对象给Bar.prototype
Bar.prototype = Object.create( Foo.prototype );
// 注意!现在没有 Bar.prototype.constructor 了
// 如果你需要这个属性的话可能需要手动修复一下它
Bar.prototype.myLabel = function() {
return this.label;
};
var a = new Bar( "a", "obj a" );
a.myName(); // "a"
a.myLabel(); // "obj a"
console.log(Object.getPrototypeOf(a) === Bar.prototype); // true
console.log(Object.getPrototypeOf(Bar.prototype) === Foo.prototype); // true
console.log(Object.getPrototypeOf(Foo.prototype) === Object.prototype); // true
这里用到了Object.create ()方法,此方法的含义是创建一个新对象,并给这个新对象设置关联对象,当然我们也可以用另外一种方法
Object.setPrototypeOf(Bar.prototype, Foo.prototype)
这是修改Bar.prototype关联对象为Foo.prototype,它没有一个创建新对象的过程,可以节省点内存。
- 就上方代码,我们找一下原型链
- a的关联对象为Bar.prototype所指向的对象
- Bar.prototype所指向的对象的关联对象为Foo.prototype所指向的对象
- Foo.prototype所指向的对象的关联对象为Object.prototype所指向的对象
- 当我们访问a.toString(),它就会顺着原型链一直找到Object.prototype所指向的对象上去
console.log(Object.getOwnPropertyNames(Object.prototype));
// ['constructor', '__defineGetter__', '__defineSetter__', 'hasOwnProperty', '__lookupGetter__', '__lookupSetter__', 'isPrototypeOf', 'propertyIsEnumerable', 'toString', 'valueOf', '__proto__', 'toLocaleString']
所以不会报错undefined,经过一层层的委托,Object.prototype返回了这个方法的引用给它,现在明白了原型链了吧~
console.log(a.toString === Object.prototype.toString); // true
【问题6】如何创建一个真正的空对象
let o = Object.create(null)
console.log(Object.getPrototypeOf(o)); // null
网友评论