之间有什么区别
var A = function () {
this.x = function () {
//do something
};
};
和
var A = function () { };
A.prototype.x = function () {
//do something
};
a1.x !== a2.x
; 关于原型:a1.x === a2.x
之间有什么区别
var A = function () {
this.x = function () {
//do something
};
};
和
var A = function () { };
A.prototype.x = function () {
//do something
};
a1.x !== a2.x
; 关于原型:a1.x === a2.x
Answers:
这些示例具有截然不同的结果。
在查看差异之前,应注意以下几点:
[[Prototype]]
属性在实例之间共享方法和值的方法。myObj.method()
),那么该方法引用内的对象。当这种不被电话或通过使用设置绑定,则默认为全局对象(窗口浏览器),或者在严格模式下,仍然不确定。因此,以下是这些片段:
var A = function () {
this.x = function () {
//do something
};
};
在这种情况下,将为变量A
分配一个值,该值是对函数的引用。当使用函数调用A()
该函数时,该函数的this不会由调用设置,因此它默认为全局对象,并且表达式this.x
有效window.x
。结果是将对右侧函数表达式的引用分配给window.x
。
如果是:
var A = function () { };
A.prototype.x = function () {
//do something
};
发生了非常不同的事情。在第一行中,为变量A
分配了对函数的引用。在JavaScript中,默认情况下,所有函数对象都具有prototype属性,因此没有单独的代码来创建A.prototype对象。
在第二行中,为A.prototype.x分配了对函数的引用。如果x属性不存在,则将创建一个x属性;如果不存在,则将分配一个新值。因此,与第一个示例(其中对象的x属性涉及表达式)的区别。
下面是另一个示例。它与第一个类似(也许您想问什么):
var A = new function () {
this.x = function () {
//do something
};
};
在此示例中,new
运算符已添加到函数表达式之前,因此该函数被称为构造函数。当使用调用时new
,该函数的this设置为引用一个新的Object,该对象的private [[Prototype]]
属性设置为引用构造函数的public 原型。因此,在赋值语句中,x
将在此新对象上创建属性。当作为构造函数调用时,函数默认情况下返回其this对象,因此不需要单独的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
。
如果要在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
。不敢相信这真的有87个投票:-(
"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
对象,如示例中的A类所示。
举两个例子:
var A = function() { this.hey = function() { alert('from A') } };
与
var A = function() {}
A.prototype.hey = function() { alert('from prototype') };
这里的大多数人(尤其是评分最高的答案)试图在不解释原因的情况下解释他们的不同之处。我认为这是错误的,如果您先了解基本知识,差异就会变得明显。让我们先解释一下基本原理...
a)函数是JavaScript中的对象。JavaScript中的每个对象都有一个内部属性(意思是,您不能像其他属性一样访问它,除了可能在Chrome之类的浏览器中),通常被称为__proto__
(您可以实际上键入anyObject.__proto__
Chrome以查看其引用的内容。 ,一个属性,仅此而已; JavaScript中的一个属性=对象内的一个变量,仅此而已,变量的作用是什么?它们指向事物。
那么这个__proto__
属性指向什么呢?好吧,通常是另一个对象(稍后我们将解释原因)。强制该__proto__
属性的JavaScript 不能指向另一个对象的唯一方法是使用var newObj = Object.create(null)
。即使执行此操作,__proto__
属性STILL仍作为对象的属性存在,只是它没有指向另一个对象,而是指向null
。
这是大多数人感到困惑的地方:
当您在JavaScript中创建一个新函数时(它也是一个对象,还记得吗?),在定义它的那一刻,JavaScript会自动在该函数上创建一个名为的新属性prototype
。尝试一下:
var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined
A.prototype
与该__proto__
属性完全不同。在我们的示例中,“ A”现在具有两个称为“ prototype”和“”的属性__proto__
。这对人们来说是一个很大的困惑。prototype
和__proto__
属性绝不相关,它们是指向不同值的独立事物。
您可能会想:为什么JavaScript __proto__
在每个对象上都创建了属性?好吧,一个字:委派。当您在对象上调用属性而该对象没有属性时,JavaScript会查找所引用的对象__proto__
以查看它是否具有该属性。如果没有它,那么它将查看该对象的__proto__
属性,依此类推...直到链结束。因此命名原型链。当然,如果__proto__
没有指向对象而是指向null
,那么运气很好,JavaScript会意识到这一点,并将undefined
为您返回该属性。
您可能还想知道,为什么prototype
在定义函数时JavaScript会创建一个为函数调用的属性?因为它试图欺骗您,所以可以欺骗您,它就像基于类的语言一样工作。
让我们继续我们的示例,并从中创建一个“对象” A
:
var a1 = new A();
当事情发生时,背景中发生了一些事情。a1
是分配了一个新的空对象的普通变量。
new
在函数调用之前使用运算符的事实A()
在后台做了一些其他操作。该new
关键字创建一个新的对象现在引用a1
和对象为空。这是另外发生的事情:
我们说过,在每个函数定义上都创建了一个称为prototype
(您可以访问它,与该__proto__
属性不同)的新属性吗?好吧,该属性现在正在使用中。
因此,现在我们有了一个新鲜出炉的空a1
对象。我们说过,JavaScript中的所有对象都有一个内部__proto__
属性,该属性指向某个东西(a1
也有它),无论它是null还是另一个对象。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 {}
现在,在var a1 = new A()
处理语句时发生了另一件事。基本上A()
被执行,如果A是这样的:
var A = function() { this.hey = function() { alert('from A') } };
内部所有的东西function() { }
都将执行。当您到达该this.hey..
行时,this
将更改为a1
,您将获得以下信息:
a1.hey = function() { alert('from A') }
我不会介绍为什么this
更改为,a1
但这是了解更多信息的好答案。
综上所述,当您这样做时var a1 = new A()
,后台会发生3件事:
a1
。a1 = {}
a1.__proto__
属性分配为指向与指向相同的事物A.prototype
(另一个空对象{})
A()
执行该函数时,将其this
设置为在步骤1中创建的新的空对象(请阅读我上面提到的有关为什么this
更改为的答案a1
)
现在,让我们尝试创建另一个对象:
var a2 = new A();
重复步骤1,2,3。你有注意到吗?关键字是重复。步骤1:a2
将是一个新的空对象,步骤2:其__proto__
属性将指向相同的对象,A.prototype
最重要的是,步骤3:A()
将再次执行函数,这意味着a2
将获得hey
包含函数的属性。a1
并a2
具有两个SEPARATE属性hey
,它们分别指向2个SEPARATE函数!现在,我们在同一对象的两个不同对象中有重复的函数,哎呀...如果我们用创建的1000个对象new A
,在所有函数声明都比数字2占用更多的内存之后,您可以想象它的内存含义。我们如何防止这种情况?
还记得为什么该__proto__
属性存在于每个对象上吗?因此,如果您在yoMan
上检索该属性a1
(不存在),__proto__
则将查询该属性,如果它是一个对象(在大多数情况下是对象),它将检查它是否包含yoMan
,如果不包含,它将查询该对象的__proto__
等。如果是,它将获取该属性值并将其显示给您。
因此,有人决定使用以下事实+当您创建时a1
,其__proto__
属性指向同一(空)对象A.prototype
指向并执行以下操作:
var A = function() {}
A.prototype.hey = function() { alert('from prototype') };
凉!现在,当您创建时a1
,它再次经历了以上所有3个步骤,而在步骤3中,它什么也没做,因为function A()
没有什么要执行的。如果我们这样做:
a1.hey
它将看到其中a1
不包含hey
该__proto__
对象,并且将检查其属性对象以查看是否包含它(是这种情况)。
使用这种方法,我们从第3步中删除了在每次创建新对象时都会复制功能的部分。相反的a1
,并a2
具有独立的hey
财产,现在他们没有了它。我想您到现在就知道了。很好。如果您理解__proto__
和Function.prototype
,类似这样的问题将非常明显。
注意:某些人倾向于不将内部Prototype属性称为__proto__
,我在帖子中使用了此名称,以将其与Functional.prototype
属性区别开来是两个不同的东西。
__proto__
并且.prototype
是完全不同的事物。
在大多数情况下,它们基本上是相同的,但是第二个版本可以节省内存,因为该函数只有一个实例,而不是每个对象都有单独的函数。
使用第一种形式的原因是访问“私人成员”。例如:
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的函数,但不适用于该对象。
第一个示例仅更改该对象的接口。第二个示例更改该类所有对象的接口。
x
可用于为其原型分配了A的新实例的所有对象:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
使用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
每个对象都链接到原型对象。当尝试访问不存在的属性时,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
总之,在第一个示例中,该函数的副本已分配给每个实例。在第二个示例中,所有实例共享该函数的单个副本。
有什么不同?=>很多。
我认为,该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
结构可以应用如下:
不同的成年人有不同的年龄,但是所有成年人都享有相同的权利。
因此,我们使用原型而非它添加它。
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 ***
希望这可以帮助。
我知道这已经死了,但我想展示一个实际的速度差异示例。
在这里,我们正在使用print
Chrome中的方法创建2,000,000个新对象。我们将每个对象存储在一个数组中。放置print
原型大约需要1/2倍的时间。
让我给您一个我在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中的函数是专门的对象。除了被调用外,函数还可以像存储其他任何对象一样存储属性。
在这种情况下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
功能。
原型模式不太适合进行相同的检查,因为通过原型委托进行功能共享是原型模式的真正目标。正如其他人指出的那样,它有望具有更好的内存占用量。
但是,有一点很有趣,您应该知道:每个prototype
对象都有一个便捷属性constructor
,它指向它附带的函数(认为对象)。
关于最后三行:
在这个例子中Car
链接到prototype
对象,它经由链路constructor
到Car
本身,即Car.prototype.constructor
是Car
本身。这使您能够确定哪个构造函数构造了某个对象。
amy.constructor
的查找失败,因此被委托给Car.prototype
,后者确实具有Constructor属性。所以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中应用超类或子类之类的概念的原因。
随时添加或编辑。再完成一次,我可以使它成为社区Wiki。
我相信@Matthew Crumley是正确的。它们在功能上(如果不是在结构上)是等效的。如果使用Firebug查看使用创建的对象,则new
可以看到它们是相同的。但是,我的偏好如下。我猜想这似乎更像是我在C#/ Java中所习惯的。即,定义类,定义字段,构造函数和方法。
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
上_instance_var`财产A
情况,但全球性的。使用this._instance_var
,如果你打算使用_instance_var
的财产A
情况。
正如在其他答案中所讨论的那样,这实际上是性能方面的考虑,因为原型中的函数与所有实例共享,而不是为每个实例创建函数。
我整理了一个jsperf来展示这一点。实例化类的时间有很大的不同,尽管实际上只有在创建多个实例时才有意义。