在 JavaScript 中使用“prototype”与“this”?

Use of 'prototype' vs. 'this' in JavaScript?

提问人:sw234 提问时间:11/22/2008 最后编辑:CRABOLOsw234 更新时间:3/9/2023 访问量:129873

问:

有什么区别

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

var A = function () { };
A.prototype.x = function () {
    //do something
};
javascript 原型 这个

评论

23赞 Bergi 10/2/2012
相关:通过原型定义方法与在构造函数中使用它 - 真的是性能差异吗?,以及重复项的良好答案:在构造函数或原型上声明方法这与原型
0赞 AL-zami 9/11/2017
此处明确解释了 THIS 关键字的概念 scotch.io/@alZami/understanding-this-in-javascript
3赞 NoChance 10/3/2017
阅读“这个”线程可以看出 JS 是多么可怕,以及许多开发人员不清楚它的原理。更容易理解的语言到底有什么问题?我认为现在是开发人员大声疾呼的时候了,拒绝那些对业务或开发工作没有或几乎没有价值的令人困惑的技术。
1赞 Ruan Mendes 6/3/2018
在对象上: ;在原型上:a1.x !== a2.xa1.x === a2.x

答:

28赞 Glenn 11/22/2008 #1

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

评论

1赞 Spencer Williams 5/1/2016
两者都将使该函数可用于其原型被分配了 A 的新实例的所有对象:xfunction B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
60赞 Matthew Crumley 11/22/2008 #2

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

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

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

评论

1赞 GFoley83 3/12/2014
请参阅此帖子:stackoverflow.com/a/1441692/654708 有关如何通过原型访问私有成员的示例。
0赞 Alnitak 12/18/2015
@GFoley83该答案没有显示 - 原型方法只能访问给定对象的“公共”属性。只有特权方法(不在原型上)才能访问私有成员。
11赞 tvanfosson 11/22/2008 #3

我相信克拉姆利@Matthew是对的。它们在功能上(如果不是结构上的话)是等价的。如果使用 Firebug 查看使用 创建的对象,则可以看到它们是相同的。但是,我的偏好如下。我猜它看起来更像是我在 C#/Java 中习惯的。也就是说,定义类,定义字段、构造函数和方法。new

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

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

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

编辑并不是说变量的范围是私有的,我只是想说明我如何在 javascript 中定义我的类。变量名称已更改以反映这一点。

评论

2赞 Lekensteyn 4/9/2011
_instance_var与实例上的 and _instance_var' 属性一样,但与全局属性相同。如果要使用实例的属性,请使用。initializex methods do not refer to the Athis._instance_var_instance_varA
2赞 Lekensteyn 4/9/2011
有趣的是,本里也犯了这样的错误,两年后也被发现:p
479赞 29 revs, 12 users 61%keparo #4

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

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

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

因此,以下是有问题的片段:

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

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

在以下情况下:

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

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

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

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

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

在此示例中,已在函数表达式之前添加了运算符,以便将函数作为构造函数调用。当调用 时,函数的 this 设置为引用一个新 Object,其私有属性设置为引用构造函数的公共原型。因此,在赋值语句中,将在此新对象上创建属性。当作为构造函数调用时,函数默认返回其 this 对象,因此不需要单独的语句。newnew[[Prototype]]xreturn 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
    };
}());

在本例中,分配了调用右侧函数的返回值。同样,由于未在调用中设置值,因此它将引用全局对象并且有效。由于该函数不返回任何内容,因此其值为 。Athis.xwindow.xAundefined

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

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

评论

