在JavaScript中使用“原型”与“this”?
有什么区别
var A = function () { this.x = function () { //do something }; };
和
var A = function () { }; A.prototype.x = function () { //do something };
这些例子有不同的结果。
在看差异之前,应该注意以下几点:
- 构造函数的原型提供了一种通过实例的private
[[Prototype]]
属性在实例之间共享方法和值的方法。 - 一个函数是通过函数的调用方式或者使用绑定来设置的 (这里没有讨论)。 在一个对象上调用一个函数的地方(例如
myObj.method()
),那么这个方法内的这个对象就引用了这个对象。 如果这不是通过调用或通过使用绑定来设置的,则默认为全局对象(浏览器中的窗口)或严格模式,但未定义。 - JavaScript是一种面向对象的语言,即一切都是一个对象,包括函数。
所以下面是这个片段:
var A = function () { this.x = function () { //do something }; };
在这种情况下,variablesA
被分配一个值作为一个函数的引用。 当使用A()
调用该函数时,该函数不是由调用设置的,所以它默认为全局对象,并且expression式this.x
有效地为window.x
。 结果是右边的函数expression式的引用被分配给了window.x
。
如果是:
var A = function () { }; A.prototype.x = function () { //do something };
发生了很大的不同。 在第一行中,variablesA
被赋予一个函数的引用。 在JavaScript中,所有的函数对象默认都有一个prototype属性,所以没有单独的代码来创buildA.prototype对象。
在第二行中, A.prototype.x被赋予一个函数的引用。 这将创build一个x属性,如果它不存在,或者分配一个新的值,如果它。 所以与第一个例子的区别是expression式中涉及哪个对象的x属性。
另一个例子如下。 这与第一个类似(可能是你想问的):
var A = new function () { this.x = function () { //do something }; };
在这个例子中, new
操作符已被添加到函数expression式之前,所以这个函数被作为构造函数调用。 当用new
调用时,函数的this被设置为引用一个新的Object,其私有[[Prototype]]
属性被设置为引用构造函数的公共原型 。 所以在赋值语句中,将在这个新对象上创buildx
属性。 当作为构造函数调用时,函数默认返回它的这个对象,所以不需要单独return this;
声明。
要检查A有一个x属性:
console.log(Ax) // function () { // //do something // };
这是新的不常见的用法,因为引用构造函数的唯一方法是通过A.constructor 。 这将是更常见的做法:
var A = function () { this.x = function () { //do something }; }; var a = new A();
实现类似结果的另一种方法是使用立即调用的函数expression式:
var A = (function () { this.x = function () { //do something }; }());
在这种情况下, A
分配了调用右侧函数的返回值。 在这里再一次,因为这不是在调用中设置的,它将引用全局对象,而this.x
实际上是window.x
。 由于该函数不返回任何东西,所以A
将具有undefined
的值。
这两种方法之间的差异,也performance出来,如果你序列化和反序列化你的JavaScript对象的JSON /从/。 在对象的原型中定义的方法在序列化对象时不会被序列化,例如当您想要序列化对象的数据部分,而不是序列化方法时,这可能很方便:
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是一个原型语言?
- JavaScript中的函数的范围是什么?
- “this”这个关键字是如何工作的?
旁注:这两种方法之间可能没有任何显着的内存节省,但是使用原型来共享方法和属性可能会比每个具有自己副本的实例使用更less的内存。
JavaScript不是低级语言。 将原型devise或其他inheritance模式视为明确改变内存分配方式的方式可能并不是很有价值。
正如其他人所说的第一个版本,使用“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
正如其他人所提到的,select一种方法有其各种原因。 我的样本只是为了清楚地表明差异。
以这两个例子:
var A = function() { this.hey = function() { alert('from A') } };
与
var A = function() {} A.prototype.hey = function() { alert('from prototype') };
这里的大多数人(特别是评分最高的答案)试图解释他们是如何不同的,而不解释为什么。 我认为这是错误的,如果你首先了解基本面,那么差异就会变得明显。 我们先试着解释基本面
a)函数是JavaScript中的一个对象。 JavaScript中的每个对象都会获得一个内部属性(也就是说,除了其他属性外,您可能无法访问它),通常称为anyObject.__proto__
(您可以在Chrome中实际inputanyObject.__proto__
来查看它引用的内容。这只是一个属性,没有其他的东西。JavaScript中的一个属性=对象内部的一个variables,仅此而已。variables做什么?它们指向事物。
那么这个__proto__
属性指向什么呢? 那么,通常是另一个对象(我们将解释为什么后面)。 强制JavaScript的__proto__
属性不指向另一个对象的唯一方法是使用var newObj = Object.create(null)
。 即使你这样做, __proto__
属性STILL作为对象的一个属性存在,只是它不指向另一个对象,它指向null
。
这是大多数人困惑的地方:
当你在JavaScript中创build一个新的函数(也是一个对象,请记住?),当它被定义的时候,JavaScript会自动在该函数上创build一个名为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在每个对象上都创build了__proto__
属性? 那么,一个字: 代表团 。 当你在一个对象上调用一个属性而对象没有这个属性的时候,JavaScript会寻找由__proto__
引用的对象来查看它是否有它。 如果没有它,那么它会查看该对象的__proto__
属性,等等…直到链结束。 因此名称原型链 。 当然,如果__proto__
没有指向一个对象,而是指向null
,好运气,JavaScript会意识到这一点,并将返回给您undefined
的属性。
你也许会想,为什么JavaScript在你定义函数的时候为函数创build一个名为prototype
的属性呢? 因为它试图欺骗你,是的愚弄你 ,它像基于类的语言。
让我们继续我们的例子,并从A
创build一个“对象”:
var a1 = new A();
发生这种事情时,背景中会发生一些事情。 a1
是一个普通variables,它被分配了一个新的空对象。
在函数调用A()
之前使用new
的事实在后台做了ADDITIONAL。 new
关键字创build了一个新的对象,现在引用a1
,该对象是空的。 以下是另外发生的事情:
我们说过,在每个函数定义中都有一个创build的名为prototype
的新属性(您可以访问它,与__proto__
属性不同)创build? 那么,这个属性现在正在使用。
所以我们现在正处于一个新鲜出炉的空的a1
对象。 我们说在JavaScript中的所有对象都有一个内部的__proto__
属性指向一些东西( a1
也有它),不pipe它是空还是另一个对象。 new
运算符所做的是将__proto__
属性设置为指向该函数的prototype
属性。 再读一遍。 基本上是这样的:
a1.__proto__ = A.prototype;
我们说A.prototype
是一个空的对象(除非我们在定义a1
之前把它改成别的东西)。 所以现在基本上a1.__proto__
指向同样的东西A.prototype
指向,那就是那个空的对象。 它们都指向这一行发生时创build的同一个对象:
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()`的时候,后台发生了三件事情:
- 一个全新的空对象被创build并分配给
a1
。a1 = {}
-
a1.__proto__
属性被指定为指向与A.prototype
指向相同的东西(另一个空对象{}) -
函数
A()
正在被执行,并将其设置为在步骤1中创build的新的空对象(请参阅上面引用的答案,为什么this
更改为a1
)
现在,我们尝试创build另一个对象:
var a2 = new A();
步骤1,2,3将重复。 你注意到了什么吗? 关键词是重复。 步骤1: a2
将是一个新的空对象,步骤2:它的__proto__
属性将指向相同的东西A.prototype
指向并且最重要的是,步骤3:函数A()
是继续执行的,这意味着a2
将得到hey
包含函数的属性。 a1
和a2
有两个SEPARATE属性,分别命名为hey
和2个SEPARATE函数! 我们现在在做同样的事情的相同的两个不同的对象中有重复的函数,oops …如果我们有1000个用new A
创build的对象,你可以想象这个内存的含义,在所有的函数声明占用更多的内存之后,比2那么我们如何防止呢?
请记住为什么__proto__
属性存在于每个对象上? 所以,如果你检索a1
(不存在)的yoMan
属性,将会查询它的__proto__
属性,如果它是一个对象(大多数情况下是这样),它将检查它是否包含yoMan
,如果它不会,它会查询该对象的__proto__
等如果它,它会采取该属性值,并显示给你。
所以有人决定使用这个事实+当你创builda1
,它的__proto__
属性指向相同的(空)对象。 A.prototype
指向并做到这一点:
var A = function() {} A.prototype.hey = function() { alert('from prototype') };
凉! 现在,当你创builda1
,它会再一次经历上面所有的3个步骤,并且在步骤3中它什么都不做,因为function A()
没有什么可执行的。 如果我们这样做:
a1.hey
它会看到, a1
不包含hey
,它会检查它的__proto__
属性对象,看看是否有它,这是事实。
通过这种方法,我们消除了步骤3中的function在每个新对象创build时被复制的部分。 而不是a1
和a2
有一个单独的hey
属性,现在没有他们拥有它。 我猜,你现在已经知道了。 这是好事…如果你理解__proto__
和Function.prototype
,像这样的问题将是非常明显的。
注意:有些人倾向于不把内部的Prototype属性作为__proto__
来调用,我已经通过这个post使用这个名字来明确地将它与Functional.prototype
属性区分为两个不同的东西。
在大多数情况下,它们基本上是相同的,但第二个版本保存内存,因为只有一个函数实例,而不是每个对象的单独函数。
使用第一种forms的理由是访问“私人成员”。 例如:
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的函数,但不在对象之外。
第一个示例仅更改该对象的接口。 第二个示例更改该类的所有对象的接口。
使用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
版本是用来启用封装,即数据隐藏。 它有助于操纵私有variables。
让我们看看下面的例子:
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 ***
希望这可以帮助。
每个对象都链接到一个原型对象。 当试图访问一个不存在的属性时,JavaScript会在该对象的原型对象中查找该属性,如果存在,则将其返回。
函数构造函数的prototype
属性是指在使用new
时使用该函数创build的所有实例的原型对象。
在你的第一个例子中,你正在为使用A
函数创build的每个实例添加一个属性x
。
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 ax(); // 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 ax(); // 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
总之,在第一个例子中,函数的副本被分配给每个实例 。 在第二个示例中,函数的单个副本由所有实例共享 。
让我给你一个在JavaScript培训课程中学到的更全面的答案。
大多数答案已经提到了这种差异,即当原型function与所有(未来)实例共享时。 而在类中声明该函数将为每个实例创build一个副本。
一般来说,没有对错,取决于您的要求,更多的是品味或devise决定。 然而,原型是用于以面向对象的方式进行开发的技术,我希望在这个答案的最后能够看到。
你在问题中显示了两种模式。 我将尝试解释两个,并尝试解释相关的差异。 随意编辑/扩展。 在所有例子中,它都是关于一个具有位置并且可以移动的汽车物体。
对象装饰模式
不知道这种模式现在是否仍然相关,但它是存在的。 这是很好的了解它。 您只需将一个对象和一个属性传递给装饰器函数。 装饰器返回具有属性和方法的对象。
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();
function类
JavaScript中的一个函数是一个专门的对象。 除了被调用之外,函数还可以像其他对象一样存储属性。
在这种情况下, Car
是一个函数 ( 也可以认为是对象 ),可以像以前那样调用它。 它有一个属性methods
(这是一个具有move
function的对象)。 当Car
被调用时, extend
函数被调用,这会产生一些魔力,并且用方法中定义的方法扩展Car
函数(思考对象)。
这个例子虽然不同,但与问题中的第一个例子最接近。
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
function。
原型模式并不适用于相同的检查,因为通过原型委派进行function共享是原型模式的目标。 正如其他人指出的那样,预计会有更好的内存占用。
然而,有一点值得注意:每个prototype
对象都有一个便利的属性constructor
,它指向它附加的函数(思考对象)。
关于最后三行:
在这个例子中, Car
链接到通过constructor
链接到Car
本身的prototype
对象,即Car.prototype.constructor
是Car
本身。 这可以让你找出哪个构造函数构build了一个特定的对象。
amy.constructor
的查找失败,因此委托给Car.prototype
,它具有构造函数属性。 所以amy.constructor
是Car
。
此外, amy
是Car
一个instanceof
。 instanceof
操作符通过查看右操作数的原型对象( Car
)是否可以在左操作数的原型( amy
)链中的任何位置find。
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
是一个用对象字面值创build的简单对象,即它只是代表Object.prototype
。
伪古典模式
这实际上只是简化forms的原型模式的另一种forms,对于那些使用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
variables)。
这允许我们在JavaScript中应用超类或子类的概念。
随意添加或编辑。 一旦更完整,我可以使这个社区维基也许。
原型是类的模板; 这适用于未来的所有情况。 而这是对象的特定实例。
我相信@Matthew Crumley是对的。 如果不是结构上的话,它们在function上是等同的。 如果使用Firebug查看使用new
创build的对象,则可以看到它们是相同的。 不过,我的偏好是以下几点。 我猜这只是看起来更像我习惯在C#/ Java中。 也就是说,定义类,定义字段,构造函数和方法。
var A = function() {}; A.prototype = { _instance_var: 0, initialize: function(v) { this._instance_var = v; }, x: function() { alert(this._instance_var); } };
编辑并不意味着该variables的范围是私人的,我只是想说明如何在JavaScript中定义我的类。 variables名已被更改以反映这一点。
As discussed in other answers, it's really a performance consideration because the function in the prototype is shared with all of the instantiations – rather than the function being created for each instantiation.
I put together a jsperf to show this. There is a dramatic difference in the time it takes to instantiate the class, although it is really only relevant if you are making many instances.
I know this has been answered to death but I'd like to show an actual example of speed differences.
Here we're creating 2,000,000 new objects with a print
method in Chrome. We're storing every object in an array. Putting print
on the prototype takes about 1/2 as long.