两者之间有什么区别

var A = function () {
    this.x = function () {
        //do something
    };
};

and

var A = function () { };
A.prototype.x = function () {
    //do something
};

第一个示例仅更改该对象的接口。第二个示例更改该类的所有对象的接口。


在大多数情况下,它们本质上是相同的,但第二个版本节省了内存,因为只有一个函数实例,而不是每个对象都有一个单独的函数。

使用第一个表单的原因是访问“私人成员”。例如:

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的函数,但不能在对象外部使用。


我相信马修·克鲁姆利是对的。它们在功能上(如果不是结构上)是等效的。如果使用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); }
};

EDIT并不意味着变量的范围是私有的,我只是想说明如何用javascript定义类。变量名称已更改以反映这一点。


这些例子有非常不同的结果。

在研究差异之前,应注意以下几点:

构造函数的原型提供了一种通过实例的私有[[prototype]]属性在实例之间共享方法和值的方法。函数的this是通过函数的调用方式或使用bind(此处未讨论)来设置的。如果在对象上调用函数(例如myObj.method()),则方法中的函数将引用该对象。如果这不是通过调用或使用bind设置的,则默认为全局对象(浏览器中的窗口)或严格模式,保持未定义。JavaScript是一种面向对象的语言,即大多数值都是对象,包括函数。(字符串、数字和布尔值不是对象。)

以下是有问题的片段:

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调用时,函数的this被设置为引用一个新Object,该Object的私有[[Prototype]]属性被设置为参考构造函数的公共原型。因此,在赋值语句中,将在这个新对象上创建x属性。当作为构造函数调用时,函数默认返回其this对象,因此不需要单独返回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指定了调用右侧函数的返回值。在这里,由于在调用中未设置此项,因此它将引用全局对象,而此.x是有效的window.x。由于函数不返回任何内容,因此A的值将为undefined。

如果要将Javascript对象序列化为JSON或从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”关键字是如何工作的?

Sidenote:这两种方法之间可能没有任何显著的内存节省,但是使用原型共享方法和财产可能比每个拥有自己副本的实例使用更少的内存。

JavaScript不是低级语言。将原型设计或其他继承模式视为显式改变内存分配方式的一种方式可能不是很有价值。


正如其他人在第一个版本中所说的那样,使用“this”会导致类A的每个实例都有自己的函数方法“x”的独立副本。而使用“prototype”将意味着类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而不是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

有什么区别?=>很多。

我认为,这个版本用于实现封装,即数据隐藏。它有助于处理私有变量。

让我们看一下以下示例:

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;
  };
};

现在,原型结构可以应用如下:

不同的成年人有不同的年龄,但所有成年人都享有相同的权利。所以,我们使用原型而不是这个添加它。

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培训课程中学到的更全面的答案。

大多数答案已经提到了这一点,即当原型化时,功能与所有(未来)实例共享。而在类中声明函数将为每个实例创建一个副本。

总的来说,没有对错之分,更多的是品味或设计决定,取决于您的需求。然而,原型是用于以面向对象的方式进行开发的技术,我希望您会在答案的结尾看到。

你的问题显示了两种模式。我将尝试再解释两个,并尝试解释相关的差异。随意编辑/扩展。在所有示例中,它都是关于一个具有位置并可以移动的汽车对象。

对象装饰器图案

不确定这种模式现在是否仍然适用,但它确实存在。知道这一点很好。您只需将对象和属性传递给decorator函数。装饰器返回带有属性和方法的对象。

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是一个函数(也称为对象),可以按照您的习惯进行调用。它有一个属性方法(这是一个带有移动函数的对象)。当调用Car时,会调用extend函数,这会产生一些魔力,并使用方法中定义的方法扩展Car函数(think对象)。

这个例子虽然不同,但最接近问题中的第一个例子。

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();

原型类

前两种模式允许讨论使用技术来定义共享方法或使用在构造函数主体中内联定义的方法。在这两种情况下,每个实例都有自己的移动功能。

原型模式不适合同样的检查,因为通过原型委托共享功能是原型模式的目标。正如其他人所指出的,预计它的内存占用量会更好。

