头图

在 Chrome 开发者工具的 sources 面板中,我们查看一个 function 时,有时会看到 [[prototype]]。要理解这个概念,先得了解 JavaScript 中的原型(Prototype)和原型链(Prototype Chain)。

在 JavaScript 中,每个对象都有一个隐藏的、内部的属性,被称为 [[Prototype]]。这个 [[Prototype]] 是指向另一个对象的引用,这个对象通常被称作原型(prototype)。通过原型,我们可以访问另一个对象的属性和方法。如果原型对象自身也有一个 [[Prototype]],那么这个链条就会向上延续。这种机制就是所谓的原型链(Prototype Chain)。

使用场合

[[Prototype]] 主要用于对象的继承机制。通过这种机制,我们可以创建出具有共享行为和属性的对象。我们用一个真实世界的例子来更好地理解这个机制。

假设我们有一个通用的Employee(员工)对象,它具有一些基本属性和方法。然后,我们有一个特定的Manager(经理)对象,需要从 Employee 继承某些基本行为,但也包含一些特定于 Manager 的属性和方法。在这种情况下,我们可以利用原型继承来实现这一点。

代码示例

我们先创建一个具有基本行为的 Employee 构造函数:

function Employee(name, age) {
  this.name = name;
  this.age = age;
}

Employee.prototype.getDetails = function() {
  return `Name: ${this.name}, Age: ${this.age}`;
};

接着,我们创建一个 Manager 构造函数,该构造函数继承自 Employee

function Manager(name, age, department) {
  Employee.call(this, name, age);
  this.department = department;
}

Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;

Manager.prototype.getManagerDetails = function() {
  return `${this.getDetails()}, Department: ${this.department}`;
};

在上面的代码中,Manager 的原型被设置为一个新的对象,这个对象是用 Employee.prototype 作为原型创建的。我们使用 Object.create(Employee.prototype) 来创建这个对象,以确保 Manager 继承了 Employee 的方法。

下面是代码的使用示例:

var emp1 = new Employee("Alice", 30);
console.log(emp1.getDetails()); // 输出: Name: Alice, Age: 30

var mgr1 = new Manager("Bob", 40, "Engineering");
console.log(mgr1.getDetails()); // 输出: Name: Bob, Age: 40
console.log(mgr1.getManagerDetails()); // 输出: Name: Bob, Age: 40, Department: Engineering

通过这个例子,我们可以看到 EmployeeManager 通过原型链建立了连接,使 Manager 可以访问 Employee 的属性和方法。

进一步探索

为了更好地理解 [[Prototype]] 的实际作用,我们可以利用 Chrome 开发者工具进行调试。创建好上述代码后,我们可以在 Chrome 开发者工具中查看具体对象及其原型链。

首先,在浏览器中运行上述代码。点击页面右上角的三点菜单,选择 更多工具,然后选择 开发者工具。在 Console 面板中,可以查看 emp1mgr1 对象的详细信息。

console.log(emp1);
console.log(mgr1);

在 Console 中输入以上命令并回车,我们可以看到 emp1mgr1 对象详情。在对象属性中,我们可以看到 [[Prototype]] 字段。点击 [[Prototype]] 字段,我们能看到这些对象的原型链。

深入剖析原型链

原型链的概念有助于理解 JavaScript 的继承机制。每个 [[Prototype]] 链条上的对象都可以访问其上的方法和属性。如果当前对象的属性或方法不存在,JavaScript 引擎会顺着 [[Prototype]] 链条向上查找,直到找到为止。我们可以从另一个角度来观察这个问题。

考虑一个简单的例子,假设我们有一个包含普通属性和原型属性的对象。

var obj = {
  prop1: 'I am prop1',
};

console.log(obj.prop1); // 输出: I am prop1
console.log(obj.prop2); // 输出: undefined

现在我们为这个对象添加一个原型链上的属性:

var parentObj = {
  prop2: 'I am prop2 via prototype',
};

