原型与原型链

时游大约 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 操作符做了什么?

  1. 创建一个新的空对象。
  2. 将这个新对象的 __proto__ 指向构造函数的 prototype
  3. 将构造函数的 this 绑定到新对象上,并执行构造函数代码。
  4. 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象。
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); // 继续向上找
    }
}
上次编辑于:
贡献者: 15327360835
Loading...