原型链
在浏览器控制台中,试着创建一个对象字面量:
const myObject = {
city: "Madrid",
greet() {
console.log(`来自 ${this.city} 的问候`);
},
};
myObject.greet(); // 来自 Madrid 的问候
这里有一个对象,它具有数据属性 city
和方法 greet()
。如果你在控制台中输入对象的名称,然后跟随一个小数点(如同 myObject.
),控制台会列出该对象可用的一系列属性。你会看到,除了 city
和 greet
外,还有很多其他属性!
__defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__ __proto__ city constructor greet hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf
试着访问其中一个:
JSCopy to Clipboard
myObject.toString(); // "[object Object]"
它可以成功调用(即使你不知道 toString()
到底在做什么)。
这些额外的属性是什么,它们是从哪里来的?
JavaScript 中所有的对象都有一个内置属性,称为它的 prototype(原型)。它本身是一个对象,故原型对象也会有它自己的原型,逐渐构成了原型链。原型链终止于拥有 null
作为其原型的对象上。
备注: 指向对象原型的属性并不是 prototype
。它的名字不是标准的,但实际上所有浏览器都使用 __proto__。访问对象原型的标准方法是 Object.getPrototypeOf()。
当你试图访问一个对象的属性时:如果在对象本身中找不到该属性,就会在原型中搜索该属性。如果仍然找不到该属性,那么就搜索原型的原型,以此类推,直到找到该属性,或者到达链的末端,在这种情况下,返回 undefined
。
所以,在调用 myObject.toString()
时,浏览器做了这些事情:
- 在
myObject
中寻找toString
属性 myObject
中找不到toString
属性,故在myObject
的原型对象中寻找toString
- 其原型对象拥有这个属性,然后调用它。
myObject
的原型是什么?为了找到答案,我们可以使用 Object.getPrototypeOf()
函数:
JSCopy to Clipboard
Object.getPrototypeOf(myObject); // Object { }
有个对象叫 Object.prototype
,它是最基础的原型,所有对象默认都拥有它。Object.prototype
的原型是 null
,所以它位于原型链的终点:
一个对象的原型并不总是 Object.prototype
,试试这段代码:
JSCopy to Clipboard
const myDate = new Date();
let object = myDate;
do {
object = Object.getPrototypeOf(object);
console.log(object);
} while (object);
// Date.prototype
// Object { }
// null
这段代码创建了 Date
对象,然后遍历了它的原型链,记录并输出了原型。从中我们知道 myDate
的原型是 Date.prototype
对象,它(Date.prototype
)的原型是 Object.prototype
。
实际上,如果调用了你所熟悉的方法(如 myDate2.getMonth()
),是在 Date.prototype
上定义的方法调用的。
属性遮蔽
如果你在一个对象中定义了一个属性,而在该对象的原型中定义了一个同名的属性,会发生什么?我们来看看:
JSCopy to Clipboard
const myDate = new Date(1995, 11, 17);
console.log(myDate.getYear()); // 95
myDate.getYear = function () {
console.log("别的东西!");
};
myDate.getYear(); // '别的东西!'
鉴于对原型链的描述,这应该是可以预测的。当我们调用 getYear()
时,浏览器首先在 myDate
中寻找具有该名称的属性,如果 myDate
没有定义该属性,才检查原型。因此,当我们给 myDate
添加 getYear()
时,就会调用 myDate
中的版本。
这叫做属性的“遮蔽”。
设置原型
在 JavaScript 中,有多种设置对象原型的方法,这里我们将介绍两种:Object.create()
和构造函数。
使用 Object.create
Object.create()
方法创建一个新的对象,并允许你指定一个将被用作新对象原型的对象。
这里有个示例:
JSCopy to Clipboard
const personPrototype = {
greet() {
console.log("hello!");
},
};
const carl = Object.create(personPrototype);
carl.greet(); // hello!
这里我们创建了一个 personPrototype
对象,它有一个 greet()
方法。然后我们使用 Object.create()
来创建一个以 personPrototype
为原型的新对象。现在我们可以在新对象上调用 greet()
,而原型提供了它的实现。