「这是我参与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等仅对对象本身起作用。
参考资料:
🎨【点赞】【关注】不迷路,更多前端干货等你解锁
往期推荐