然而,有一点值得注意:每个原型对象都有一个方便的属性构造函数,它指向它所附加的函数(think对象)。

关于最后三行:

在本例中,Car链接到原型对象,该对象通过构造函数链接到Car本身,即Car.prototype.constructor是Car本身。这允许您确定哪个构造函数构建了某个对象。

amy.constructor的查找失败,因此被委托给Car.prototype,它确实具有构造函数属性。所以我的结构是Car。

此外,amy是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,因为在fido的原型链中的任何位置都找不到Dog的原型。fido是一个用对象文字创建的简单对象,即它只委托给object.prototype。

伪经典模式

这实际上只是简化形式的原型模式的另一种形式,对于用Java编程的人来说更为熟悉,因为它使用了新的构造函数。

它确实和原型模式一样,它只是原型模式的句法糖份。

然而,主要区别在于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中应用超类或子类等概念的原因。

可以随意添加或编辑。一旦完成,我可能会把它变成一个社区维基。


举两个例子:

var A = function() { this.hey = function() { alert('from A') } };

vs.

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

这里的大多数人(尤其是排名最高的答案)试图解释他们是如何不同的,而不解释为什么。我认为这是错误的,如果你首先了解基本面,差异就会变得明显。让我们先解释一下基本原理。。。

a) 函数是JavaScript中的一个对象。JavaScript中的每个对象都有一个内部属性(也就是说,你不能像访问其他财产一样访问它,除非是在像Chrome这样的浏览器中),通常被称为__proto__(你可以在Chrome中键入anyObject.__proto__来查看它引用了什么。这只是一个属性,仅此而已。JavaScript中的一个属性=对象内的一个变量,仅此之外。变量做什么?它们指向事物。

那么这个__proto__属性指向什么?嗯,通常是另一个对象(我们稍后会解释原因)。强制__proto__属性的JavaScript不指向另一个对象的唯一方法是使用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与__proto__属性完全不同。在我们的示例中,“A”现在有两个名为“prototype”和__proto__的财产。这对人们来说是一个很大的困惑。prototype和proto___财产没有任何关联,它们是指向不同值的不同事物。

您可能会想:为什么JavaScript在每个对象上都创建__proto__属性?嗯,一个词:授权。当你在一个对象上调用一个属性,而该对象没有属性时,JavaScript会查找__proto__引用的对象,看看它是否有属性。如果没有属性,则会查看该对象的__proto__属性,依此类推……直到链结束。因此,名称原型链。当然,如果__proto__没有指向一个对象,而是指向null,那么很不幸,JavaScript会意识到这一点,并返回未定义的属性。

您可能还想知道,当您定义函数时,为什么JavaScript会为函数创建一个名为prototype的属性?因为它试图欺骗你,是的,欺骗你它像基于类的语言一样工作。

让我们继续我们的示例,从A创建一个“对象”:

var a1 = new A();

当这件事发生时,背景中发生了一些事情。a1是一个普通变量,它被分配了一个新的空对象。

在函数调用a()之前使用了运算符new,这一事实在后台做了一些额外的事情。new关键字创建了一个新对象,该对象现在引用a1,并且该对象为空。以下是另外发生的情况:

我们说过,在每个函数定义上都创建了一个名为prototype的新属性(与__proto__属性不同,您可以访问它)?好吧,那处房产现在正在使用。

现在我们有一个新烘焙的空a1对象。我们说过JavaScript中的所有对象都有一个内部__proto__属性,该属性指向某个对象(a1也有它),无论它是null还是另一个对象。新运算符的作用是将__proto__属性设置为指向函数的原型属性。再读一遍。基本上是这样的:

a1.__proto__ = A.prototype;

我们说过,A.prototype只不过是一个空对象(除非我们在定义a1之前将其更改为其他对象)。所以现在基本上a1.__proto__指向的是A.prototype指向的同一个东西,那就是那个空对象。它们都指向发生此行时创建的同一对象:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

现在,在处理vara1=newA()语句时会发生另一件事。基本上执行A(),如果A是这样的:

var A = function() { this.hey = function() { alert('from A') } };

函数(){}中的所有内容都将执行。当你到达这条线时,这条线变为a1,你会得到:

a1.hey = function() { alert('from A') }

我不会解释为什么这会变为a1,但这是一个很好的答案,可以了解更多信息。

总之,当您执行var a1=new A()时,后台会发生三件事:

创建一个全新的空对象并将其分配给a1。a1={}a1.__proto__属性被指定指向与A.prototype指向相同的对象(另一个空对象{})正在执行函数A(),并将其设置为步骤1中创建的新的空对象(请阅读上面我提到的答案,了解为什么会更改为a1)

现在,让我们尝试创建另一个对象:

var a2 = new A();

将重复步骤1、2、3。你注意到什么了吗?关键词是重复。第一步:a2将是一个新的空对象,第二步:它的__proto__属性将指向a原型所指向的相同对象,最重要的是,第三步:函数a()被AGAIN执行,这意味着a2将获得包含函数的hey属性。a1和a2有两个名为hey的独立财产,它们指向两个独立函数!我们现在在相同的两个不同的对象中有重复的函数在做相同的事情,哦。。。如果我们用新的A创建了1000个对象,那么可以想象这对内存的影响,毕竟所有的函数声明都比数字2占用更多的内存。那么我们如何防止这种情况呢?

还记得为什么__proto__属性存在于每个对象上吗?因此,如果您检索a1上的yoMan属性(它不存在),将查询它的__proto__属性,如果它是一个对象(大多数情况下是),它将检查它是否包含yoMan,如果它不包含,它将查询该对象的__proto__etc.如果它包含,它会获取该属性值并将其显示给您。

所以有人决定使用这个事实+当你创建a1时,它的__proto__属性指向同一个(空)对象。prototype指向并执行以下操作:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

凉的现在,当您创建a1时,它会再次执行上面的所有3个步骤,在步骤3中,它不会执行任何操作,因为函数A()没有什么可执行的。如果我们这样做:

a1.hey

它将看到a1不包含hey,并将检查其__proto__属性对象,看看它是否包含hey。

使用这种方法,我们从步骤3中消除了在每次创建新对象时重复函数的部分。而不是a1和a2有一个单独的hey属性,现在它们都没有。这是件好事。。。如果你了解__proto__和Function.prototype,类似这样的问题将非常明显。

注意:有些人倾向于不将内部Prototype属性称为__proto__,我在文章中使用了这个名称,以将其与Functional.Prototype属性区分开来,这是两个不同的东西。


每个对象都链接到原型对象。当试图访问不存在的属性时,JavaScript将在对象的原型对象中查找该属性,如果该属性存在,则返回该属性。

函数构造函数的prototype属性引用使用new时使用该函数创建的所有实例的原型对象。


在第一个示例中,您将向使用a函数创建的每个实例添加属性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
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

总之,在第一个示例中,函数的副本被分配给每个实例。在第二个示例中,所有实例共享函数的单个副本。


我知道这已经得到了答案,但我想展示一个速度差异的实际例子。

直接作用于对象:

函数ExampleFn(){this.print=函数(){console.log(“正在调用打印!”);}}var对象=[];控制台时间('x');for(设i=0;i<2000000;i++){objects.push(new ExampleFn());}console.timeEnd('x');//x: 1151.960693359375毫秒

原型上的功能:

函数ExampleFn(){}ExampleFn.prototype.print=函数(){console.log(“正在调用打印!”);}var对象=[];控制台时间('y');for(设i=0;i<2000000;i++){objects.push(new ExampleFn());}console.timeEnd('y');//x: 617.866943359375毫秒

在这里,我们使用Chrome中的打印方法创建了2000000个新对象。我们将每个对象存储在一个数组中。在原型上打印大约需要1/2的时间。


想想静态类型的语言,原型上的东西是静态的,而这上面的东西是与实例相关的。


当您使用原型时,函数只会被加载到内存中一次(取决于您创建的对象数量),并且您可以随时重写该函数。