Object.setPrototypeOf(obj, parentObj);

console.log(obj.prop2); // 输出: I am prop2 via prototype

在这里,通过 Object.setPrototypeOf(obj, parentObj),我们可以看到即使 obj 没有 prop2 属性,它也能通过原型链获得 prop2 的值。

现实案例研究

为了更好地理解原型链及其在实际应用中的使用,我们来看一个更为复杂的案例。

假设我们正在开发一个电子商务网站,这个网站需要处理大量和多种类的产品。我们有一个通用的 Product 类,其中包含所有产品的基本属性和方法。然后,我们有一些特定类型的产品,比如 Electronics(电子产品)和 Clothing(服装),继承基本的 Product 行为并添加各自特有的行为。

function Product(name, price) {
  this.name = name;
  this.price = price;
}

Product.prototype.getDetails = function() {
  return `Name: ${this.name}, Price: ${this.price}`;
};

function Electronics(name, price, brand) {
  Product.call(this, name, price);
  this.brand = brand;
}

Electronics.prototype = Object.create(Product.prototype);
Electronics.prototype.constructor = Electronics;
Electronics.prototype.getElectronicsDetails = function() {
  return `${this.getDetails()}, Brand: ${this.brand}`;
};

function Clothing(name, price, size) {
  Product.call(this, name, price);
  this.size = size;
}

Clothing.prototype = Object.create(Product.prototype);
Clothing.prototype.constructor = Clothing;
Clothing.prototype.getClothingDetails = function() {
  return `${this.getDetails()}, Size: ${this.size}`;
};

在这个案例中,我们可以创建具有通用行为和特定行为的不同类型对象:

var laptop = new Electronics("Laptop", 1200, "Dell");
console.log(laptop.getDetails()); // 输出: Name: Laptop, Price: 1200
console.log(laptop.getElectronicsDetails()); // 输出: Name: Laptop, Price: 1200, Brand: Dell

var tshirt = new Clothing("T-shirt", 20, "Medium");
console.log(tshirt.getDetails()); // 输出: Name: T-shirt, Price: 20
console.log(tshirt.getClothingDetails()); // 输出: Name: T-shirt, Price: 20, Size: Medium

我们会发现 laptop 对象可以调用 Product 类中的 getDetails() 方法,而 tshirt 对象也可以调用同样的方法。由于原型链的存在,我们可以优雅地共享和组织代码,不必为每种产品类型重复代码。

记住,[[Prototype]] 是抽象的

尽管在 JavaScript 中可以使用许多不同的方法来模仿和实现面向对象编程,但 [[Prototype]] 本质上是非常抽象的。在大多数情况下,我们不需要直接使用这个属性。我们可以通过构造函数和工厂函数间接使用它。

例如,当我们使用 JavaScript 内置的 Object.create() 方法时,我们实际上是在设置对象的 [[Prototype]]

var someObject = Object.create(parentObj);
console.log(someObject.__proto__ === parentObj); // 输出: true

Object.create(parentObj) 创建了一个新的对象,并将该对象的 [[Prototype]] 设置为 parentObj

总结

[[Prototype]] 是 JavaScript 中的一个基本概念,用于实现对象的继承机制。它通过指向另一个对象(即原型),形成一个链条,使得 JavaScript 对象能够继承和共享属性和方法。在实际编程中,理解和掌握原型链可以帮助我们编写更简洁、更高效的代码。

利用 Chrome 开发者工具查看和调试对象的原型链,可以帮助我们更好地理解和应用这个抽象的概念。通过示例代码和真实案例的讲解,希望帮助大家更好地理解这一重要机制。

请记住,在编写 JavaScript 代码时,虽然我们可以直接操作 [[Prototype]],但大多数情况下,我们更应该使用诸如 Object.create() 、构造函数以及 Class 语法等高级抽象来间接操作原型链,以便我们的代码更加易于维护和理解。


注销
1k 声望1.6k 粉丝

invalid