55赞 Bergi 9/19/2012
@keparo:你错了。每个对象都有一个 [内部] 原型对象(可以是 ),但这与属性有很大不同 - 属性位于函数上,所有实例的原型在构造时都设置为该对象。简直不敢相信这真的得到了 87 个赞:-(nullprototypenew
9赞 phant0m 9/26/2012
"The language is functional"你确定这就是功能的含义吗?
27赞 Jim Cooper 3/13/2013
我支持@Bergi关于原型的说法。函数具有 prototype 属性。所有对象(包括函数)都有另一个内部属性,可以使用 Object.getPrototypeOf(myObject) 或某些浏览器中的 myObject.__proto__ 访问该属性。proto 属性指示原型链中对象的父级(或此对象继承的对象)。prototype 属性(仅在函数上)指示对象,该对象将成为任何对象的父对象,这些对象利用该函数使用 new 关键字创建新对象。
14赞 RobG 2/5/2014
这篇文章被误导了,混淆了它是如何设置的。正在重写。
45赞 JLRishe 3/20/2014
这个答案很奇怪,似乎完全没有抓住问题的重点。这个问题似乎是一个非常常见的问题,即在构造函数和原型中定义类型属性,但一半的答案是关于如果你用作函数会发生什么,另一半是关于晦涩和非正统的方法来做一些简单的事情。A
249赞 Benry 11/22/2008 #5

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

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

评论

5赞 jellyfishtree 10/20/2010
这就是我所期望的,但是当我像上面一样更改 A.x 后实例化一个新对象时,我仍然显示“A”,除非我像单例一样使用 A。jsbin.com/omida4/2/edit
19赞 Benry 10/23/2010
那是因为我的例子是错误的。它只错了两年。叹息。但这一点仍然有效。我用一个实际有效的示例更新了示例。感谢您指出这一点。
4赞 5/5/2014
这是一个静态方法!:D
6赞 Aneer Geek 3/23/2015
是的。。。“原型”表示静态或类级别。这将由创建的所有实例共享...而“this”是一个实例方法,每个实例都有自己的副本
9赞 JimmyMcHoover 5/27/2015
它不是一成不变的。静态,正如大多数OO语言所使用的那样,意味着对对象没有依赖性,对象是方法的所有者。即该方法没有作为其所有者的对象。在本例中,有一个对象,如示例中的类 A 所示。thisthis
14赞 harropriiz 11/22/2008 #6

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

21赞 tarkabak 9/21/2012 #7

使用 instead 的最终问题是,在重写方法时,基类的构造函数仍将引用被重写的方法。考虑一下:thisprototype

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
17赞 oozzal 1/6/2014 #8

有什么区别?=>很多。

我认为,该版本用于启用封装,即数据隐藏。 它有助于操作私有变量。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

不同的成年人有不同的年龄,但所有成年人都享有相同的权利。
因此,我们使用 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 ***

希望这会有所帮助。

评论

3赞 yerforkferchips 7/31/2014
+1 与其他答案相比,答案不那么复杂,更生动。但是,在提供这些(好的)示例之前,您应该更详细地阐述一下。
1赞 NoChance 10/3/2017
我不确定“此版本用于启用封装,即数据隐藏”。如果函数内部的属性是使用 “this” 定义的,如 “this.myProperty=...” 中,则此类属性不是“私有”的,可以使用“new”从类外部的对象访问。
10赞 Devgr 8/15/2014 #9

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

我整理了一个 jsperf 来证明这一点。实例化类所需的时间存在巨大差异,尽管它实际上仅在创建许多实例时才有意义。

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

14赞 Ely 5/10/2015 #10

让我给你一个更全面的答案,这是我在 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 中的函数是一个专用对象。除了被调用之外,函数还可以像任何其他对象一样存储属性。

在本例中,是一个函数也可以考虑对象),可以像您习惯的那样调用。它有一个属性(这是一个具有函数的对象)。调用时,该函数被调用,它执行了一些魔术,并使用其中定义的方法扩展函数(think 对象)。CarmethodsmoveCarextendCarmethods

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

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

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

但是,有一点很有趣: 每个对象都有一个便利属性,它指向它所附加的函数(think对象)。prototypeconstructor

关于最后三行:

在此示例中,链接到对象,该对象通过链接到自身,即 是它自己。这使您可以确定哪个构造函数构建了某个对象。CarprototypeconstructorCarCar.prototype.constructorCar

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

此外,是一个 .运算符的工作原理是查看右操作数的原型对象 () 是否可以在左操作数的原型 () 链中的任何位置找到。amyinstanceofCarinstanceofCaramy

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

运算符返回 ,因为 的原型在原型链的任何地方都找不到。 是使用对象字面量创建的简单对象,即它只是委托给 。instanceoffalseDogfidofidoObject.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 中应用超类或子类等概念。

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

