「这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战」
你有没有遇到过这种需求,已经有一个 user
对象及其属性和方法,现在希望将 admin
和 guest
作为基于 user
稍加修改的变体。想重用 user
中的内容,而不是复制或者重新实现它的方法,只是在其之上构建一个新的对象。
原型继承(Prototypal inheritance)就可以用来实现这个需求。
[[Prototype]]
在JavaScript中,对象有一个特殊的隐藏属性 [[Prototype]]
,它要么为 null
,要么就是对另一个对象的引用。该对象被称为“原型”:
当从 object
中读取一个缺失的属性时,JavaScript 会自动从原型中获取该属性。在编程中,这种行为被称为“原型继承”。
怎么设置?
属性 [[Prototype]]
应该怎么设置呢?
其中之一就是使用特殊的名字 __proto__
示例:
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
可以看到,上面示例中对象animal
有eats
,rabbit
有jumps
,这个时候,如果在rabbit
获取eats
的值即console.log(rabbit.eats)
,结果是什么?显而易见,这是不存在的。
但是,如果你通过__proto__
设置了 [[Prototype]]
rabbit.__proto__ = animal;
从执行结果可以看到,即使rabbit
自身没有eats
属性,它也获取到了值。
过程分析:当我们试图读取 rabbit.eats
时,因为它不存在于 rabbit
中,所以 JavaScript 会顺着 [[Prototype]]
引用,在 animal
中查找(自下而上)
这个时候我们可以说 "animal
是 rabbit
的原型",或者说 "rabbit
的原型是从 animal
继承而来的"。
因此,如果 animal
有许多有用的属性和方法,那么它们将自动地变为在 rabbit
中可用。这种属性被称为“继承”。
如果我们在 animal
中有一个方法,它在 rabbit
中也可以被调用
原型链可以很长
let animal = {
eats: true,
walk() {
alert("Animal walk");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
let longEar = {
earLength: 10,
__proto__: rabbit
};
// walk 是通过原型链获得的
longEar.walk();
在这个原型链中,如果我们从 longEar
中读取一些它不存在的内容,JavaScript 会先在 rabbit
中查找,然后在 animal
中查找。
注意:
- 只能有一个
[[Prototype]]
。一个对象不能从其他两个对象获得继承。- 注意
__proto__
和[[Prototype]]
的区别,__proto__
是[[Prototype]]
的 getter/setter。__proto__
属性有点过时了。它的存在是出于历史的原因,现代编程语言建议我们应该使用函数Object.getPrototypeOf/Object.setPrototypeOf
来取代__proto__
去 get/set 原型。
关于属性的枚举
想要获取属性的key,有Object.keys(obj)
和for..in
两种方式,但是他们是有些不同的
从执行结果,我们可以看到
- Object.keys 只返回自己的 key
- for..in 会遍历自己以及继承的键
如果你想要for...in
只返回自己的key也是可以的,因为有这样一个内建方法 obj.hasOwnProperty(key)
:如果 obj
具有自己的(非继承的)名为 key
的属性,则返回 true
。
代码部分改写:
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
if (isOwn) {
console.log(`Our: ${prop}`); // Our: jumps
}
}
思考一:
在上面我们用的了rabbit.hasOwnProperty
,那么它是怎么来的?
从图中的原型链可以看到,该方法是 Object.prototype.hasOwnProperty
提供的。换句话说,它是从``Object`继承的。
思考二:
for..in
循环会列出继承的属性,为什么 hasOwnProperty
没有像 eats
和 jumps
那样出现在 for..in
循环中?
因为它是不可枚举的。就像 Object.prototype
的其他属性,hasOwnProperty
有 enumerable:false
标志。并且 for..in
只会列出可枚举的属性。这就是为什么它和其余的 Object.prototype
属性都未被列出。
总结
- 通过
[[Prototype]]
引用的对象被称为“原型” for..in
循环在其自身和继承的属性上进行迭代。所有其他的键/值获取方法,如Object.keys
和Object.values
等仅对对象本身起作用。
参考资料:
🎨【点赞】【关注】不迷路,更多前端干货等你解锁
往期推荐