myfreax

JavaScript 原型

在本教程中,您将了解 JavaScript 原型及其背后的工作原理

JavaScript 原型
JavaScript 原型

在本教程中,您将了解 JavaScript 原型及其背后的工作原理。

JavaScript 原型简介

在 JavaScript ,对象可以通过原型继承特性。每个对象都有一个自己的原型属性。

因为原型本身也是另一个对象,所以原型有自己的原型。这形成了原型。当原型的值是 null ,原型结束。

假设您有一个对象 person,其属性名为 name

let person = {'name' : 'John'}

在控制台中检查 person 对象时,您会发现 person 对象有一个名为 prototype 的属性 [[Prototype]]

原型本身是一个具有自己属性的对象:

当您访问对象的属性时,如果对象具有该属性,它将返回属性值。以下示例访问 person 对象的 name 属性:

它按预期返回 name 属性值。

但是,如果您访问对象中不存在的属性,JavaScript 引擎将在对象的原型中进行搜索。

如果 JavaScript 引擎无法在对象的原型中找到该属性,它将在原型的原型中搜索,直到找到该属性或到达原型的末尾。

例如,您调用 person 对象 toString() 的方法:

toString() 方法会返回 person 对象的字符串表示形式 [object Object]。默认情况下,它给出信息是不足够的 。

请注意,当函数是对象属性的值时,它被称为方法。因此,方法是函数作为作为的属性。

在这个例子中,当我们调用 person 对象 toString() 方法时,JavaScript 引擎会在person 对象中找到它。

因为这个 person 对象没有这个 toString()  方法,它会在 person 对象的原型对象中寻找 toString()  这个方法。

由于 person 对象原型有 toString() 方法,因此 JavaScript 引擎会调用 person 对象原型的 toString()

JavaScript 原型图

JavaScript 具有内置 Object() 函数。如果您将函数传递给运算符 typeoftypeof 运算符将返回 'function' 。例如:

typeof(Object)

输出:

'function'
请注意,Object() 是一个函数,而不是一个对象。如果这是您第一次了解 JavaScript 原型,您会感到困惑。

此外,JavaScript 提供一个匿名对象,可以通过对象的 prototype 的属性来引用 Object() 函数:

console.log(Object.prototype);

Object.prototype 对象具有一些有用的属性方法,例如 toString()valueOf()

Object.prototype 还有 一个重要的属性,称为 constructorconstructor 引用着 Object() 函数。

以下语句可以确定 Object.prototype.constructor 属性引用  Object 函数:

console.log(Object.prototype.constructor === Object); // true

假设一个圆代表一个函数,一个正方形代表一个对象。下图说明 Object() 函数与 Object.prototype 对象的关系:

首先,定义一个构造函数称为 Person。如下:

function Person(name) {
    this.name = name;
}

在此示例中,Person()函数接受一个 name 参数并将其分配给 this 对象 name 的属性。

在幕后,JavaScript 创建一个函数 Person() 和一个匿名对象:

Object() 函数一样,Person() 函数有一个属性叫 prototypeprototype 引用着匿名对象。并且匿名对象有一个 constructor 属性,引用着 Person() 函数。

下面展示 Person() 函数与匿名对象通过 Person.prototype 互相引用。

console.log(Person);
console.log(Person.prototype);

此外,JavaScript 通过将 Person.prototype 对象链接到对象,这被称为原型链接Object.prototype[[Prototype]]

In addition, JavaScript links the Person.prototype object to the Object.prototype object via the [[Prototype]], which is known as a prototype linkage.

除此之外,JavaScript 通过 [[Prototype]] 链接 Person.prototypeObject.prototype ,这称为原型链。

原型链接在下图中用 [[Prototype]] 表示:

在 JavaScript 原型对象定义方法

下面定义了一个 greet() 方法在 Person.prototype 对象:

Person.prototype.greet = function() {
    return "Hi, I'm " + this.name + "!";
}

在这种情况下,JavaScript 引擎将 greet() 方法添加到 Person.prototype 对象:

下面创建一个 Person 实例:

let p1 = new Person('John');

在内部,JavaScript 引擎创建一个名为 p1 的新对象 ,并通过原型链接将 p1 对象链接到 Person.prototype 对象:

p1Person.prototypeObject.protoype 之间的链接称为原型链。

下面在 p1 对象调用 greet() 方法:

let greeting = p1.greet();
console.log(greeting);

