【问题标题】:Is there any benefit to using prototype instead of declaring properties on the object itself?使用原型而不是在对象本身上声明属性有什么好处吗?
【发布时间】:2012-07-02 13:24:34
【问题描述】:

原型用于声明一类对象的属性和方法。使用原型的一个优点是它可以节省内存,因为类的所有实例都指向原型的属性和方法,这样可以节省内存并有效地允许类的所有实例将属性视为静态。

原型用于通过原型链进行继承。

我的问题很简单。既然可以做到,为什么还要使用原型:

function car() {
    this.engine = "v8";
}
function mustang() {
    // nm, no good way to inherit without using prototypes
}

对吗?所以原型的主要目的有三个:

  1. 节省内存
  2. 提供静态属性
  3. 是引用类型从超类继承的唯一方法

【问题讨论】:

  • 我不确定你在问什么......你似乎在说“你可以做......”,但是在那个脚本的评论中你说“没有好办法[去做]”。这不是自相矛盾吗?
  • 我想他在问这个问题的过程中意识到他的想法是错误的。无论如何,自从他 18 个月前发布问题以来,他就没有上过 stackexchange,所以没有必要解决他。是我还在想……
  • 如果你不使用原型你可以做 function mustang() { car​​.apply(this); }

标签: javascript ecma262


【解决方案1】:

节省内存

是的,当您创建数百个 Car 实例并且它们都有自己的函数(具有自己的闭包范围)时,您将消耗更多内存。

找不到它的参考,但有人建议 Chrome 优化使用原型的构造函数,而不是使用构造函数主体中的所有内容的构造函数。

提供静态属性

静态更像Date.now(),每个实例都有原型中的成员,但可以在实例上调用。

是引用类型从超类继承的唯一方式

您可以在 Child 中使用 Parent.apply(this,arguments); 继承,但这会使扩展 Parent 函数更加复杂,并且不会使 childInstance instanceof Parent 为真。该代码所做的是将要创建的子实例作为调用对象 (this) 运行父代码。继承通常在两个地方完成。

  1. 在Child body Parent.apply(this,arguments); 中重新使用Parent 初始化代码,并使Parent 实例成员成为Child 实例成员(例如:this.name)。
  2. 将 Child.prototype 设置为 Parent.prototype Child.prototype=Object.create(Parent.prototype);Child.prototype.constructor=Child; 的浅拷贝这将确保共享 Parent 成员在 Child 实例上可用(如函数 getName)。

这些点在这里有更详细的解释:https://stackoverflow.com/a/16063711/1641941

