原型与原型链
大约 3 分钟
原型与原型链
JavaScript 是一门基于原型的语言。理解原型链对于掌握 JS 的继承机制至关重要。
原型 (Prototype)
每个 JavaScript 对象(除了 null)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
__proto__: 每个对象都有这个属性,指向该对象的原型。prototype: 只有函数对象才有这个属性,指向实例的原型对象。
function Person() {}
const p = new Person();
console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
原型对象 vs 构造函数 vs 实例
- 实例.proto === 原型对象
- 构造函数.prototype === 原型对象
- 原型对象.constructor === 构造函数
特殊的对象创建
// 创建一个没有原型的对象 (纯净对象,常用于字典)
const pureObj = Object.create(null);
console.log(pureObj.__proto__); // undefined
// pureObj.toString(); // Error: pureObj.toString is not a function
原型链 (Prototype Chain)
当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链。
function Animal() {
this.species = "Animal";
}
function Dog(name) {
this.name = name;
}
// 设置原型链
Dog.prototype = new Animal();
const dog = new Dog("Buddy");
console.log(dog.name); // Buddy (自身属性)
console.log(dog.species); // Animal (继承自原型链)
console.log(dog.toString()); // [object Object] (继承自 Object.prototype)
链的终点: Object.prototype.__proto__ === null。
继承的实现方式
1. 组合继承 (常用)
结合了原型链继承和构造函数继承的优点。
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name); // 继承属性 (构造函数借用)
this.age = age;
}
// 继承方法
Child.prototype = new Parent();
Child.prototype.constructor = Child; // 修复 constructor
const child1 = new Child("Tom", 5);
child1.colors.push("black");
console.log(child1.colors); // ["red", "blue", "black"]
child1.sayName(); // Tom
const child2 = new Child("Jerry", 4);
console.log(child2.colors); // ["red", "blue"] (引用类型互不干扰)
2. 寄生组合式继承 (最佳实践)
解决了组合继承中调用两次父类构造函数的问题。
function inheritPrototype(child, parent) {
const prototype = Object.create(parent.prototype); // 创建对象
prototype.constructor = child; // 增强对象
child.prototype = prototype; // 指定对象
}
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHi = function() {
console.log("Hi");
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
inheritPrototype(Child, Parent);
const instance = new Child("Alice", 18);
instance.sayHi();
3. ES6 Class 继承
语法糖,底层依然是原型继承。
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // 调用父类构造函数
}
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.
Class 的新特性 (ES2022)
class MyClass {
// 实例属性 (无需在 constructor 中定义)
count = 0;
// 私有属性 (只能在类内部访问)
#privateField = "secret";
// 静态代码块 (类加载时执行一次)
static {
console.log("Class loaded");
}
getSecret() {
return this.#privateField;
}
}
new 操作符做了什么?
- 创建一个新的空对象。
- 将这个新对象的
__proto__指向构造函数的prototype。 - 将构造函数的
this绑定到新对象上,并执行构造函数代码。 - 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象。
function myNew(Constructor, ...args) {
const obj = Object.create(Constructor.prototype);
const result = Constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}
instanceof 原理
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left); // 获取对象的原型
const prototype = right.prototype; // 获取构造函数的 prototype
while (true) {
if (!proto) return false; // 到了原型链顶端 (null)
if (proto === prototype) return true; // 找到了
proto = Object.getPrototypeOf(proto); // 继续向上找
}
}
Loading...
