jannahuang / blog

MIT License
0 stars 0 forks source link

原型、继承是什么 #14

Open jannahuang opened 2 years ago

jannahuang commented 2 years ago

原型继承

假设有一个对象包含一些属性和方法,而我们在构建另一个新的对象时,想直接复用那些属性和方法而不是复制或重新实现它们,我们可以用原型继承来实现这个需求。

[[ Prototype]]

在 JavaScript 中,对象有一个特殊的隐藏属性 [[ Prototype]],它要么为 null,要么就是对另一个对象的引用。该对象被称为“原型”。 当从一个对象中读取一个它没有的属性时,JavaScript 会向它的原型读取该属性,这被称为“原型继承”。 根据 MDN 文档可知,可以用 Object.setPrototypeOf() 方法设置一个指定的对象的原型 (内部 [[ Prototype]] 属性)为另一个对象或 null。可以用 Object.getPrototypeOf() 方法获取原型。但是更改对象的 [[ Prototype]] 在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。建议使用 Object.create() 来创建带有你想要的 [[ Prototype]] 的新对象。

// 语法 Object.create(proto, propertiesObject),其中 propertiesObject 可选
let animal = {
  eats: true
};
// 创建一个新对象 rabbit,将 animal 设置为 rabbit 的原型,rabbit 便继承了 animal 的属性。
let rabbit = Object.create(animal, {
  jumps: {
    value: true,
    writable: true, //非必填
    enumerable: true, //非必填
    configurable: true //非必填
  }
})

Object.getPrototypeOf(rabbit) === animal // true
Object.setPrototypeOf(rabbit, {}); // 将 rabbit 的原型修改为 {}

注意:以前可以用 proto 属性设置原型,现在该属性已废弃,不建议使用。

for...in 循环也会迭代继承的属性

如果想排出继承的属性,可以用内建方法 obj.hasOwnProperty(key),如果 obj 有自己的名为 key 的属性则返回 true。 该方法是 Object.prototype.hasOwnProperty 提供的,hasOwnProperty 的 enumerable 为 false,是不可枚举的。而在 for...in 只会列出可枚举的属性。

F.prototype

我们可以使用 new F() 这样的构造函数来创建一个新对象。 如果给 F.prototype 赋值一个对象,那么在 new F() 时,F.prototype 属性为新对象添加 [[ Prototype]](原型)。 举例:

let animal = {
  eats: true
};
function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal; // 当创建一个 new Rabbit 时,把它的 [[Prototype]] 赋值为 animal
let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal

注意: 如果在创建之后,F.prototype 属性发生变化(F.prototype = < another object>),那么通过 new F 创建的新对象也将随之拥有新的对象作为 [[ Prototype]],但已经存在的对象将保持旧有的值。

默认 F.prototype

每个函数都有 "prototype" 属性,默认的 "prototype" 是一个只有属性 constructor 的对象,属性 constructor 指向函数本身。

// 可以在控制台打印出来看,f 指向函数本身
{constructor: ƒ}

举例:

// 假设有函数 Rabbit
function Rabbit() {}

/* 则其默认的 prototype 为
Rabbit.prototype = { constructor: Rabbit }
*/

// 此时新建一个对象,则对象的原型 [[Prototype]] 为 { constructor: Rabbit }
let rabbit = new Rabbit('white')
// 此时 rabbit.constructor == Rabbit,所以可以用 rabbit.constructor 属性来创建一个新对象
let rabbit2 = new rabbit.constructor("black")

constructor 的好处就是,当不知道一个对象使用哪个构造函数时,可以用这个对象的 constructor 属性来创建类似的新对象。 可是如果将 F.prototype 默认值改掉(比如 F.prototype = {}),就不一定会有 "contructor" 属性了。 因此,为了保证有正确的 "contructor",我们直接添加/删除属性到默认的 prototype(比如 F.prototype.check = true),而不是将其整个替换。 或者,手动添加 "contructor" 属性,将其指向函数本身。

F.prototype = {
    check: true,
    constructor: F
}

以上笔记参考《现代 JavaScript 教程》及 MDN 文档