814

What's the difference between

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

and

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

15 回答 15

476

The examples have very different outcomes.

Before looking at the differences, the following should be noted:

  • A constructor's prototype provides a way to share methods and values among instances via the instance's private [[Prototype]] property.
  • A function's this is set by how the function is called or by the use of bind (not discussed here). Where a function is called on an object (e.g. myObj.method()) then this within the method references the object. Where this is not set by the call or by the use of bind, it defaults to the global object (window in a browser) or in strict mode, remains undefined.
  • JavaScript is an object-oriented language, i.e. most values are objects, including functions. (Strings, numbers, and booleans are not objects.)

So here are the snippets in question:

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

In this case, variable A is assigned a value that is a reference to a function. When that function is called using A(), the function's this isn't set by the call so it defaults to the global object and the expression this.x is effective window.x. The result is that a reference to the function expression on the right-hand side is assigned to window.x.

In the case of:

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

something very different occurs. In the first line, variable A is assigned a reference to a function. In JavaScript, all functions objects have a prototype property by default so there is no separate code to create an A.prototype object.

In the second line, A.prototype.x is assigned a reference to a function. This will create an x property if it doesn't exist, or assign a new value if it does. So the difference with the first example in which object's x property is involved in the expression.

Another example is below. It's similar to the first one (and maybe what you meant to ask about):

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

In this example, the new operator has been added before the function expression so that the function is called as a constructor. When called with new, the function's this is set to reference a new Object whose private [[Prototype]] property is set to reference the constructor's public prototype. So in the assignment statement, the x property will be created on this new object. When called as a constructor, a function returns its this object by default, so there is no need for a separate return this; statement.

To check that A has an x property:

console.log(A.x) // function () {
                 //   //do something
                 // };

This is an uncommon use of new since the only way to reference the constructor is via A.constructor. It would be much more common to do:

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

Another way of achieving a similar result is to use an immediately invoked function expression:

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

In this case, A assigned the return value of calling the function on the right-hand side. Here again, since this is not set in the call, it will reference the global object and this.x is effective window.x. Since the function doesn't return anything, A will have a value of undefined.

These differences between the two approaches also manifest if you're serializing and de-serializing your Javascript objects to/from JSON. Methods defined on an object's prototype are not serialized when you serialize the object, which can be convenient when for example you want to serialize just the data portions of an object, but not it's methods:

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

Related questions:

Sidenote: There may not be any significant memory savings between the two approaches, however using the prototype to share methods and properties will likely use less memory than each instance having its own copy.

JavaScript isn't a low-level language. It may not be very valuable to think of prototyping or other inheritance patterns as a way to explicitly change the way memory is allocated.

于 2008-11-22T05:26:52.203 回答
242

As others have said the first version, using "this" results in every instance of the class A having its own independent copy of function method "x". Whereas using "prototype" will mean that each instance of class A will use the same copy of method "x".

Here is some code to show this subtle difference:

// 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

As others have mentioned, there are various reasons to choose one method or the other. My sample is just meant to clearly demonstrate the difference.

于 2008-11-22T05:41:49.443 回答
168

举这两个例子:

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__属性仍然作为对象的属性存在,只是它不指向另一个对象,它指向null.

这是大多数人感到困惑的地方:

当您在 JavaScript 中创建一个新函数(它也是一个对象,还记得吗?)时,在定义它的那一刻,JavaScript 会自动在该函数上创建一个名为prototype. 试试看:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototype__proto__物业完全不同。在我们的示例中,“A”现在有两个属性,称为“原型”和__proto__. 这对人们来说是一个很大的困惑。prototype__proto__属性没有任何关系,它们是指向不同值的独立事物。

您可能想知道:为什么 JavaScript 会__proto__在每个对象上创建属性?好吧,一个词:代表团。当你调用一个对象的属性并且对象没有它时,JavaScript 会查找被引用的对象__proto__,看看它是否有它。如果它没有它,那么它会查看该对象的__proto__属性等等......直到链结束。因此得名原型链。当然,如果__proto__不是指向一个对象而是指向一个对象,那么null运气不好,JavaScript 会意识到这一点,并会为您返回undefined该属性。

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

让我们继续我们的示例并从以下内容创建一个“对象” 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 件事:

  1. 一个全新的空对象被创建并分配给a1.a1 = {}
  2. a1.__proto__属性被分配为指向与指向相同的事物A.prototype(另一个空对象 {} )

  3. 该函数A()正在执行,并this设置为在步骤 1 中创建的新的空对象(阅读我上面引用的关于为什么this更改为的答案a1

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

var a2 = new A();

步骤 1、2、3 将重复。你注意到什么了吗?关键词是重复。第 1 步:a2将是一个新的空对象,第 2 步:它的__proto__属性将指向同一个对象,A.prototype最重要的是,第 3 步:A()再次执行函数,这意味着a2将获得hey包含函数的属性。a1a2有两个单独的属性命名hey,它们指向 2 个单独的函数!我们现在在相同的两个不同对象中有重复的函数做同样的事情,哎呀......如果我们创建了 1000 个对象,您可以想象这对内存的影响new A,毕竟所有函数声明都比数字 2 占用更多的内存。所以我们如何防止这种情况发生?

还记得为什么__proto__属性存在于每个对象上吗?因此,如果您检索yoManon 上的属性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分为两个不同的属性。

于 2016-01-22T13:47:10.783 回答
60

In most cases they are essentially the same, but the second version saves memory because there is only one instance of the function instead of a separate function for each object.

A reason to use the first form is to access "private members". For example:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Because of javascript's scoping rules, private_var is available to the function assigned to this.x, but not outside the object.

于 2008-11-22T05:03:58.570 回答
28

The first example changes the interface for that object only. The second example changes the interface for all object of that class.

于 2008-11-22T04:43:28.000 回答
21

使用thisinstead of的最终问题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
于 2012-09-21T11:15:27.407 回答
21

每个对象都链接到一个原型对象。当试图访问一个不存在的属性时,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

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

于 2016-02-11T15:30:41.917 回答
16

有什么不同?=> 很多。

我认为,该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 ***

希望这可以帮助。

于 2014-01-06T06:13:34.873 回答
16

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

直接作用于对象:

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

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

于 2017-07-28T20:56:22.830 回答
14

原型是类的模板;这适用于它的所有未来实例。而这是对象的特定实例。

于 2008-11-22T06:16:02.543 回答
14

让我给你一个我在 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,是一个函数也可以认为是object),可以按照您习惯的方式调用。它有一个属性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对象,该对象链接constructorCar自身,即自身。这使您可以确定哪个构造函数构建了某个对象。Car.prototype.constructorCar

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任何地方都找不到' 的原型。是一个使用对象字面量创建的简单对象,即它只是委托给.fidofidoObject.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 中应用超类或子类等概念的原因。

随意添加或编辑。再完整一次,我也许可以把它变成一个社区维基。

于 2015-05-10T07:42:58.787 回答
11

I believe that @Matthew Crumley is right. They are functionally, if not structurally, equivalent. If you use Firebug to look at the objects that are created using new, you can see that they are the same. However, my preference would be the following. I'm guessing that it just seems more like what I'm used to in C#/Java. That is, define the class, define the fields, constructor, and methods.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

EDIT Didn't mean to imply that the scope of the variable was private, I was just trying to illustrate how I define my classes in javascript. Variable name has been changed to reflect this.

于 2008-11-22T05:11:46.680 回答
10

正如在其他答案中所讨论的,这实际上是一个性能考虑,因为原型中的函数与所有实例化共享 - 而不是为每个实例化创建函数。

我整理了一个 jsperf 来展示这一点。实例化类所需的时间有显着差异,尽管它只有在您创建许多实例时才真正相关。

http://jsperf.com/functions-in-constructor-vs-prototype

于 2014-08-14T18:18:30.073 回答
9

想想静态类型的语言,事物prototype是静态的,事物this是与实例相关的。

于 2017-12-19T01:13:04.063 回答
-1

当您使用原型时,该函数只会加载一次到内存中(与您创建的对象数量无关),您可以随时覆盖该函数。

于 2020-07-22T15:30:59.697 回答