提问人:sw234 提问时间:11/22/2008 最后编辑:CRABOLOsw234 更新时间:3/9/2023 访问量:129873
在 JavaScript 中使用“prototype”与“this”?
Use of 'prototype' vs. 'this' in JavaScript?
问:
有什么区别
var A = function () {
this.x = function () {
//do something
};
};
和
var A = function () { };
A.prototype.x = function () {
//do something
};
答:
第一个示例仅更改该对象的接口。第二个示例更改该类的所有对象的接口。
评论
x
function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
在大多数情况下,它们本质上是相同的,但第二个版本可以节省内存,因为函数只有一个实例,而不是每个对象都有一个单独的函数。
使用第一种表单的一个原因是访问“私人成员”。例如:
var A = function () {
var private_var = ...;
this.x = function () {
return private_var;
};
this.setX = function (new_x) {
private_var = new_x;
};
};
由于 javascript 的作用域规则,private_var可用于分配给 this.x 的函数,但不能在对象外部使用。
评论
我相信克拉姆利@Matthew是对的。它们在功能上(如果不是结构上的话)是等价的。如果使用 Firebug 查看使用 创建的对象,则可以看到它们是相同的。但是,我的偏好如下。我猜它看起来更像是我在 C#/Java 中习惯的。也就是说,定义类,定义字段、构造函数和方法。new
var A = function() {};
A.prototype = {
_instance_var: 0,
initialize: function(v) { this._instance_var = v; },
x: function() { alert(this._instance_var); }
};
编辑并不是说变量的范围是私有的,我只是想说明我如何在 javascript 中定义我的类。变量名称已更改以反映这一点。
评论
initialize
x methods do not refer to the
A
this._instance_var
_instance_var
A
这些例子的结果截然不同。
在查看差异之前,应注意以下几点:
- 构造函数的原型提供了一种通过实例的私有属性在实例之间共享方法和值的方法。
[[Prototype]]
- 函数的 this 由函数的调用方式或使用 bind 设置(此处未讨论)。如果对对象(例如)调用函数,则方法中的函数引用该对象。如果调用或使用 bind 未设置此值,则默认为全局对象(浏览器中的窗口)或在严格模式下,保持未定义状态。
myObj.method()
- JavaScript 是一种面向对象的语言,即大多数值都是对象,包括函数。(字符串、数字和布尔值不是对象。
因此,以下是有问题的片段:
var A = function () {
this.x = function () {
//do something
};
};
在这种情况下,变量被赋值为一个值,该值是对函数的引用。当调用该函数时,使用 ,该函数的 this 不是由调用设置的,因此它默认为全局对象并且表达式有效。结果是将对右侧函数表达式的引用分配给 。A
A()
this.x
window.x
window.x
在以下情况下:
var A = function () { };
A.prototype.x = function () {
//do something
};
发生了一些非常不同的事情。在第一行中,变量被分配了对函数的引用。在 JavaScript 中,默认情况下,所有函数对象都有一个 prototype 属性,因此没有单独的代码来创建 A.prototype 对象。A
在第二行中,为 A.prototype.x 分配了对函数的引用。如果 x 属性不存在,这将创建一个 x 属性,如果存在,则分配一个新值。因此,与第一个示例的区别在于,表达式中涉及对象的 x 属性。
下面是另一个例子。它与第一个类似(也许是你想问的):
var A = new function () {
this.x = function () {
//do something
};
};
在此示例中,已在函数表达式之前添加了运算符,以便将函数作为构造函数调用。当调用 时,函数的 this 设置为引用一个新 Object,其私有属性设置为引用构造函数的公共原型。因此,在赋值语句中,将在此新对象上创建属性。当作为构造函数调用时,函数默认返回其 this 对象,因此不需要单独的语句。new
new
[[Prototype]]
x
return this;
要检查 A 是否具有 x 属性,请执行以下操作:
console.log(A.x) // function () {
// //do something
// };
这是 new 的不常见用法,因为引用构造函数的唯一方法是通过 A.constructor。更常见的做法是:
var A = function () {
this.x = function () {
//do something
};
};
var a = new A();
实现类似结果的另一种方法是使用立即调用的函数表达式:
var A = (function () {
this.x = function () {
//do something
};
}());
在本例中,分配了调用右侧函数的返回值。同样,由于未在调用中设置此值,因此它将引用全局对象并且有效。由于该函数不返回任何内容,因此其值为 。A
this.x
window.x
A
undefined
如果要将 Javascript 对象序列化为 JSON 或从 JSON 反序列化 Javascript 对象,这两种方法之间的这些差异也会显现出来。序列化对象时,不会序列化在对象原型上定义的方法,例如,当您只想序列化对象的数据部分而不是它的方法时,这会很方便:
var A = function () {
this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance));
// {"objectsOwnProperties":"are serialized"}
相关问题:
旁注:这两种方法之间可能不会节省任何显著的内存,但是使用原型共享方法和属性可能会比每个实例都有自己的副本使用更少的内存。
JavaScript 不是一种低级语言。将原型设计或其他继承模式视为显式更改内存分配方式的一种方式可能不是很有价值。
评论
null
prototype
new
"The language is functional"
你确定这就是功能的含义吗?
A
正如其他人所说,使用“this”会导致类 A 的每个实例都有自己独立的函数方法“x”副本。而使用“原型”意味着类 A 的每个实例都将使用方法“x”的相同副本。
下面是一些代码来显示这种微妙的差异:
// x is a method assigned to the object using "this"
var A = function () {
this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
this.x = function() { alert( value ); }
};
var a1 = new A();
var a2 = new A();
a1.x(); // Displays 'A'
a2.x(); // Also displays 'A'
a1.updateX('Z');
a1.x(); // Displays 'Z'
a2.x(); // Still displays 'A'
// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };
B.prototype.updateX = function( value ) {
B.prototype.x = function() { alert( value ); }
}
var b1 = new B();
var b2 = new B();
b1.x(); // Displays 'B'
b2.x(); // Also displays 'B'
b1.updateX('Y');
b1.x(); // Displays 'Y'
b2.x(); // Also displays 'Y' because by using prototype we have changed it for all instances
正如其他人所提到的,选择一种方法或另一种方法有多种原因。我的样本只是为了清楚地展示差异。
评论
this
this
Prototype 是类的模板;这适用于它的所有未来实例。而这是对象的特定实例。
使用 instead 的最终问题是,在重写方法时,基类的构造函数仍将引用被重写的方法。考虑一下:this
prototype
BaseClass = function() {
var text = null;
this.setText = function(value) {
text = value + " BaseClass!";
};
this.getText = function() {
return text;
};
this.setText("Hello"); // This always calls BaseClass.setText()
};
SubClass = function() {
// setText is not overridden yet,
// so the constructor calls the superclass' method
BaseClass.call(this);
// Keeping a reference to the superclass' method
var super_setText = this.setText;
// Overriding
this.setText = function(value) {
super_setText.call(this, "SubClass says: " + value);
};
};
SubClass.prototype = new BaseClass();
var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!
subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!
对:
BaseClass = function() {
this.setText("Hello"); // This calls the overridden method
};
BaseClass.prototype.setText = function(value) {
this.text = value + " BaseClass!";
};
BaseClass.prototype.getText = function() {
return this.text;
};
SubClass = function() {
// setText is already overridden, so this works as expected
BaseClass.call(this);
};
SubClass.prototype = new BaseClass();
SubClass.prototype.setText = function(value) {
BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};
var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!
如果你认为这不是问题,那么这取决于你是否能在没有私有变量的情况下生存,以及你是否有足够的经验在看到泄漏时知道泄漏。此外,必须将构造函数逻辑放在方法定义之后也很不方便。
var A = function (param1) {
var privateVar = null; // Private variable
// Calling this.setPrivateVar(param1) here would be an error
this.setPrivateVar = function (value) {
privateVar = value;
console.log("setPrivateVar value set to: " + value);
// param1 is still here, possible memory leak
console.log("setPrivateVar has param1: " + param1);
};
// The constructor logic starts here possibly after
// many lines of code that define methods
this.setPrivateVar(param1); // This is valid
};
var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0
a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0
对:
var A = function (param1) {
this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
this.publicVar = value; // No private variable
};
var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1
有什么区别?=>很多。
我认为,该版本用于启用封装,即数据隐藏。
它有助于操作私有变量。this
让我们看一下下面的例子:
var AdultPerson = function() {
var age;
this.setAge = function(val) {
// some housekeeping
age = val >= 18 && val;
};
this.getAge = function() {
return age;
};
this.isValid = function() {
return !!age;
};
};
现在,该结构可以按如下方式应用:prototype
不同的成年人有不同的年龄,但所有成年人都享有相同的权利。
因此,我们使用 prototype 而不是这个来添加它。
AdultPerson.prototype.getRights = function() {
// Should be valid
return this.isValid() && ['Booze', 'Drive'];
};
现在让我们看一下实现。
var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )
var p2 = new AdultPerson;
p2.setAge(45);
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***
希望这会有所帮助。
评论
正如其他答案中所讨论的,这实际上是一个性能考虑因素,因为原型中的函数与所有实例化共享,而不是为每个实例化创建的函数。
我整理了一个 jsperf 来证明这一点。实例化类所需的时间存在巨大差异,尽管它实际上仅在创建许多实例时才有意义。
http://jsperf.com/functions-in-constructor-vs-prototype
让我给你一个更全面的答案,这是我在 JavaScript 培训课程中学到的。
大多数答案已经提到了差异,即当原型设计与所有(未来)实例共享时。而在类中声明函数将为每个实例创建一个副本。
一般来说,没有对错之分,更多的是品味问题或设计决定,具体取决于您的要求。然而,原型是用于以面向对象的方式进行开发的技术,我希望你能在这个答案的末尾看到。
您在问题中展示了两种模式。我将尝试再解释两个,并尝试解释差异(如果相关)。随意编辑/扩展。 在所有示例中,它都是关于具有位置并且可以移动的汽车对象。
对象装饰器模式
不确定这种模式现在是否仍然适用,但它确实存在。很高兴知道这一点。 您只需将对象和属性传递给装饰器函数即可。装饰器返回具有属性和方法的对象。
var carlike = function(obj, loc) {
obj.loc = loc;
obj.move = function() {
obj.loc++;
};
return obj;
};
var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();
函数类
JavaScript 中的函数是一个专用对象。除了被调用之外,函数还可以像任何其他对象一样存储属性。
在本例中,是一个函数(也可以考虑对象),可以像您习惯的那样调用。它有一个属性(这是一个具有函数的对象)。调用时,该函数被调用,它执行了一些魔术,并使用其中定义的方法扩展函数(think 对象)。Car
methods
move
Car
extend
Car
methods
这个例子虽然不同,但最接近问题中的第一个例子。
var Car = function(loc) {
var obj = {loc: loc};
extend(obj, Car.methods);
return obj;
};
Car.methods = {
move : function() {
this.loc++;
}
};
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();
原型类
前两种模式允许讨论使用技术来定义共享方法或使用在构造函数主体中以内联方式定义的方法。在这两种情况下,每个实例都有自己的函数。move
原型模式并不适合进行相同的检查,因为通过原型委托进行功能共享是原型模式的目标。正如其他人指出的那样,预计它会有更好的内存占用。
但是,有一点很有趣:
每个对象都有一个便利属性,它指向它所附加的函数(think对象)。prototype
constructor
关于最后三行:
在此示例中,链接到对象,该对象通过链接到自身,即 是它自己。这使您可以确定哪个构造函数构建了某个对象。Car
prototype
constructor
Car
Car.prototype.constructor
Car
amy.constructor
的查找失败,因此被委托给 ,它具有构造函数属性。也是.Car.prototype
amy.constructor
Car
此外,是一个 .运算符的工作原理是查看右操作数的原型对象 () 是否可以在左操作数的原型 () 链中的任何位置找到。amy
instanceof
Car
instanceof
Car
amy
var Car = function(loc) {
var obj = Object.create(Car.prototype);
obj.loc = loc;
return obj;
};
Car.prototype.move = function() {
this.loc++;
};
var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();
console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);
一些开发人员在开始时可能会感到困惑。请参阅以下示例:
var Dog = function() {
return {legs: 4, bark: alert};
};
var fido = Dog();
console.log(fido instanceof Dog);
运算符返回 ,因为 的原型在原型链的任何地方都找不到。 是使用对象字面量创建的简单对象,即它只是委托给 。instanceof
false
Dog
fido
fido
Object.prototype
伪经典模式
这实际上只是简化形式的原型模式的另一种形式,例如,对于那些使用 Java 编程的人来说,它更熟悉,因为它使用构造函数。new
它实际上与原型模式中的作用相同,它只是原型模式之上的句法糖。
但是,主要区别在于,JavaScript 引擎中实现的优化仅在使用伪经典模式时适用。想想伪经典模式,一个可能更快的原型模式版本;两个示例中的对象关系是相同的。
var Car = function(loc) {
this.loc = loc;
};
Car.prototype.move = function() {
this.loc++;
};
var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();
最后,了解如何完成面向对象编程应该不会太难。有两个部分。
定义原型(链)中常见属性/方法的部分。
还有另一个部分,您可以在其中放置区分对象的定义(示例中的变量)。loc
这使我们能够在 JavaScript 中应用超类或子类等概念。
随意添加或编辑。再一次完成,我可以使它成为一个社区维基。
评论
以以下 2 个例子为例:
var A = function() { this.hey = function() { alert('from A') } };
与。
var A = function() {}
A.prototype.hey = function() { alert('from prototype') };
这里的大多数人(尤其是最受好评的答案)试图解释它们的不同之处,而没有解释为什么。我认为这是错误的,如果你先了解基本面,区别就会变得很明显。让我们先试着解释一下基本面......
a) 函数是 JavaScript 中的一个对象。JavaScript 中的每个对象都有一个内部属性(这意味着,你不能像其他属性一样访问它,除非在 Chrome 等浏览器中),通常被称为(你实际上可以在 Chrome 中输入它来查看它引用的内容。这只是一个属性,仅此而已。JavaScript 中的属性 = 对象内部的变量,仅此而已。变量有什么作用?他们指出事物。__proto__
anyObject.__proto__
那么这个属性指向什么呢?好吧,通常是另一个对象(我们稍后会解释原因)。强制属性的 JavaScript 不指向另一个对象的唯一方法是使用 .即使你这样做了,该属性仍然作为对象的属性存在,只是它不指向另一个对象,它指向 .__proto__
__proto__
var newObj = Object.create(null)
__proto__
null
这是大多数人感到困惑的地方:
当你在 JavaScript 中创建一个新函数(它也是一个对象,还记得吗?)时,当它被定义时,JavaScript 会自动在该函数上创建一个名为 .尝试一下:prototype
var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined
A.prototype
与酒店完全不同。在我们的示例中,“A”现在有两个属性,分别称为“prototype”和 。这对人们来说是一个很大的困惑。 属性没有任何关系,它们是指向不同值的独立事物。__proto__
__proto__
prototype
__proto__
您可能想知道:为什么 JavaScript 在每个对象上都创建了属性?嗯,一个词:授权。当你在一个对象上调用一个属性,而该对象没有它时,JavaScript 会查找所引用的对象,看看它是否可能有它。如果它没有它,那么它会查看该对象的属性,依此类推......直到链条结束。因此得名原型链。当然,如果不指向对象,而是指向 ,那么运气不好,JavaScript 会意识到这一点,并会返回您获取该属性。__proto__
__proto__
__proto__
__proto__
null
undefined
您可能还想知道,为什么 JavaScript 会在定义函数时创建为函数调用的属性?因为它试图愚弄你,是的,愚弄你,它像基于类的语言一样工作。prototype
让我们继续我们的示例,并创建一个“对象”:A
var a1 = new A();
当这件事发生时,后台发生了一些事情。 是一个普通变量,它被分配了一个新的空对象。a1
事实上,您在函数调用之前使用了运算符,这在后台执行了 ADDITIONAL 操作。关键字创建了一个现在引用的新对象,该对象为空。以下是另外发生的情况:new
A()
new
a1
我们说过,在每个函数定义上,都会创建一个名为“(您可以访问它,与属性不同)创建的新属性?好吧,现在正在使用该属性。prototype
__proto__
因此,我们现在正处于一个新鲜出炉的空物体的地步。我们说过 JavaScript 中的所有对象都有一个内部属性,它指向某物(也有它),无论是 null 还是另一个对象。运算符的作用是将该属性设置为指向函数的属性。再读一遍。基本上是这样的:a1
__proto__
a1
new
__proto__
prototype
a1.__proto__ = A.prototype;
我们说这只不过是一个空对象(除非我们在定义之前将其更改为其他内容)。所以现在基本上指向的就是那个空的物体。它们都指向发生此行时创建的同一对象:A.prototype
a1
a1.__proto__
A.prototype
A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}
现在,在处理语句时发生了另一件事。基本上被执行,如果 A 是这样的:var a1 = new A()
A()
var A = function() { this.hey = function() { alert('from A') } };
里面的所有东西都会被执行。当你到达这条线时,被更改为,你得到这个:function() { }
this.hey..
this
a1
a1.hey = function() { alert('from A') }
我不会介绍为什么会更改,但这是了解更多信息的一个很好的答案。this
a1
总而言之,当你这样做时,后台会发生 3 件事:var a1 = new A()
- 将创建一个全新的空对象并将其分配给 。
a1
a1 = {}
a1.__proto__
属性被指定指向与指向相同的事物(另一个空对象 {} )A.prototype
该函数在执行时设置为在步骤 1 中创建的新空对象(阅读我上面引用的答案,了解为什么更改为
A()
this
this
a1
)
现在,让我们尝试创建另一个对象:
var a2 = new A();
将重复步骤 1、2、3。你注意到什么了吗?关键词是重复。第 1 步:将是一个新的空对象,第 2 步:它的属性将指向与所指向的相同的东西,最重要的是,第 3 步:再次执行函数,这意味着将获得包含函数的属性。 并有两个 SEPARATE 属性,分别指向 2 个 SEPARATE 函数!我们现在在相同的两个不同的对象中有重复的函数,做同样的事情,哎呀......你可以想象,如果我们用 创建了 1000 个对象,那么这样做的内存含义是怎样的,毕竟所有函数声明都比数字 2 之类的东西占用更多的内存。那么我们如何防止这种情况发生呢?a2
__proto__
A.prototype
A()
a2
hey
a1
a2
hey
new A
还记得为什么每个对象上都存在该属性吗?因此,如果您检索 上的属性(不存在),将查询其属性,如果它是一个对象(并且大多数情况下是),它将检查它是否包含 ,如果没有,它将查询该对象的等。如果是这样,它将获取该属性值并将其显示给您。__proto__
yoMan
a1
__proto__
yoMan
__proto__
所以有人决定使用这个事实+当你创建时,它的属性指向同一个(空)对象指向并这样做:a1
__proto__
A.prototype
var A = function() {}
A.prototype.hey = function() { alert('from prototype') };
凉!现在,当您创建时,它再次完成上述所有 3 个步骤,并且在步骤 3 中,它不执行任何操作,因为没有要执行的内容。如果我们这样做:a1
function A()
a1.hey
它将看到它不包含,它将检查其属性对象以查看它是否具有它,情况就是这样。a1
hey
__proto__
通过这种方法,我们消除了步骤 3 中的部分,其中函数在每次创建新对象时都会重复。而不是拥有单独的财产,现在他们都没有它。我猜,你现在已经想通了。这是一件好事......如果你理解 和 ,像这样的问题将非常明显。a1
a2
hey
__proto__
Function.prototype
注意:有些人倾向于不将内部 Prototype 属性称为 ,我在帖子中使用了这个名称来清楚地将其与属性区分开来,这是两个不同的东西。__proto__
Functional.prototype
评论
__proto__
.prototype
每个对象都链接到一个原型对象。当尝试访问不存在的属性时,JavaScript 将在对象的原型对象中查找该属性,如果存在,则返回该属性。
函数构造函数的属性是指使用 时使用该函数创建的所有实例的原型对象。prototype
new
在第一个示例中,您将向使用该函数创建的每个实例添加一个属性。x
A
var A = function () {
this.x = function () {
//do something
};
};
var a = new A(); // constructor function gets executed
// newly created object gets an 'x' property
// which is a function
a.x(); // and can be called like this
在第二个示例中,您将向原型对象添加一个属性,所有创建的实例都指向该属性。A
var A = function () { };
A.prototype.x = function () {
//do something
};
var a = new A(); // constructor function gets executed
// which does nothing in this example
a.x(); // you are trying to access the 'x' property of an instance of 'A'
// which does not exist
// so JavaScript looks for that property in the prototype object
// that was defined using the 'prototype' property of the constructor
总之,在第一个示例中,将函数的副本分配给每个实例。在第二个示例中,所有实例共享函数的单个副本。
评论
我知道这已经死了,但我想展示一个速度差异的实际例子。
直接在对象上起作用:
function ExampleFn() {
this.print = function() {
console.log("Calling print! ");
}
}
var objects = [];
console.time('x');
for (let i = 0; i < 2000000; i++) {
objects.push(new ExampleFn());
}
console.timeEnd('x');
//x: 1151.960693359375ms
原型上的功能:
function ExampleFn() {
}
ExampleFn.prototype.print = function() {
console.log("Calling print!");
}
var objects = [];
console.time('y');
for (let i = 0; i < 2000000; i++) {
objects.push(new ExampleFn());
}
console.timeEnd('y');
//x: 617.866943359375ms
在这里,我们将使用 Chrome 中的方法创建 2,000,000 个新对象。我们将每个对象存储在一个数组中。穿上原型大约需要 1/2 的时间。print
print
评论
想想静态类型语言,on 的东西是静态的,而 的东西是与实例相关的。prototype
this
使用 prototype 时,该函数只会加载到内存中一次(与创建的对象数量无关),并且可以随时覆盖该函数。
所以在javascript构造函数中,当你在点运算符之前使用这个变量赋值时,你基本上是在为将要创建的对象的键赋值,如果你使用new关键字调用这个构造函数,如下图所示:
function foo(){
this.name="foo"
}
const obj=new foo()
console.log(obj)
将打印
{
name:"foo"
}
但是,如果您将任何属性分配给 foo 的原型键,如下所示:
function foo(){
this.name="foo"
}
foo.prototype.greet="Hi"
const obj=new foo()
console.log(obj)
它仍将打印:
{
name:"foo"
}
但是现在,如果您尝试通过 访问,它将打印“Hi”。发生的情况是,javascript 尝试在 obj
中查找 greet
键,但找不到。然后它看看 obj
指向哪个原型?它指向 foo
原型属性。然后 javascript 在 prototype 属性中查找 greet
键,它能够找到它。greet
obj
所以基本上,如果你创建一个构造函数的实例。 变量将从所有这些中访问。这被称为委托,感觉有点类似于 javascript 中的静态属性或继承。foo
greet
评论
a1.x !== a2.x
a1.x === a2.x