评论

0赞 Nick Pineda 3/22/2016
不是要敲一个非常彻底的帖子,但我认为 OO 和原型继承本质上是不同的思想流派。
0赞 Ely 3/22/2016
他们是,但人们可以用不同的技术/思想“做OO”,不是吗?
0赞 Nick Pineda 3/22/2016
真的不确定。许多人只是说原型哲学是不同的,许多人试图将其与OO进行比较,因为它是许多人习惯的思想流派。
0赞 Ely 3/22/2016
我的意思是,如果你想练习OO风格,并且语言提供了一套有助于这样做的技术,那不一定是错的。
175赞 daremkd 1/22/2016 #11

以以下 2 个例子为例:

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

与。

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

这里的大多数人(尤其是最受好评的答案)试图解释它们的不同之处,而没有解释为什么。我认为这是错误的,如果你先了解基本面,区别就会变得很明显。让我们先试着解释一下基本面......

a) 函数是 JavaScript 中的一个对象。JavaScript 中的每个对象都有一个内部属性(这意味着,你不能像其他属性一样访问它,除非在 Chrome 等浏览器中),通常被称为(你实际上可以在 Chrome 中输入它来查看它引用的内容。这只是一个属性,仅此而已。JavaScript 中的属性 = 对象内部的变量,仅此而已。变量有什么作用?他们指出事物。__proto__anyObject.__proto__

那么这个属性指向什么呢?好吧,通常是另一个对象(我们稍后会解释原因)。强制属性的 JavaScript 不指向另一个对象的唯一方法是使用 .即使你这样做了,该属性仍然作为对象的属性存在,只是它不指向另一个对象,它指向 .__proto____proto__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与酒店完全不同。在我们的示例中,“A”现在有两个属性,分别称为“prototype”和 。这对人们来说是一个很大的困惑。 属性没有任何关系,它们是指向不同值的独立事物。__proto____proto__prototype__proto__

您可能想知道:为什么 JavaScript 在每个对象上都创建了属性?嗯,一个词:授权。当你在一个对象上调用一个属性,而该对象没有它时,JavaScript 会查找所引用的对象,看看它是否可能有它。如果它没有它,那么它会查看该对象的属性,依此类推......直到链条结束。因此得名原型链。当然,如果不指向对象,而是指向 ,那么运气不好,JavaScript 会意识到这一点,并会返回您获取该属性。__proto____proto____proto____proto__nullundefined

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

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

var a1 = new A();

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

事实上,您在函数调用之前使用了运算符,这在后台执行了 ADDITIONAL 操作。关键字创建了一个现在引用的新对象,该对象为空。以下是另外发生的情况:newA()newa1

我们说过,在每个函数定义上,都会创建一个名为“(您可以访问它,与属性不同)创建的新属性?好吧,现在正在使用该属性。prototype__proto__

因此,我们现在正处于一个新鲜出炉的空物体的地步。我们说过 JavaScript 中的所有对象都有一个内部属性,它指向某物(也有它),无论是 null 还是另一个对象。运算符的作用是将该属性设置为指向函数的属性。再读一遍。基本上是这样的:a1__proto__a1new__proto__prototype

a1.__proto__ = A.prototype;

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

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

现在,在处理语句时发生了另一件事。基本上被执行,如果 A 是这样的:var a1 = new A()A()

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

里面的所有东西都会被执行。当你到达这条线时,被更改为,你得到这个:function() { }this.hey..thisa1

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

我不会介绍为什么会更改,但这是了解更多信息的一个很好的答案thisa1

总而言之,当你这样做时,后台会发生 3 件事:var a1 = new A()

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

  3. 该函数在执行时设置为在步骤 1 中创建的新空对象(阅读我上面引用的答案,了解为什么更改为A()thisthisa1)

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

var a2 = new A();

将重复步骤 1、2、3。你注意到什么了吗?关键词是重复。第 1 步:将是一个新的空对象,第 2 步:它的属性将指向与所指向的相同的东西,最重要的是,第 3 步:再次执行函数,这意味着将获得包含函数的属性。 并有两个 SEPARATE 属性,分别指向 2 个 SEPARATE 函数!我们现在在相同的两个不同的对象中有重复的函数,做同样的事情,哎呀......你可以想象,如果我们用 创建了 1000 个对象,那么这样做的内存含义是怎样的,毕竟所有函数声明都比数字 2 之类的东西占用更多的内存。那么我们如何防止这种情况发生呢?a2__proto__A.prototypeA()a2heya1a2heynew A

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

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

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

凉!现在,当您创建时,它再次完成上述所有 3 个步骤,并且在步骤 3 中,它不执行任何操作,因为没有要执行的内容。如果我们这样做:a1function A()

a1.hey

它将看到它不包含,它将检查其属性对象以查看它是否具有它,情况就是这样。a1hey__proto__

通过这种方法,我们消除了步骤 3 中的部分,其中函数在每次创建新对象时都会重复。而不是拥有单独的财产,现在他们都没有它。我猜,你现在已经想通了。这是一件好事......如果你理解 和 ,像这样的问题将非常明显。a1a2hey__proto__Function.prototype

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

评论

2赞 jookyone 8/3/2017
真正彻底和翔实的答案。我使用上面的对象结构(A.prototype.hey 与对象 this.hey)进行了一些内存测试,并为每个结构创建了 1000 个实例。与原型相比,对象属性方法的内存占用量大约增加了 100kb。然后,我添加了另一个具有相同目的的函数,称为“愚蠢”,它线性增加到 200kb。不重要,但也不是花生。
0赞 jookyone 8/3/2017
更有趣的是,prototype 方法比本地运行的 object 属性方法略慢。总的来说,我不确定是否应该使用 javascript 来操作数量超过 10k 的对象,因此否定了基于潜在内存效应改变方法的任何理由。此时,应将工作卸载到服务器上。
1赞 牛さん 12/19/2017
关键是完全不同的东西。__proto__.prototype
5赞 Kristianmitk 4/21/2018
我不满足于只给你一个赞成票......干的好!
0赞 Pravin W 4/28/2022
“非常感谢”解释这一点
22赞 pishpish 2/11/2016 #12

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

函数构造函数的属性是指使用 时使用该函数创建的所有实例的原型对象。prototypenew


在第一个示例中,您将向使用该函数创建的每个实例添加一个属性。xA

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

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

评论

1赞 Nick Pineda 3/22/2016
投票认为这是对这个问题最直截了当的答案。
1赞 Prince Vijay Pratap 3/27/2016
我喜欢你直截了当的方法!砰!
17赞 Arnav Aggarwal 7/29/2017 #13

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

直接在对象上起作用:

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

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

评论

2赞 frzsombor 11/11/2020
指出这一点干得好!但是,下次请不要插入代码的屏幕截图图像,而是复制和粘贴代码,以便其他人也可以轻松测试/使用它。这一次,我为您替换了图像。
10赞 牛さん 12/19/2017 #14

想想静态类型语言,on 的东西是静态的,而 的东西是与实例相关的。prototypethis

-1赞 Tiago Martins Peres 7/22/2020 #15

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

0赞 Ravi Verma 3/9/2023 #16

所以在javascript构造函数中,当你在点运算符之前使用这个变量赋值时,你基本上是在为将要创建的对象的键赋值,如果你使用new关键字调用这个构造函数,如下图所示:

function foo(){
this.name="foo"
}
const obj=new foo()
console.log(obj)

将打印

{
name:"foo"
}

但是,如果您将任何属性分配给 foo 的原型键,如下所示:

function foo(){
this.name="foo"
}
foo.prototype.greet="Hi"
const obj=new foo()
console.log(obj)

它仍将打印:

{
name:"foo"
}

但是现在,如果您尝试通过 访问,它将打印“Hi”。发生的情况是,javascript 尝试在 obj 中查找 greet 键,但找不到。然后它看看 obj 指向哪个原型?它指向 foo 原型属性。然后 javascript 在 prototype 属性中查找 greet 键,它能够找到它。greetobj

所以基本上,如果你创建一个构造函数的实例。 变量将从所有这些中访问。这被称为委托,感觉有点类似于 javascript 中的静态属性或继承。foogreet