在本教程中,您将了解 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()
函数。如果您将函数传递给运算符 typeof
,typeof
运算符将返回 'function' 。例如:
typeof(Object)
输出:
'function'
请注意,Object()
是一个函数,而不是一个对象。如果这是您第一次了解 JavaScript 原型,您会感到困惑。
此外,JavaScript 提供一个匿名对象,可以通过对象的 prototype
的属性来引用 Object()
函数:
console.log(Object.prototype);
Object.prototype
对象具有一些有用的属性和方法,例如 toString()
和 valueOf()
。
Object.prototype
还有 一个重要的属性,称为 constructor
,constructor
引用着 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()
函数有一个属性叫 prototype
,prototype
引用着匿名对象。并且匿名对象有一个 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.prototype
与 Object.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
对象:
p1
、Person.prototype
和 Object.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.prototype
的 toString()
方法,因此它会执行 toString()
方法。
如果你调用了一个在 Person.prototype
和 Object.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__。