探究JavaScript中对象原型与原型链的基础概念

256 阅读3分钟

探究JavaScript中对象原型与原型链的基础概念

一.前言

当谈论 JavaScript 中对象的原型时,我们需要理解几个关键概念和特例。大多数情况下,JavaScript 中的对象通过原型链进行属性和方法的继承。然而,使用 Object.create(null) 创建的对象是一个例外,它们不具有原型链,因此不继承任何属性或方法。

二.原型 (显式原型)

  • 定义与用途:

    • 原型是函数对象的一个属性,称为 prototype。它定义了通过该构造函数创建的实例对象的共享属性和方法。
    • 实例对象可以访问并继承构造函数的原型对象的属性和方法。
  • 简单示例:

// 构造函数
function Person(name) {
    this.name = name;
}

// 在构造函数的原型上定义方法
Person.prototype.greet = function() {
    return 'Hello, ' + this.name;
};

// 创建实例
let person1 = new Person('Alice');
let person2 = new Person('Bob');

// 调用原型上的方法
console.log(person1.greet()); // 输出: Hello, Alice
console.log(person2.greet()); // 输出: Hello, Bob

在这个示例中:

  • Person 是一个构造函数,它有一个 prototype 属性,该属性上定义了 greet 方法。
  • person1 和 person2 是通过 Person 构造函数创建的实例。它们通过原型链继承了 greet 方法,因此可以直接调用 greet() 方法。

三. 原型链

  • 机制与作用:
    • 原型链是对象间委托的机制,用于属性和方法的查找。
    • 当访问对象的属性或方法时,如果当前对象没有该属性或方法,JavaScript 引擎会沿着对象的 __proto__ (隐式原型)链向上查找,直到找到该属性或方法或链结束(即 __proto__ 为 null)
  • 简单示例:
// 定义一个父构造函数
function Animal(name) {
    this.name = name;
}

// 在父构造函数的原型上定义方法
Animal.prototype.walk = function() {
    return this.name + ' is walking.';
};

// 定义一个子构造函数,继承自 Animal
function Bird(name, canFly) {
    Animal.call(this, name); // 调用父构造函数的方法
    this.canFly = canFly;
}

// 设置 Bird 的原型为 Animal 的实例,实现继承
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird; // 修复 constructor 指向

// 在 Bird 的原型上定义方法
Bird.prototype.fly = function() {
    if (this.canFly) {
        return this.name + ' is flying.';
    } else {
        return this.name + ' cannot fly.';
    }
};

// 创建 Bird 实例
let eagle = new Bird('Eagle', true);

// 调用继承自 Animal 的方法和 Bird 自身的方法
console.log(eagle.walk()); // 输出: Eagle is walking.
console.log(eagle.fly()); // 输出: Eagle is flying.

在这个示例中:

  • Animal 是一个父构造函数,定义了 walk 方法。
  • Bird 是一个子构造函数,继承自 Animal 构造函数。通过 Object.create 方法将 Bird.prototype 设置为 Animal.prototype 的实例,实现了原型链的继承关系。
  • eagle 是 Bird 构造函数创建的实例,它可以访问 Animal 的方法 walk 和自身定义的方法 fly

四.隐式原型

  • 定义与关联:

    • 对象的 __proto__ 属性指向其构造函数的 prototype 属性。
    • 通过 __proto__ 属性,对象可以访问构造函数的原型对象。
  • 简单示例:

// 创建一个对象
let obj = {
    a: 1,
    b: 2
};

// 使用 __proto__ 来访问对象的隐式原型
console.log(obj.__proto__ === Object.prototype); // 输出: true

// 扩展对象的原型方法
Object.prototype.printProperties = function() {
    for (let key in this) {
        if (this.hasOwnProperty(key)) {
            console.log(key + ': ' + this[key]);
        }
    }
};

// 调用扩展的原型方法
obj.printProperties();

在这个示例中:

  • obj 是一个普通对象,它的隐式原型 __proto__ 指向 Object.prototype
  • 通过给 Object.prototype 扩展了一个 printProperties 方法,使得所有通过 Object 构造函数创建的对象(包括 obj)都可以访问和调用这个方法。

五. 所有对象都有原型吗?

  • 特例:
    • 大多数对象在 JavaScript 中都有原型,它们通过 __proto__ 指向其构造函数的 prototype。然而,使用 Object.create(null) 创建的对象是例外,它们的 __proto__ 是 null,因此不继承任何属性或方法。
  • 简单示例:
let obj = Object.create(null);
console.log(obj.__proto__); // 输出: null

在这个例子中,obj 是通过 Object.create(null) 创建的,它没有原型链,因此无法继承任何方法或属性,包括 JavaScript 中所有对象默认继承的方法如 toString() 等。