Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
先带着问题思考一下:
- 原型是什么?
- 怎么操作原型?
protoType
[[Prototype]] 是对象的隐藏属性,要么是null,要么是对另一个对象的引用,该对象称为原型.
当我们在当前对象上找不到属性时就会在原型对象上查找,这在编程上称为原型继承.
虽然[[Prototype]]是隐藏的,但还是有方法可以设置它: __proto__就是其中一个.
let animal = { eats: true };
let rabbit = { jumps: true };
rabbit.__proto__ = animal; // 设置 rabbit.[[Prototype]] = animal
// 现在这两个属性我们都能在 rabbit 中找到:
*alert( rabbit.eats ); // true
alert( rabbit.jumps ); // true
当我们在rebbit上找不到的时候会向上查找(自下而上)
在这我们就可以说 animal 是 rabbit 的原型
因此,在animal中的属性和方法会自动可以被rabbit调用,这种属性称为继承
这里有两个限制
- 引用不能形成闭环,如果在闭环中引用__proto__会报错.
- __proto__引用只能是对象或者null.
我们经常分不清__proto__和[[Prototype]]的区别,其实__proto__就是[[prototype]]的getter和setter
__proto__可能有点过时,但是他在任何浏览器包括服务端的任何环境都支持,所以很安全.
我们也可以用Object.getPrototypeOf/Object.setProtoTypeOf来代替__proto__取get/set原型
写入不使用原型
原型只用于读取
在对对象写入或者删除时不会操作该对象的原型
let animal = {
eats: true,
walk() { /* rabbit 不会使用此方法 */ }
};
let rabbit = { __proto__: animal };
rabbit.walk = function() {
alert("Rabbit! Bounce-bounce!");
};
rabbit.walk(); // Rabbit! Bounce-bounce!
可以看到 rabbit.walk不会使用和改变animal的walk()
访问器(accessor)属性是一个例外,因为分配(assignment)操作是由 setter 函数处理的。因此,写入此类属性实际上就像调用函数一样。
let user = {
name: "John",
surname: "Smith",
set fullName(value) {
[this.name, this.surname] = value.split(" ");
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user,
isAdmin: true
};
alert(admin.fullName); // John Smith (*)
// setter triggers!
admin.fullName = "Alice Cooper"; // (**)
alert(admin.fullName); // Alice Cooper,admin 的内容被修改了
alert(user.fullName); // John Smith,user 的内容被保护了
在 (*) 行中,属性 admin.fullName 在原型 user 中有一个 getter,因此它会被调用。在 (**) 行中,属性在原型中有一个 setter,因此它会被调用。
this
在上面这个代码中有个很有意思的地方,在set fullName 的时候this指向的是谁呢?
属性name和surname被写在user还是amdin
答案揭晓:this不受原型的影响
无论在哪里找到方法:在一个对象还是在原型中。在一个方法调用中,this 始终是点符号 . 前面的对象。
这点很重要,因为我们可能会有一个带有很多方法和属性的大对象,并且还有从他继承的对象,当修改继承的对象时,不会改到大对象上的属性.所以方法是共享的,但是状态不是.
for...in
for...in循环也会迭代继承的属性
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
// Object.keys 只返回自己的 key
alert(Object.keys(rabbit)); // jumps
// for..in 会遍历自己以及继承的键
for(let prop in rabbit) alert(prop); // jumps,然后是 eats
如果这不是我们想要的,并且我们想排除继承的属性,那么这儿有一个内建方法 obj.hasOwnProperty(key):如果 obj 具有自己的(非继承的)名为key 的属性,则返回 true。
这样就可以过滤掉继承的属性,或者做出想要的操作.
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: animal
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
if (isOwn) {
alert(`Our: ${prop}`); // Our: jumps
} else {
alert(`Inherited: ${prop}`); // Inherited: eats
}
}
注意这里的rabbit.hasOwnProperty()是从哪里来的呢?
是从Object.prototype.hasOwnProperty()中继承而来的.
那为什么没有打印出来呢? 其实也很简单,for...in会循环可枚举的值,这个属性是不可枚举的.
就像 Object.prototype 的其他属性,hasOwnProperty 有 enumerable:false 标志。并且 for..in 只会列出可枚举的属性。这就是为什么它和其余的 Object.prototype 属性都未被列出。
总结
- 在所有对象中都有
[[Prototype]]的隐藏属性,要么是对象要么是null __proto__是[[prototype]]的get/set方法- 通过
[[prototype]]引用的对象就是原型 - 在访问一个对象的属性时如果没有就自动去他的原型上查找.
- 在直接写入属性时不会改变原型的属性,而是在对象上新增一个,访问器属性例外,相当于直接执行get()/set()
- this始终指向的.前面的对象,方法是共享的,属性不是,修改继承后对象的属性不会改变原对象的属性
- for...in只会列出可枚举的属性,会列出原型上的属性.