这是我参与8月更文挑战的第16天,活动详情查看: 8月更文挑战
序言
很多新学JS的小伙伴,原型都是绕不过的重要知识点,但是相信很多人和我当时初学时有一样的疑惑,这个东西有什么特殊的意义吗,我们为什么要学这个,这个东西带来了什么,学完了总是一知半解,今天我们就带着这些问题来寻找答案。
Prototype
在 JavaScript 中,对象有一个特殊的隐藏属性 [Prototype](,它要么为 null,要么就是对另一个对象的引用。该对象被称为“原型”。 Prototype
存在的意义在于,当我们有一个 user 对象及其属性和方法,并希望将 admin 和 guest 作为基于 user 稍加修改的变体。我们想重用 user 中的内容,而不是复制/重新实现它的方法,而只是在其之上构建一个新的对象。那么我们可以利用JS当从对象中读取一个缺失属性时,JS会自动从原型中查找该属性,这种行为被称为原型继承。
案例
let people = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = people; // 设置 rabbit.[[Prototype]] = people
现在,当我们从 rabbit 中读取一个他没有的属性,JS 会自动从 people 中获取
例如
let people = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = people; //
// 现在这两个属性我们都能在 rabbit 中找到:
alert( rabbit.eats ); // true
alert( rabbit.jumps ); // true
在这儿我们可以说 "animal 是 rabbit 的原型",或者说 "rabbit 的原型是从 animal 继承而来的"。因此,如果 animal 有许多有用的属性和方法,那么它们将自动地变为在 rabbit 中可用。这种属性被称为“继承”。
如果我们在people中有一个方法,他可以在rabbit中被调用
let people = {
eats: true,
walk() {
alert("people walk");
}
};
let rabbit = {
jumps: true,
__proto__: people
};
// walk 方法是从原型中获得的
rabbit.walk(); // people walk
原型链可以多对象之间继承
let people = {
eats: true,
walk() {
alert("people walk");
}
};
let rabbit = {
jumps: true,
__proto__: people
};
let longEar = {
earLength: 10,
__proto__: rabbit
};
// walk 是通过原型链获得的
longEar.walk(); // people walk
alert(longEar.jumps); // true(从 rabbit)
我们看上例,longEar 继承了 rabbit ,rabbit 继承了 people,所以longEar同时继承了rabbit 和 people,当longEar上没有的属性被取用时,他会自下向上查找。会先在 rabbit 中查找,然后在 animal 中查找。
关于原型链有两个限制
- 引用不能形成闭环。如果我们试图在一个闭环中分配 proto,JavaScript 会抛出错误。
- proto 的值可以是对象,也可以是 null。而其他的类型都会被忽略。
需要强调的是:只能有一个 [[Prototype]]。一个对象不能从其他两个对象获得继承。 这里需要提示: proto 是 [[Prototype]] 的因历史原因而留下来的 getter/setter 很多人搞不清proto 和 [[Prototype]] 的区别。 proto 是 [[Prototype]] 的 getter/setter。所以修改proto,[[Prototype]] 会做出相应的变化,后来使用了Object.getPrototypeOf/Object.setPrototypeOf 来取代 proto 做 get/set 原型。
for...in循环
for..in 循环也会迭代继承的属性。
let people = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: people
};
// 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 people = {
eats: true
};
let rabbit = {
jumps: true,
__proto__: people
};
for(let prop in rabbit) {
let isOwn = rabbit.hasOwnProperty(prop);
if (isOwn) {
alert(`Our: ${prop}`); // Our: jumps
} else {
alert(`Inherited: ${prop}`); // Inherited: eats
}
}
这样,我们可以区分,对象本身的属性和继承的属性。