原型
JS 通过原型 (prototype) 支持 OOP 中的继承。
什么是原型
每个对象有一个隐藏属性 [[Prototype]]
,称为该对象的原型。
隐藏属性指的是 JS 引擎内部维护该属性,用户代码无法直接访问。
原型的值要么为 null
,要么为另一个对象。
同样,原型对象也可以有原型,这样就构成了一条原型链 (prototype chain)。
访问属性时首先查找对象的自有属性,然后查找原型属性。
原型链上的所有属性都会被对象所继承。
设置属性只针对对象本身,不会修改原型链。
有 2 种方法读写对象的原型:
Object.getPrototypeOf/Object.setPrototypeOf
方法__proto__
属性[[Prototype]]
的 getter/setter
构造函数原型
除了手动设置对象的原型实现继承之外,更多的是让一个类型继承另一个类型,从而使子类型的所有对象都能继承父类型的属性。
JS 中用构造函数定义一个类型 (假设是 F
),使用 new F(args)
创建该类型的对象。
构造函数 F
可以设置一个属性 prototype
,使用 new
操作符创建对象时,如果 F.prototype
是一个对象,则会把这个对象设置为新创建对象的原型 [[Prototype]]
。
// 原型对象
const animal = {
eats: true,
};
// 构造函数
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal;
// 实例对象
const rabbit = new Rabbit('White Rabbit'); // rabbit.__proto__ === animal
console.log(rabbit.eats); // true
函数默认原型
每个函数默认存在 prototype
属性,是只有一个属性 constructor
的对象,值为函数本身。
function Rabbit() {}
console.log(Rabbit.prototype); // { constructor: Rabbit }
constructor
的值可以被所有 Rabbit
创建出的对象访问,因此可以使用与一个对象相同的构造函数创建另一个对象。
const rabbit = new Rabbit('White Rabbit');
const rabbit2 = new rabbit.constructor('Black Rabbit');
为了不覆盖 constructor
这一有用的属性,修改函数的 prototype
时尽量不要完全替换,而是往上面添加属性,或者完全替换后再手动加上 constructor
。
Object.prototype
原型通常用于提供该类型的一些实用函数,比如 toString
、forEach
等。使一个该类型的空对象 (没有自有属性) 可以直接调用这些实用函数。
JS 中绝大部分对象都直接或间接继承自 Object.prototype
,该原型提供了大量适用于对象的实用函数。
其他内建类型,比如 Array
、Date
、Function
等,也提供了自己的原型方法,而这些原型对象 (Array.prototype
等) 的原型为 Object.prototype
。
Object.create
创建对象时指定原型的方法有 2 种:
Object.create(proto)
- 字面量中包括
__proto__
Object.create(proto, propertiesObject)
也可以用于拷贝一个对象 (浅拷贝),包括自有属性和继承属性 (原型),而自有属性可以使用 getOwnPropertyDescriptors
涵盖可枚举和不可枚举的数据属性和访问器属性,实现完全拷贝。
const clone = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);