理解 JavaScript 的原型

71 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

JavaScript 中的对象是基于原型的。

什么是原型

JavaScript 中的对象有一个特殊的 [[prototype]] 内置属性,就是对其他对象的引用,这个引用对象被称为此对象的原型。

几乎所有的对象在创建时 [[prototype]] 属性都会被赋予一个非空的值,也就是说几乎所有对象在创建时都有一个与之关联的原型。

JavaScript 中没有类来作为对象的抽象模式,通过原型来实现继承。

prototype 属性

几乎所有对象都有原型,但是只有少数对象有 proto type 属性。正是这些有 prototype 属性的对象为其他对象定义了原型。

prototype 特性

对象的 prototype 特性指的是对象的 [[prototype]] 内置属性。对象的 prototype 特性指定对象从哪里继承属性。

当 prototype 以代码字体出现时,指的是一个普通对象的属性,而不是 prototype 特性。构造函数的 prototype 属性用于指定通过该构造函数创建的对象的 prototype 特性。

function Foo(){}
const foo = new Foo();

Function.prototype.isPrototypeOf(Foo) // true
Foo.prototype.isPrototypeOf(foo) // true
Object.prototype.isPrototypeOf(Foo.prototype) // true

// 改写 prototype 属性
function Bar(){}
Bar.prototype = {a: 1}
const bar = new Bar();

Object.getPrototypeOf(bar) // {a: 1}
Function.prototype.isPrototypeOf(Bar) // true
Bar.prototype.isPrototypeOf(bar) // true
Object.prototype.isPrototypeOf(Bar.prototype) // true 

没有原型的对象

Object.prototype 是没有原型的对象,它不继承任何属性。

Object.create(null) 也是没有原型的对象,适合用来存储数据,不用担心受原型链的影响。

检查原型的方法

检查对象和对象的关系

确定一个对象是不是另一个对象的原型(或==原型链==中的一环),可以用 isPrototypeOf(),如下:

const p = {x:1};
const o = Object.create(p);
p.isPrototypeOf(o) // true 
Object.prototype.isPrototypeOf(o) // true
Object.prototype.isPrototypeOf(p) // true

检查对象和函数的关系

判断一个对象是不是一个构造函数的实例,可以用 instanceof,instanceof 操作符左侧是对象,右侧是对象类的标识(构造函数)。如下:

const arr = [1,2,3];
arr instanceof Array // true
arr instanceof Object // true 所有数组都是对象
arr instanceof Number // false

instanceof 的工作原理是:为了对 o instanceof f 求值,JavaScript 会对 f.prototype 求值,然后在 o 的原型链上查找这个值。如果找到了,o 是 f 的实例,instanceof 返回 true。否则,o 不是 f 的实例,instanceof 返回 false。

查找和修改对象的原型

查询任何对象的原型,可以用 Object.getPrototypeOf(),如下:

Object.getPrototypeOf({}) // Object.prototype
Object.getPrototypeOf([]) // Array.prototype
Object.getPrototypeOf(()=>{}) // Function.prototype

对象的 prototype 特性在它创建时会被设定,修改对象的原型可以使用 Object.setPrototypeOf() ,如下:

const foo = {x:1};
const bar = {y:2};
Object.setPrototypeOf(foo,bar) // 把 foo 的原型设为 bar
foo.y // 2

对象的 _proto_ 属性暴露了对象的 prototype 特性。在现在 JavaScript 中,_proto_ 是可读可写的,可以(但不应该)代替 Object.getPrototypeOf() 和 Object.setPrototypeOf()。

检查属性

hasOwnProperty() 只会检查属性是否在对象中,不会检查原型链。

in 操作符 会检查属性是否在对象及其原型链中。