原型(prototype)是 JavaScript 对象相互继承特性的机制。
继承和原型链
JavaScript 中所有的对象都有一个内置属性,称为它的原型。它本身是一个对象,故原型对象也会有它自己的原型,逐渐构成了原型链。原型链终止于拥有 null 作为其原型的对象上。
有个对象叫 Object.prototype,它是最基础的原型,所有对象默认都拥有它。Object.prototype 的原型是 null,所以它位于原型链的终点。
const myDate = new Date();
例如 myDate 对象的原型是 Date.prototype 对象,Date.prototype 的原型是 Object.prototype。
原型的作用
当你试图访问一个对象的属性时,如果在对象本身中找不到该属性,就会在原型中搜索该属性。如果仍然找不到该属性,那么就搜索原型的原型,以此类推,直到找到该属性,或者到达链的末端,在这种情况下,返回 undefined。
属性遮蔽
在对象的原型链中,当一个对象的属性与其原型链上某个对象的属性同名时,访问该属性时会优先访问对象自身的属性,而不是原型链上的属性。这种现象称为 属性遮蔽。
访问对象的原型
内部属性 [[Prototype]] 指向对象原型。访问对象原型的标准方法是 Object.getPrototypeOf()。实例属性 __proto__ 也指向对象原型,是一个非标准但广泛支持的方式来访问和设置对象的原型,但是不被推荐使用。
使用
__proto__是有争议且不被鼓励的。它的存在和确切行为仅作为遗留特性被标准化,以确保 Web 兼容性,但它存在一些安全问题和隐患。为了更好的支持,请优先使用Object.getPrototypeOf()/Reflect.getPrototypeOf()和Object.setPrototypeOf()/Reflect.setPrototypeOf()。
设置新对象的原型
使用字面量
使用字面量创建的对象的原型是 Object.prototype。
可以通过 __proto__ 字面量属性将新创建对象的原型指向另一个对象。
该语法已经标准化,并且在实现中得到了优化,与
Object.prototype.__proto__相当不同。
const o = { a: 1 };
const p = { b: 2, __proto__: o };
// 原型链:p ---> o ---> Object.prototype ---> null
使用 Object.create()
Object.create() 方法创建一个新的对象,并允许你指定一个将被用作新对象原型的对象。
const personPrototype = {
greet() {
console.log("hello!");
},
};
const carl = Object.create(personPrototype);
carl.greet(); // hello!
这里我们创建了一个 personPrototype 对象,它有一个 greet() 方法。然后我们使用 Object.create() 来创建一个以 personPrototype 为原型的新对象。现在我们可以在新对象上调用 greet(),而原型提供了它的实现。
使用构造函数
在 JavaScript 中,所有的函数都有一个名为 prototype 的属性。当你调用一个函数作为构造函数时,这个属性被设置为新构造对象的原型(按照惯例,在名为 __proto__ 的属性中)。
// Person 是一个函数
function Person(name) {
this.name = name;
}
const personPrototype = {
greet() {
console.log(`你好,我的名字是 ${this.name}!`);
},
};
// 为 Person 函数指定 prototype 属性
Object.assign(Person.prototype, personPrototype);
// 或者用下面的方法修改 Person 函数的 prototype 属性
Person.prototype.greet = personPrototype.greet;
在这段代码之后,使用 Person() 创建的对象将获得 Person.prototype 作为其原型,其中自动包含 greet 方法。
const p = new Person();
// p 的原型是 Person.prototype
// 由于 p 的原型有 greet() 方法,所以 p 也有 greet() 方法
p.greet();
使用类
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
const square = new Square(2);
// square ---> Square.prototype ---> Polygon.prototype ---> Object.prototype ---> null
修改现有对象的原型
Object.setPrototypeOf() 允许修改现有对象的 [[Prototype]] 内部属性。
const obj = { a: 1 };
const anotherObj = { b: 2 };
Object.setPrototypeOf(obj, anotherObj);
// obj ---> anotherObj ---> Object.prototype ---> null
自有属性
直接在对象中定义的而不是继承而来的属性,被称为 自有属性。
可以使用静态方法 Object.hasOwn() 检查一个属性是否是自有属性:
const irma = new Person("Irma");
console.log(Object.hasOwn(irma, "name")); // true
console.log(Object.hasOwn(irma, "greet")); // false
也可以在这里使用实例方法 Object.prototype.hasOwnProperty()。
const obj = {
name: 'John',
age: 30
};
console.log(obj.hasOwnProperty('name')); // 输出 true
console.log(obj.hasOwnProperty('toString')); // 输出 false,因为 'toString' 是从原型链继承的
两个方法除了调用方法不同外,还有以下区别:
-
hasOwnProperty的参数必须是字符串。Object.hasOwn的参数可以是字符串或符号。 -
hasOwnProperty是 JavaScript 的老方法,兼容性非常好,几乎所有环境都支持。Object.hasOwn是 ES2022 引入的,较新,可能在一些旧环境中不支持。
判断对象的原型(对象的类)
instanceof 运算符 用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);
// Expected output: true
console.log(auto instanceof Object);
// Expected output: true
有时会把对象的原型称为对象的类,例如 [] 是 Array 类的,实际上指的是 [] 的原型是 Array.prototype。(这里不是指 class 关键字创建的类)
参考
对象原型
https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes
继承与原型链
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#构造函数
instanceof
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof