在JavaScript中使用'prototype'与'this'?

时间:2008-11-22 04:39:50

标签: javascript prototype this

之间有什么区别
var A = function () {
    this.x = function () {
        //do something
    };
};

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

15 个答案:

答案 0 :(得分:450)

这些例子的结果截然不同。

在查看差异之前,应注意以下几点:

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

以下是有问题的片段:

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

在这种情况下,为变量A分配一个值,该值是对函数的引用。当使用A()调用该函数时,函数的 this 不会被调用设置,因此默认为全局对象,表达式this.x有效{{1} }。结果是对右侧的函数表达式的引用被分配给window.x

以下情况:

window.x
发生了一些非常不同的事情。在第一行中,变量var A = function () { }; A.prototype.x = function () { //do something }; 被赋予对函数的引用。在JavaScript中,默认情况下所有函数对象都具有 prototype 属性,因此没有单独的代码来创建 A.prototype 对象。

在第二行中,为 A.prototype.x 分配了对函数的引用。如果它不存在,这将创建 x 属性,如果不存在,则分配新值。因此,与表达式中涉及对象的 x 属性的第一个示例的区别。

另一个例子如下。它与第一个类似(也许你想问的是):

A

在此示例中,在函数表达式之前添加了var A = new function () { this.x = function () { //do something }; }; 运算符,以便将该函数作为构造函数调用。使用new调用时,函数的 this 设置为引用一个新的Object,其私有new属性设置为引用构造函数的public prototype 。因此,在赋值语句中,将在此新对象上创建[[Prototype]]属性。当作为构造函数调用时,函数默认返回其对象,因此不需要单独的x语句。

要检查 A 是否有 x 属性:

return this;

这是 new 的罕见用法,因为引用构造函数的唯一方法是通过 A.constructor 。这样做会更常见:

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

实现类似结果的另一种方法是使用立即调用的函数表达式:

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

如果您将Javascript对象序列化和反序列化为JSON,则这两种方法之间的差异也会显现出来。在序列化对象时,对象原型上定义的方法不是序列化的,这在例如您只想序列化对象的数据部分时会很方便,但不是它的方法:

undefined

相关问题

旁注:两种方法之间可能没有任何显着的内存节省,但是使用原型共享方法和属性可能比使用自己的副本的每个实例使用更少的内存。

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

答案 1 :(得分:229)

正如其他人所说的第一个版本一样,使用“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

正如其他人所提到的,选择一种方法或另一种方法有多种原因。我的样本只是为了清楚地展示其中的差异。

答案 2 :(得分:138)

采取以下两个例子:

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__(您实际上可以键入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__属性完全不同。在我们的示例中,&#39; A&#39;现在有两个名为&#39; prototype&#39;和__proto__。这对人们来说是一个很大的混乱。 prototype__proto__属性没有任何关联,它们将指向不同值的内容分开。

您可能想知道:为什么JavaScript在每个对象上都创建了__proto__属性?嗯,一句​​话:代表团。当您在对象上调用属性并且该对象没有它时,JavaScript会查找__proto__引用的对象以查看它是否具有该对象。如果它没有它,那么它会查看该对象的__proto__属性,依此类推......直到链结束。因此名称原型链。当然,如果__proto__没有指向某个对象,而是指向null,那么运气不错,JavaScript会意识到这一点,并会为您返回属性undefined

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

让我们继续我们的例子并创建一个&#34;对象&#34;超出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,但this is a great answer要了解更多内容。

总而言之,当你做var a1 = new A()时,后台发生了三件事:

  1. 创建一个全新的空对象并将其分配给a1a1 = {}
  2. a1.__proto__属性被指定为指向A.prototype指向的另一个属性(另一个空对象{})

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

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

    var a2 = new A();
    

    步骤1,2,3将重复。你注意到了什么吗?关键词是重复。第1步:a2将是一个新的空对象,第2步:其__proto__属性将指向相同的A.prototype点到最重要的是,第3步:函数A()已执行AGAIN,这意味着a2将获得包含函数的hey属性。 a1a2有两个名为hey的SEPARATE属性,指向2个SEPARATE函数!我们现在在相同的两个不同的对象中有重复的函数做同样的事情,oops ...如果我们有new A创建的1000个对象,你可以想象这个内存含义,在所有函数声明占用更多内存之后数字2.那么我们如何防止这种情况呢?

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

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

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

    酷!现在,当您创建a1时,它会再次执行上述所有3个步骤,而在步骤3中,它不会执行任何操作,因为function A()无需执行任何操作。如果我们这样做:

    a1.hey
    

    它会看到a1不包含hey,它会检查其__proto__属性对象,看它是否有它,就是这种情况。

    通过这种方法,我们消除了第3步中的部分,其中函数在每个新对象创建时都是重复的。而a1a2具有单独的hey属性,而现在没有属性。我想,你现在已经弄明白了。这很好......如果你理解__proto__Function.prototype,那么这些问题就会非常明显。

    注意:有些人倾向于不将内部Prototype属性称为__proto__,我通过帖子使用此名称将其与Functional.prototype属性区分为两个不同的东西。

答案 3 :(得分:57)

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

使用第一种形式的原因是访问“私人成员”。例如:

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的函数,但不在对象之外。

答案 4 :(得分:26)

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

答案 5 :(得分:20)

使用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

答案 6 :(得分:19)

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

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

答案 7 :(得分:15)

有什么区别? =&GT;很多。

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

希望这有帮助。

答案 8 :(得分:13)

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

答案 9 :(得分:12)

让我给你一个我在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();

Prototypal Classes

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

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

然而,有一点值得关注: 每个prototype对象都有一个便利属性constructor,它指向它附加到的函数(思考对象)。

关于最后三行:

在此示例中Car指向prototype对象的链接,该对象通过constructorCar本身相关联,即Car.prototype.constructor本身为Car 。这允许您找出构建特定对象的构造函数。

amy.constructor的查找失败,因此被委托给Car.prototype,它具有构造函数属性。因此amy.constructorCar

此外,amyinstanceof Carinstanceof运算符的工作原理是查看右操作数的原型对象(Car)是否可以在左操作数的原型(amy)链中找到。< / p>

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中应用超类或子类等概念。

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

答案 10 :(得分:12)

我知道这已被解决,但我想展示一个速度差异的实际例子。

Function directly on object

Function on prototype

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

答案 11 :(得分:10)

我相信@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中定义我的类。变量名称已更改以反映此情况。

答案 12 :(得分:9)

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

我把一个jsperf放在一起来表明这一点。实例化类所花费的时间差别很大,尽管只有在你创建很多实例时它才真正有用。

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

答案 13 :(得分:6)

考虑静态类型语言,prototype上的内容是静态的,this上的内容与实例相关。

答案 14 :(得分:-1)

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