在 JavaScript 中使用“原型”与“这个”?

IT技术 javascript prototype this
2020-12-14 01:17:10

有什么区别

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

var A = function () { };
A.prototype.x = function () {
    //do something
};
6个回答

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

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

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

所以这里是有问题的片段:

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

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

如果是:

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

发生了非常不同的事情。在第一行,变量A被分配了一个对函数的引用。在 JavaScript 中,默认情况下所有函数对象都有一个原型属性,因此没有单独的代码来创建A.prototype对象。

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

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

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

在本例中,new运算符已添加到函数表达式之前,以便将函数作为构造函数调用。当调用 with 时new,函数的this被设置为引用一个新的 Object ,其私有[[Prototype]]属性被设置为引用构造函数的公共原型所以在赋值语句中,x属性将在这个新对象上创建。当作为构造函数调用时,函数默认返回其this对象,因此不需要单独的return 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分配调用右侧函数的返回值。再次,由于this没有在调用中设置,它将引用全局对象并且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 不是低级语言。将原型设计或其他继承模式视为显式更改内存分配方式的一种方式可能不是很有value。

"The language is functional" 你确定这是函数式的意思吗?
2021-02-09 01:17:10
这篇文章被误导了,并且混淆了如何设置的。正在重写。
2021-02-23 01:17:10
@keparo:你错了。每个对象都有一个 [内部] 原型对象(可以是null),但这与prototype属性有很大不同- 属性位于函数上,并且所有实例的原型在使用new. 不敢相信这真的得到了 87 票 :-(
2021-03-02 01:17:10
这个答案很奇怪,似乎完全没有抓住问题的重点。这个问题似乎是关于在构造函数与原型中定义类型属性的一个非常常见的问题,但一半的答案是关于如果你用作A函数会发生什么,另一半是关于模糊和非正统的方法直截了当的事情。
2021-03-06 01:17:10
我支持@Bergi 所说的原型。函数有一个原型属性。所有对象,包括函数,都有另一个内部属性,可以在某些浏览器中使用 Object.getPrototypeOf(myObject) 或 myObject.__proto__ 访问。属性指示在原型链中的对象的父(或对象从该对象继承)。原型属性(仅适用于函数)指示将成为使用该函数使用 new 关键字创建新对象的任何对象的父对象的对象。
2021-03-07 01:17:10

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

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

那是因为我的例子是错误的。只错了两年。叹。但这一点仍然有效。我用一个真正有效的例子更新了这个例子。谢谢你指出。
2021-02-07 01:17:10
是的...'prototype' 意味着静态或类级别 .. 它将被所有创建的实例共享...而 'this' 是一个实例方法,每个实例都有自己的副本
2021-02-15 01:17:10
这是一个静态方法!:D
2021-02-23 01:17:10
它不是静态的。在大多数面向对象语言中使用的静态意味着不依赖于this对象,它是方法的所有者。即该方法没有属于其所有者的对象。在这种情况下,有一个this对象,如示例中的类 A 所示。
2021-02-28 01:17:10
这是我期望发生的事情,但是当我像上面那样更改 Ax 后实例化一个新对象时,除非我像单例一样使用 A,否则我仍然显示“A”。jsbin.com/omida4/2/edit
2021-03-07 01:17:10

以这两个例子为例:

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的属性。

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

让我们继续我们的例子并从 中创建一个“对象” 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. 一个全新的空对象被创建并分配给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()再次执行,这意味着a2将获得hey包含一个函数的属性。a1并且a2有两个单独的属性命名hey,它们指向 2 个单独的函数!我们现在在相同的两个不同的对象中有重复的函数做同样的事情,oop...如果我们有 1000 个用 来创建的对象new A你可以想象这对内存的影响,毕竟函数声明比数字 2 占用更多的内存。所以我们如何防止这种情况?

还记得为什么__proto__属性存在于每个对象上吗?所以,如果你获取yoMan的财产a1(不存在),它的__proto__属性将进行磋商,而如果它是一个对象(而且是大多数情况下它是),它会检查它是否包含yoMan,如果没有,它将咨询该对象__proto__等。如果是,它将获取该属性值并将其显示给您。

所以有人决定使用这个事实 + 当你 create 时a1,它的__proto__属性指向同一个(空)对象A.prototype指向的事实,并这样做:

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

凉爽的!现在,当您 create 时a1,它再次执行上述所有 3 个步骤,在第 3 步中,它什么也不做,因为function A()没有任何东西可以执行。如果我们这样做:

a1.hey

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

通过这种方法,我们消除了步骤 3 中在每个新对象创建时复制函数的部分。相反的a1,并a2具有独立的hey财产,现在他们没有了它。我想,你现在明白了。这是一件好事...如果你理解__proto__Function.prototype,像这样的问题将是非常明显的。

注意:有些人倾向于不将内部 Prototype 属性称为__proto__,我在帖子中使用这个名称来清楚地将它与Functional.prototype属性区分为两个不同的东西。

更有趣的是原型方法比本地运行的对象属性方法稍微慢一些。总体而言,我不确定 javascript 是否应该用于编号超过 10k 的对象的数据操作,因此否定了基于潜在记忆效应更改方法的任何理由。那时,工作应该被卸载到服务器上。
2021-02-14 01:17:10
关键是__proto__.prototype是完全不同的事情。
2021-02-15 01:17:10
我不满足于仅仅给你一个赞……干得好!
2021-02-15 01:17:10
真正彻底和翔实的答案。我使用上面的对象结构(A.prototype.hey 与对象 this.hey)做了一些内存测试,并为每个创建了 1000 个实例。与原型相比,对象属性方法的内存占用大约大 100kb。然后我添加了另一个具有相同目的的函数,称为“傻”,它线性增加到 200kb。不显着,但也不是花生。
2021-02-18 01:17:10

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

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

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

@GFoley83 该答案并未表明-原型方法只能访问给定对象的“公共”属性。只有特权方法(不在原型上)才能访问私有成员。
2021-02-18 01:17:10
有关如何通过原型访问私有成员的示例,请参阅此帖子:stackoverflow.com/a/1441692/654708
2021-02-27 01:17:10

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

两者都将使该函数x可用于其原型被分配了 A 的新实例的所有对象:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
2021-03-01 01:17:10