【问题标题】:Object oriented javascript with prototypes vs closures带有原型与闭包的面向对象的javascript
【发布时间】:2011-04-03 14:47:02
【问题描述】:

我很好奇以下 OOP javascript 技术之间有什么区别。他们似乎最终会做同样的事情,但被认为比另一个更好吗?

function Book(title) {
    this.title = title;
}

Book.prototype.getTitle = function () {
    return this.title;
};

var myBook = new Book('War and Peace');
alert(myBook.getTitle())

function Book(title) {
    var book = {
        title: title
    };
    book.getTitle = function () {
        return this.title;
    };
    return book;
}

var myBook = Book('War and Peace');
alert(myBook.getTitle())

【问题讨论】:

  • 闭包主要用于创建私有作用域。因此,当使用闭包时,您可以创建真正的私有变量。对于像 jQuery 这样的大型项目,他们使用它来防止代码冲突。
  • 这是 MSDN 上一篇关于 execution speed 的有趣(有些旧)文章。

标签: javascript oop


【解决方案1】:

第二个并没有真正创建实例,它只是返回一个对象。这意味着您不能利用像instanceof 这样的运算符。例如。对于第一种情况,您可以使用if (myBook instanceof Book) 来检查变量是否为 Book 类型,而对于第二种情况,这将失败。

如果你想在构造函数中指定你的对象方法,这是正确的方法:

function Book(title) {
    this.title = title;

    this.getTitle = function () {
        return this.title;
    };
}

var myBook = new Book('War and Peace');
alert(myBook.getTitle())

虽然在此示例中两者的行为方式完全相同,但存在差异。使用基于闭包的实现,您可以拥有私有变量和方法(只是不要在this 对象中公开它们)。因此,您可以执行以下操作:

function Book(title) {
    var title_;

    this.getTitle = function() {
        return title_;
    };

    this.setTitle = function(title) {
        title_ = title;
    };

    // should use the setter in case it does something else than just assign
    this.setTitle(title);
}

Book函数之外的代码不能直接访问成员变量,必须使用访问器。

另一个很大的区别是性能;由于使用闭包中包含一些开销,基于原型的分类通常要快得多。你可以在这篇文章中了解性能差异:http://blogs.msdn.com/b/kristoffer/archive/2007/02/13/javascript-prototype-versus-closure-execution-speed.aspx

【讨论】:

  • 基于原型的分类在现代浏览器中并没有那么快。您引用的文章在您发布时已经 3 岁了,现在已经 6 岁了,即完全无关紧要。这是一项性能测试,更相关 - jsperf.com/prototype-vs-closures/20
  • 另一个区别是,如果你使用this.getTitle,每个实例都会有不同的getTitle函数做同样的事情。如果您使用 Book.prototype.getTitle,它们将共享原型中的相同方法实现。
  • @opensas,我不确定这在实践中是否真的如此。我认为现代浏览器可能会编译一次闭包并重用它。它可以通过识别哪些变量属于函数之外的范围来做到这一点,并将这些变量用作函数的一种隐藏参数列表。
  • 实际上,Chrome 现在显示闭包速度更快:jsperf.com/prototype-vs-closures/20
  • 在 Chrome 53.0.2785 上,我看到原型要快得多(大约 10 倍操作/秒)。
【解决方案2】:

哪个更好有时可以根据它们的使用环境来定义。

我如何在 PrototypeClosure 编码方法之间进行选择的三个限制(我都积极使用这两种方法):

  1. 性能/资源
  2. 压缩要求
  3. 项目管理

1.性能/资源

对于对象的单个实例,任何一种方法都可以。任何速度优势很可能都可以忽略不计。

如果我要实例化其中的 100,000 个,例如建立一个图书库,那么 原型方法 将是首选。所有的.prototype。如果使用 Closure Method,函数只会被创建一次,而不是被创建 100,000 次。资源不是无限的。

2。压缩

如果压缩效率很重要(例如:大多数浏览器库/模块),请使用 闭包方法。解释见下文:

压缩 - 原型方法

function Book(title) {
    this.title = title;
}

Book.prototype.getTitle = function () {
  return this.title;
};

YUI 被压缩成

function Book(a){this.title=a}Book.prototype.getTitle=function(){return this.title};

节省约 18%(所有空格/制表符/回车)。此方法需要公开变量/函数(this.variable=value),以便每个原型函数都可以访问它们。因此,这些变量/函数无法在压缩中进行优化。

压缩 - 闭合方法

function Book(title) {
  var title = title; // use var instead of this.title to make this a local variable

this.getTitle = function () {
  return title;
};
}

YUI 被压缩成

function Book(a){var a=a;this.getTitle=function(){return a}};

节省约 33%。可以优化局部变量。在具有许多支持功能的大型模块中,这可以显着节省压缩。

3.项目管理

在一个有多个开发人员的项目中,他们可能在同一个模块上工作,如果不受性能或压缩的限制,我更喜欢该模块的原型方法

对于浏览器开发,我可以在我自己的“test.js”中覆盖“production.js”中的producton.prototype.aFunction(在后记中阅读)用于测试或开发目的,而无需修改“production .js”,可能正在由其他开发人员积极开发。

我不是复杂的 GIT 存储库签出/分支/合并/冲突流程的忠实粉丝。我更喜欢简单。

此外,通过测试台重新定义或“劫持”模块功能的能力可能是有益的,但这里过于复杂......

【讨论】:

    【解决方案3】:

    前一种方法是 JavaScript 的使用方式。后者是更现代的技术,由 Douglas Crockford 部分推广。这种技术更加灵活。

    你也可以这样做:

    function Book(title) {
        return {
            getTitle: function () {
                return title;
            }
        }
    }
    

    返回的对象将只有一个名为 getTitle 的访问器,它将返回参数,并保持在闭包中。

    Crockford 在Private Members in JavaScript 上有一个很好的页面 - 绝对值得一读以了解不同的选项。

    【讨论】:

      【解决方案4】:

      这也有点关于引擎盖下的可重用性。在使用Function.prototype 属性的第一个示例中,Book 函数对象的所有实例都将共享getTitle 方法的相同副本。而第二个 sn-p 将使 Book 函数执行创建并保存在堆“书架”中的本地 可关闭 book 对象的不同副本。

      function Book(title) {
          var book = {
              title: title
          };
          book.getTitle = function () {
              return this.title += '#';
          };
          return book;
      }
      
      var myBook = Book('War and Peace');
      var myAnotherBook = Book('Anna Karenina');
      alert(myBook.getTitle()); // War and Peace#
      alert(myBook.getTitle()); // War and Peace##
      alert(myAnotherBook.getTitle()); // Anna Karenina#
      alert(myBook.getTitle());// War and Peace###
      

      另一方面,原型成员存在于对象的所有new 实例的唯一副本中。所以这是它们之间的一个更细微的区别,由于闭包技巧,从第一次叹息中就不是很明显。

      【讨论】:

        【解决方案5】:

        这是一篇文章about this 一般来说,来自 Book.prototype 的 Book inharets。在第一个示例中,您将函数添加到 getTitle Book.prototype

        【讨论】:

        • 链接失效,请改