JavaScript对象的原型(prototype)简介

原文:Intro to Object Prototypes in JavaScript

JavaScript的继承是基于原型(prototype-based)的,即使使用ES6中类的extends关键词时也是如此。本文介绍了原型(prototype)的一些知识。

JavaScript中用{}创建一个空对象,它其实自带了一些内置属性,例如toString()函数

1
2
const obj = {};
obj.toString(); // '[object Object]'

Mozilla的文档把这个函数记录为Object.prototype.toString(),因为objObject类的一个实例。

在调用toString属性时,JavaScript先查看obj是否有toString属性,如果没有,JavaScript沿着继承链向上找到Object.prototype,继续查看Object.prototype是否有toString属性。

1
2
3
4
5
6
const obj = {};
obj instanceof Object; // true
obj.toString === Object.prototype.toString; // true

obj.toString = () => {};
obj.toString === Object.prototype.toString; // false

可以把Object.prototype看作是一个模板对象,所有对象都从它这里继承一些方法和属性。

向prototype添加属性

prototype与JavaScript的其他对象没有本质区别。这意味着可以向Object.prototype添加新属性,这样所有对象都可以访问到这些属性。

1
2
3
4
5
// 为所有对象添加 `getAnswer()` 函数
Object.prototype.getAnswer = function() { return 42 };

const obj = {};
obj.getAnswer(); // 42

虽然可以向Object.prototype添加方法,但这样做是不对的,这种操作会导致未来版本JavaScript的兼容性问题。例如,一个流行的库添加了一个Array.prototype.flatten(),与一个新的JavaScript内置函数冲突,导致了著名的“Smoosh门”崩溃事件

创建自己的prototype原型

在ES6之前的类,只是一个用new调用的普通的传统函数。

1
function MyClass() {}

MyClass函数有一个prototype属性,是可以修改的。

1
2
3
4
5
6
7
function MyClass() {}

// 向`MyClass`所有实例添加`getAnswer()`函数
MyClass.prototype.getAnswer = function() { return 42; };

const obj = new MyClass();
obj.getAnswer(); // 42

甚至可以整体覆盖MyClass函数的prototype

1
2
3
4
5
6
7
8
9
function MyClass() {}

// 整体覆盖prototype
MyClass.prototype = {
getAnswer: function() { return 42; }
};

const obj = new MyClass();
obj.getAnswer(); // 42

从其他类继承

prototype对象并不一定是一个普通的对象,它可以是任何其他类的实例。要创建一个MyChildClass类,继承MyClass,可以将MyChildClassprototype设置为MyClass的一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function MyClass() {}

// 整体覆盖prototype
MyClass.prototype = {
getAnswer: function() { return 42; }
};

function MyChildClass() {}
MyChildClass.prototype = new MyClass();

const obj = new MyChildClass();
obj.getAnswer(); // 42

// `obj`是`MyChildClass`的实例,`MyChildClass`继承于`MyClass`,`MyClass`继承于`Object`。
obj instanceof MyChildClass; // true
obj instanceof MyClass; // true
obj instanceof Object; // true

MyChildClass继承于MyClassMyClass继承于Object。因为MyChildClass.prototypeMyClass的实例,而MyClass.prototypeObject的实例。这就是JavaScript开发者常说的原型链

获取对象的原型

给定一个对象,可以用.constructor.prototype获取它的prototype。

1
2
3
4
5
6
7
function MyClass() {}

const obj = new MyClass();
obj.constructor.prototype.getAnswer = function() { return 42; };

const obj2 = new MyClass();
obj2.getAnswer(); // 42

这是因为对象的constructor属性指向了Object.prototype.constructor。另外还有一个和constructor.prototype相似的,非标准的__proto__属性。

constructor__proto__属性是潜在的原型中毒攻击向量。一些流行的JavaScript库,包括lodashMongoose,都曾报告过原型中毒缺陷。