【讨论】:

    【解决方案2】:

    关于你的三点:

    1. 原型不一定具有更高的性能,尤其是对于变长或包含许多成员的原型链。原型越小,链越短,浏览器的编译器就越能优化它。最终,需要针对各个应用程序、他们的个人需求以及目标浏览器(其性能差异很大)提出这个问题。
    2. 根据定义,静态成员需要对象。也就是说,静态成员属于对象本身而不是特定实例。对象是在 JavaScript 中创建静态属性的唯一方法。请注意,作为“特殊”对象的对象字面量本质上是静态的。
    3. 可以实现自己的对象类型,允许继承之类的东西(即jQuery.extend),但就引用类型而言,原型是创建继承的唯一方法。

    【讨论】:

    • jspref 不是显示原型比其他的更好吗?只有 Chrome 29 和 30 大致相同。这是关于每秒的操作数,不显示内存消耗。在第 3 点;让someEmployer instanceof Person 为真的唯一方法是使用原型
    • HMR,感谢您了解这一点——这对我来说是一个糟糕的例子。我编辑了答案来解释原因。
    • 是的,developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/… 在调用原型链上层方法的循环中可能会对性能产生负面影响,您可能会注意到这一点。
    【解决方案3】:

    原型设计远不止这些。 您还可以在运行时使用方法和属性扩展类和已经存在的对象实例。

    这应该用一种非常容易理解的方式来解释它:http://javascript.info/tutorial/inheritance

    【讨论】:

      【解决方案4】:

      如果您关心遵循约定以便人们(以及您以后)真正理解您的代码,则不应将this.engine="v8" 放在构造函数中。 Prototype 旨在为每辆汽车定义属性,而构造函数旨在定义单个实例。那么,为什么要在构造函数中为每个 smack dab 实例添加正确的东西呢?这属于原型。即使做这两件事最终会完成同样的事情,也有一些事情要放在适当的位置上。您和其他人都可以理解您的代码。

      【讨论】:

        【解决方案5】:

        关于你的观点:

        1. 肯定会提高性能,尤其是在函数方面 - 在原型上声明函数要好得多。
        2. 我认为您的意思是说“公共”属性,以便通过编写some_instance.foo 来检索信息。 “静态”属性/方法不同(见下文)。
        3. 正确。继承只能从原型中真正发生。

        让我解释一些事情,看看这是否有帮助。在 javascript 中创建新的“类”是一个相当简单的过程。

        var MyClass = new Function();
        

        此时,引擎知道您的新类,并在创建“类”的新实例时知道“要做什么”(就性能而言)。

        var my_instance = new MyClass();
        

        如果您想修改原型,您可以这样做并且知道每个实例都将得到更新,因为它们都共享相同的原型。

        MyClass.prototype.name = "default name";
        console.log( my_instance.name ); //=> default name
        

        现在引擎知道有一个“名称”属性需要一个字符串值...它将这些资源分配给您的类的所有新实例和现有实例...这非常便利。请注意,像这样修改现有“类”的原型是一个昂贵的过程,不应频繁执行(但也不要害怕这样做)。

        我不能真正谈论在实例上声明临时属性/方法的性能利弊:

        my_instance.foo = function() { /* this is not in the prototype chain */ };
        

        我的猜测是,这对引擎来说非常简单,除非您同时对数以万计的对象执行此操作,否则没什么大不了的。

        使用原型 IMO 的主要好处是您可以编写代码来扩展方法的功能,并且知道您的“类”的所有实例都会相应地更新:

        var old_foo = MyClass.prototype.foo;
        MyClass.prototype.foo = function() {
            /* new business logic here */
        
            // now call the original method.
            old_foo.apply(this, arguments);
        };
        

        关于“静态”属性,您可以在“类”(构造函数)本身上声明它们:

        // example static property
        MyClass.num_instances = 0;
        

        现在您可以像这样创建 init/destroy 方法:

        MyClass.prototype.init = function() {
            this.constructor.num_instances++;
        };
        
        MyClass.prototype.destroy = function() {
            this.constructor.num_instances--;
        };
        
        // and call the init method any time you create a new instance
        my_instance.init();
        console.log( MyClass.num_instances ); //=> 1
        
        var instance_2 = new MyClass();
        instance_2.init();
        console.log( MyClass.num_instances ); //=> 2
        
        instance_2.destroy();
        console.log( MyClass.num_instances ); //=> 1
        

        希望对您有所帮助。

        【讨论】:

          【解决方案6】:

          (1) 我不认为仅保留内存是使用 .prototype 的正当理由,除非您在复制对象方面变得非常极端。

          (2) 静态属性的想法也不是使用 .prototype 的真正理由(恕我直言),因为它的行为不像传统的静态属性。你(据我所知)总是需要一个对象实例才能访问“静态”属性,这使得它根本不是静态的。

          function Car() {}
          Car.prototype.Engine = "V8";
          // I can't access Car.Engine... I'll always need an instance.
          alert(new Car().Engine);
          // or
          var car1 = new Car();
          alert(car1.Engine); //you always need an instance.
          //unless you wanted to do
          alert(Car.prototype.Engine); //this is more like a static property, but has an
          //unintended consequence that every instance of Car also receives a .Engine
          //behavior, so don't do this just to create a "static property."
          

          需要注意的是,从传统的 OO 角度来看,这种“静态”思想不仅适用于属性,还适用于所有成员,包括方法(函数)。

          最好将原型(同样,恕我直言)视为具有附加到实例对象的行为的注入单例对象。 Car() 的所有实例都可以有自己的实例成员,但是 Car() 的每个实例也将“自动”注入所有 Car.prototype 的成员/行为。这在技术上并不相同,但我发现以这种方式考虑原型很方便。

          //define Car and Car.GoFast
          function Car() {}
          Car.prototype.GoFast = function () { alert('vroom!'); };
          
          var car1 = new Car();
          var car2 = new Car();
          
          car1.GoFast();
          car2.GoFast(); //both call to same GoFast implementation on Car.prototype
          
          //change the GoFast implementation
          Car.prototype.GoFast = function () { alert('vvvvvrrrrroooooooooommmmm!!!!!'); };
          
          car1.GoFast();
          car2.GoFast(); //both have been "updated" with the new implementation because
          //both car1 and car2 are pointing to the same (singleton) Car.prototype object!
          

          Car.prototype 的行为就像一个单例对象,其成员/行为已被注入到 Car 类型的实例对象中。

          (3) 原型不应与继承混淆。您可以获得看似继承的行为,但事实并非如此。原型上的成员/行为保留在原型对象上。它们不会像真正的继承那样成为派生类的成员/行为。这就是为什么我把它描述得更像是原型被“注入”到你的实例中。

          function Car() {}
          Car.prototype.Engine = "V8";
          var car1 = new Car();
          var car2 = new Car();
          
          alert(car1.Engine); //alerts "V8"
          //There is no "Engine" variable defined within the instance scope of 'car1'.
          //Javascript searches the scope of car1's Type.prototype object to find 'Engine'.
          //Equivalent to: Object.getPrototypeOf(car1).Engine
          
          //But if we create 'Engine' within the scope of car1
          car1.Engine = "V6"; //Car.prototype was never accessed or updated
          alert(car1.Engine); //we get "V6"
          alert(car2.Engine); //we still get "V8"
          alert(Object.getPrototypeOf(car1).Engine); //we still get "V8"!
          

          所以直接回答这个问题:使用原型而不是在对象本身上声明属性有什么好处吗?

          是的,当您想在给定类型的实例对象之间共享行为实现时。巧合的是,您将减少内存占用,但这并不是使用原型的唯一理由。 “创建静态属性”(它们不是)或继承(不是)都不是。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-05-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-06-18
            相关资源
            最近更新 更多