原型

我们来仔细看看以下这段代码。

  1. let empty = {};
  2. console.log(empty.toString);
  3. // → function toString(){…}
  4. console.log(empty.toString());
  5. // → [object Object]

我从一个空对象中取出了一个属性。 好神奇!

实际上并非如此。我只是掩盖了一些 JavaScript 对象的内部工作细节罢了。每个对象除了拥有自己的属性外,都包含一个原型(prototype)。原型是另一个对象,是对象的一个属性来源。当开发人员访问一个对象不包含的属性时,就会从对象原型中搜索属性,接着是原型的原型,依此类推。

那么空对象的原型是什么呢?是Object.prototype,它是所有对象中原型的父原型。

  1. console.log(Object.getPrototypeOf({}) ==
  2. Object.prototype);
  3. // → true
  4. console.log(Object.getPrototypeOf(Object.prototype));
  5. // → null

正如你的猜测,Object.getPrototypeOf返回一个对象的原型。

JavaScript 对象原型的关系是一种树形结构,整个树形结构的根部就是Object.prototypeObject.prototype提供了一些可以在所有对象中使用的方法。比如说,toString方法可以将一个对象转换成其字符串表示形式。

许多对象并不直接将Object.prototype作为其原型,而会使用另一个原型对象,用于提供一系列不同的默认属性。函数继承自Function.prototype,而数组继承自Array.prototype

  1. console.log(Object.getPrototypeOf(Math.max) ==
  2. Function.prototype);
  3. // → true
  4. console.log(Object.getPrototypeOf([]) ==
  5. Array.prototype);
  6. // → true

对于这样的原型对象来说,其自身也包含了一个原型对象,通常情况下是Object.prototype,所以说,这些原型对象可以间接提供toString这样的方法。

你可以使用Object.create来创建一个具有特定原型的对象。

  1. let protoRabbit = {
  2. speak(line) {
  3. console.log(`The ${this.type} rabbit says '${line}'`);
  4. }
  5. };
  6. let killerRabbit = Object.create(protoRabbit);
  7. killerRabbit.type = "killer";
  8. killerRabbit.speak("SKREEEE!");
  9. // → The killer rabbit says 'SKREEEE!'

像对象表达式中的speak(line)这样的属性是定义方法的简写。 它创建了一个名为speak的属性,并向其提供函数作为它的值。

原型对象protoRabbit是一个容器,用于包含所有兔子对象的公有属性。每个独立的兔子对象(比如killerRabbit)可以包含其自身属性(比如本例中的type属性),也可以派生其原型对象中公有的属性。