因为 p1 没有 greet() 方法,JavaScript 引擎搜索原型链并在 Person.prototype 对象找到它 greet()

由于 JavaScript 可以在 Person.prototype 对象上找到 greet() 方法,因此它会执行greet() 方法并返回结果:

下面调用 p1 对象上的 toString() 方法:

let s = p1.toString();
console.log(s);

在这种情况下,JavaScript 引擎会在 Person.prototype 原型链查找 toString()

因为 Person.prototype 没有 toString() 方法,JavaScript 引擎会向上搜索原型链并在 Object.prototype 对象找到 toString() 方法。

由于 JavaScript 可以找到 Object.prototypetoString() 方法,因此它会执行 toString() 方法。

如果你调用了一个在 Person.prototypeObject.prototype 对象上都不存在的方法,JavaScript 引擎会沿着原型链,如果找不到该方法就会抛出错误。例如:

p1.fly(); 

由于原型链中的任何对象都不存在 fly() 方法,因此 JavaScript 引擎会抛出以下错误:

TypeError: p1.fly is not a function

以下创建 Person 的另一个实例其名称属性的值是 'Jane'

let p2 = new Person('Jane');

对象 p2 具有 p1 对象的属性和方法。总之,当你在 prototype 对象定义一个方法时,这个方法被所有实例共享。

在单个对象中定义方法

下面在 p2 对象定义 draw() 方法。

p2.draw = function () {
    return "I can draw.";
};

JavaScript 引擎将 draw() 方法添加到 p2 对象,而不是 Person.prototype 对象:

这意味着您可以调用 p2 对象的 draw() 方法:

p2.draw();

但是你不能在 p1 对象上调用 draw() 方法:

p1.draw()

错误:

TypeError: p1.draw is not a function

当您在对象中定义方法时,该方法仅可用于该对象。默认情况下不能与其他对象共享。

获取原型链接

__proto__ 发音为 dunder proto。__proto__  是 Object.prototype 对象的访问器属性。它公开访问它的对象的内部原型链接。

__proto__ ES6 中标准化,以确保与 Web 浏览器的兼容性。但是,将来可能会弃用__proto__ 并使用 Object.getPrototypeOf() 代替。因此,您不应该在生产代码中使用 __proto__

p1.__proto__ 公开了引用 Person.prototype 对象的  [[Prototype]] 。同样,p2.__proto__ 也引用相同的对象作为 p1.__proto__:

console.log(p1.__proto__ === Person.prototype); // true
console.log(p1.__proto__ === p2.__proto__); // true

如前所述,您应该使用 Object.getPrototypeOf() 方法而不是 __proto__. Object.getPrototypeOf() 方法返回指定对象的原型。

console.log(p1.__proto__ === Object.getPrototypeOf(p1)); // true

获取原型链接的另一种流行方法是当 Object.getPrototypeOf() 方法不可用时通过constructor 属性获取,如下所示:

p1.constructor.prototype

因此  .constructor.prototype 返回原型对象, p1.constructor 返回 Person

隐藏方法

请参阅以下方法调用:

console.log(p1.greet());

p1 对象没有 greet() 定义方法,因此 JavaScript 会向上到原型链搜索 greet()。在这种情况下,它可以找到 Person.prototype 对象中的方法。

让我们向 p1  对象添加一个新方法,其名称与 Person.prototype 对象中的方法相同:

p1.greet = function() {
    console.log('Hello');
}

然后调用 greet() 方法:

console.log(p1.greet());

因为 p1 对象有 greet() 方法,JavaScript 直接执行它,而不用在原型链中搜索 greet() 方法。

这是阴影的一个例子。p1 对象的 greet() 方法隐藏 prototype 对象的 greet() 方法。

结论

  • Object() 函数有一个名为 prototype 的属性,它引用 Object.prototype 对象。
  • Object.prototype 对象具有所有对象中可用的所有属性和方法,例如 toString() 和 valueOf()。
  • Object.prototype 对象具有引用 Object 函数的构造函数属性。
  • 每个函数都有一个 prototype 对象。Object.prototype 此原型对象通过 [[prototype]] 链接或属性引用对象 __proto__。
  • 原型链允许一个对象通过 [[prototype]] 链接使用其原型对象的方法和属性。
  • Object.getPrototypeOf() 方法返回指定对象的原型对象。 请使用 Object.getPrototypeOf() 方法而不是 __proto__。

内容导航