协慌网

登录 贡献 社区

在 JavaScript 中使用'prototype' 与'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
    };
};

在这种情况下,为变量A分配一个值,该值是对函数的引用。当该功能是使用称为A()函数的不是由呼叫设定成默认为全局对象和表达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属性涉及表达式。

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

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

在此示例中,在函数表达式之前添加了new运算符,以便将该函数作为构造函数调用。当所谓的new的功能的,这是设置为引用一个新的对象,其私人[[Prototype]]属性被设置为参考构造函数的原型公众。因此在赋值语句中,将在此新对象上创建x属性。当被称为构造函数,函数返回它在默认情况下这个对象,所以没有需要单独return this;声明。

要检查A是否具有x属性:

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

这是一个不常见的用法,因为引用构造函数的唯一方法是通过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

如果您将 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 不是一种低级语言。将原型设计或其他继承模式视为明确更改内存分配方式的方法可能不是很有价值。

正如其他人所说的那样,使用 “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

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

拿这两个例子:

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__属性指向什么?好吧,通常是另一个对象(我们将在后面解释原因)。强制 JavaScript 为__proto__属性指向另一个对象的唯一方法是使用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的属性。

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

让我们继续我们的例子,创建一个 “对象” 出来的A

var a1 = new A();

当事情发生时,背景中会发生一些事情。 a1是一个普通变量,它被赋予一个新的空对象。

在函数调用A()之前使用 operator new的事实在后台执行了一些 ADDITIONAL。 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. 创建一个全新的空对象并将其分配给a1a1 = {}
  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()是 AGAIN 执行的,这意味着a2会得到hey包含函数的属性。 a1a2有两个名为hey SEPARATE 属性,指向 2 个 SEPARATE 函数!我们现在在相同的两个不同的对象中有重复的函数做同样的事情,oops ... 如果我们有 1000 个使用new A创建的对象,你可以想象这个内存的含义,所有函数声明占用的内存比数字 2 更多那么我们如何防止这种情况呢?

还记得为什么每个对象都存在__proto__属性吗?因此,如果您在a1 (不存在)上检索yoMan属性,将查询其__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 中的部分,其中在每个新对象创建时复制了函数。而不是a1a2具有单独的hey属性,现在没有它有它。我想,你现在已经弄明白了。这是件好事...... 如果您理解__proto__Function.prototype ,那么这些问题就会非常明显。

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