JavaScript prototype, [[Prototype]] 和 __proto__ 晕不晕?
1 prototype, [[Prototype]] 和 __proto__
| 名称 | 类型 | 适用对象 | 作用 | 是否推荐使用 |
|---|---|---|---|---|
prototype | 函数属性 | 构造函数 | 定义实例共享的原型对象 | 是 |
[[Prototype]] | 内部属性 | 所有对象 | 表示对象的原型,用于继承 | 不直接操作 |
__proto__ | 对象属性 | 所有对象 | [[Prototype]] 的访问接口 | 不推荐 |
__proto__ 是一个早期引入的属性,现代 JavaScript 中,更推荐使用 Object.getPrototypeOf 和 Object.setPrototypeOf 来操作对象的 原型,而非 直接使用 __proto__。
2 什么是原型([[Prototype]])?
在 JavaScript 中,原型 是一个 对象,作为 其他对象的 模板,用于实现 属性 和 方法 的 继承。
每个对象 都有一个 内部属性 [[Prototype]](通过 __proto__ 访问),指向它的 原型对象。通过这个机制,对象可以 共享 和 继承 原型链 上的 属性 和 方法。
2.1 核心特点
// 示例代码 1
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () { // 所有实例共享同一个函数
console.log("Hello, I'm " + this.name);
};
const person1 = new Person("Alice");
const person2 = new Person("Bob");
console.log(person1.sayHello === person2.sayHello); // true (同一个函数)
// 示例代码 2
function Person(name) {
this.name = name;
this.sayHello = function () { // 每个实例都有一份独立的函数
console.log("Hello, I'm " + this.name);
};
}
const person1 = new Person("Alice");
const person2 = new Person("Bob");
console.log(person1.sayHello === person2.sayHello); // false (不同的函数)
观察上述代码(示例代码 1 和 示例代码 2),通过 Person.prototype.sayHello 定义方法,而不是直接在 Person 构造函数 中 定义方法,有以下几个 关键原因:
1 节约内存
如果 直接在 构造函数 中 定义方法,那么 每次 创建一个实例 都会 创建一份 新的 sayHello 方法。这会导致 内存浪费,特别是当 实例数量 较多时。
2 方法共享
通过 原型 定义的方法 是 所有实例 共享的,这符合 面向对象 的 设计原则。如果某些功能 是 所有实例 都需要的,应该 共享实现,而不是为每个实例 重复定义。
3 更方便地更新逻辑
通过 原型 定义方法,如果需要 更改 sayHello 的 逻辑,只需要 修改一次,所有实例 都会 自动继承 新的逻辑。
4 符合 JavaScript 的设计模式
JavaScript 的 原型机制 是为了实现 共享 和 继承 而设计的。如果直接在 构造函数 中 定义方法,实际上 违背了 它的 设计初衷。
3 什么是原型链?
原型链 是 JavaScript 用于实现 对象继承 的 一种 机制。
原型链 是 对象 与 原型 连接 形成的 链式结构。你可以把 原型链 想象成一条 “寻找路径”,每次 访问 属性 或 方法 时,JavaScript 都会 沿着 这条路径 逐级向上查找,直到 找到 或 返回 undefined。
3.1 查找步骤
当 访问 一个 对象的 属性 或 方法 时,JavaScript 按以下 步骤查找:
1 检查对象本身
首先,JavaScript 会检查 对象自身 是否有 这个属性或方法(包括通过 this 定义的 属性,以及 直接添加到 对象上的 属性)。
// 示例代码 3
const obj = { name: "Alice" };
console.log(obj.name); // 输出 "Alice" (找到对象本身的属性)
2 查找对象的原型
如果 对象本身 没有 这个属性或方法,JavaScript 会通过 对象的 内部属性(即 __proto__)指向的 原型 继续查找。
// 示例代码 4
const animal = { eats: true };
const dog = Object.create(animal); // 创建一个对象,并设置其原型为 animal
console.log(dog.eats); // 输出 true (从原型 animal 中获取)
console.log(dog.__proto__ === animal); // true
3 沿着原型链继续查找
如果 对象的原型 上也没有这个 属性或方法,JavaScript 会 沿着 原型链 向上查找,直到 找到该属性 或 到达 原型链的 终点。
// 示例代码 5
// dog 的原型
console.log(dog.__proto__); // 输出 { eats: true }
console.log(dog.__proto__ === animal); // true
// animal 的原型
console.log(animal.__proto__); // 输出 Object.prototype
console.log(animal.__proto__ === Object.prototype); // true
// Object.prototype 的原型
console.log(Object.prototype.__proto__); // null
4 到达原型链的终点
原型链的终点 是 null,即 Object.prototype.__proto__ 是 null。如果到达这里仍未找到,返回 undefined。
// 示例代码 6
console.log(dog.someNonExistentProperty); // 输出 undefined
4 继承
继承 是指 一个对象 可以通过 另一个对象 获得 属性和方法 的 机制。它是实现 代码复用 和 共享 的重要方式。
JavaScript 中 继承 的 核心机制 是通过 原型链 实现的。
4.1 实现方式
1 使用 Object.create 实现继承
// 示例代码 7
const animal = {
eats: true,
walk() {
console.log("Animal walks");
}
};
// 创建对象 dog, 设置 dog 的原型为 animal
const dog = Object.create(animal); // dog 继承自 animal
dog.barks = true;
console.log(dog.eats); // true, 从 animal 继承
dog.walk(); // 输出 "Animal walks"
2 构造函数和原型的结合
// 示例代码 8
// 定义父类(构造函数 + 原型)
function Animal(name) {
this.name = name; // 每个 Animal 实例都会有自己的 name 属性
}
// 给 Animal 的原型添加方法(共享方法)
Animal.prototype.walk = function () {
console.log(this.name + " is walking");
};
// 定义子类
function Dog(name, breed) {
// 使用 `call` 是为了在 `Dog` 的构造函数中调用 `Animal` 的构造函数,把 `name` 属性赋值到当前实例上。
Animal.call(this, name); // 调用父类的构造函数,继承 name 属性
this.breed = breed; // 给 Dog 添加自己的 breed 属性
}
// 继承父类的原型方法
Dog.prototype = Object.create(Animal.prototype); // 创建一个新的对象,并把它的原型设置为 Animal.prototype
// 因为继承后,`Dog.prototype.constructor` 被改成了父类的构造函数。
Dog.prototype.constructor = Dog; // 修正 constructor 指向
// 扩展子类的方法
Dog.prototype.bark = function () {
console.log(this.name + " says Woof!");
};
// 测试
const dog = new Dog("Buddy", "Golden Retriever");
dog.walk(); // 输出 "Buddy is walking" (继承自 Animal)
dog.bark(); // 输出 "Buddy says Woof!" (Dog 自己的方法)
3 使用 ES6 class 语法
// 示例代码 9
class Animal {
constructor(name) {
this.name = name;
}
walk() {
console.log(this.name + " walks");
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
console.log("Woof! Woof!");
}
}
const dog = new Dog("Buddy", "Golden Retriever");
dog.walk(); // 输出 "Buddy walks"
dog.bark(); // 输出 "Woof! Woof!"