2

我一直在重构别人的 JavaScript 代码。

前:

function SomeObj(flag) {
    var _private = true;
    this.flag = (flag) ? true : false;
    this.version="1.1 (prototype)";
    if (!this._someProperty) this._init();
            // leading underscore hints at what should be a 'private' to me
    this.reset(); // assumes reset has been added...
}

SomeObj.prototype.reset = function() {
    /* perform some actions */
}

/* UPDATE */
SomeObj.prototype.getPrivate = function() {
    return _private; // will return undefined
}

/* ...several other functions appended via `prototype`...*/

后:

var SomeObj = function (flag) {
    var _private = true;
    this.flag = (flag) ? true : false;
    this.version = "2.0 (constructor)";

    this.reset = function () {
       /* perform some actions */
    };

    /* UPDATE */
    this.getPrivate = function() {
        return _private; // will return true
    }

    /* other functions and function calls here */
}

对我来说,第一个例子看起来很难阅读,尤其是在更大的上下文中。reset像这样使用属性添加类似 on 的方法prototype似乎不太受控制,因为它可能发生在脚本中的任何地方。我重构的代码(上面的第二个示例)对我来说看起来更整洁,因此更容易阅读,因为它是独立的。我通过变量声明获得了一些隐私,但我失去了原型链的可能性。

...

问题:

  1. 首先,我很想知道前面提到的我还失去了什么prototype,或者原型链的丢失是否有更大的影响。这篇文章已有6 年历史,但声称prototype在大规模上使用该属性比闭包模式更有效。

  2. 上面的两个例子仍然是由new操作符实例化的;它们都是“经典”式的构造函数。最终,我什至想从这个模型转移到所有属性和函数都声明为vars 的模型中,并且我有一个公开的方法,它能够返回一个对象,打开我需要的所有属性和方法,这些属性和方法具有特权(通过关闭)对那些私有的。像这样的东西:

    var SomeObj = (function () {
    
        /* all the stuff mentioned above, declared as 'private' `var`s */
    
        /* UPDATE */
        var getPrivate = function () {
            return private;
        }
    
        var expose = function (flag) {
             // just returns `flag` for now
             // but could expose other properties
             return {
                 flag: flag || false, // flag from argument, or default value
                 getPrivate: getPrivate
             } 
        };
    
        return {
            expose: expose
        }
    })(); // IIFE
    
    // instead of having to write `var whatever = new SomeObj(true);` use...
    var whatever = SomeObj.expose();
    

    StackOverflow 上有一些解决“原型与闭包”问题的答案(例如,此处此处)。但是,与prototype财产一样,我感兴趣的是,朝着这个方向发展并远离new运营商对我的代码效率和任何可能性的损失(例如instanceof丢失)意味着什么。如果无论如何我都不打算使用原型继承,那么在前面的new运算符中我真的会失去任何东西吗?

  3. 如果我被允许,一个更宽松的问题,考虑到我要求上面的细节:如果prototypenew真的是最有效的方式,比关闭有更多的优势(无论你认为它们可能是什么),是否有任何指导方针或设计以更整洁的方式编写它们的模式?

...

更新:

请注意,expose每次都会返回一个新对象,因此这是实例化发生的地方。据我了解,该对象指的是SomeObj闭包中声明的方法,它们在所有对象中都是相同的方法(除非被覆盖)。在flag变量的情况下(我现在已经更正了),这可以从 的参数继承expose,具有默认值,或者再次引用封装的预先存在的方法或属性。所以有一些正在生成的对象的实例,并且这里正在进行一些继承(加上多态性?)。

所以重复问题 2:如果我无论如何都不打算使用原型继承,那么在前面的new运算符中我真的会失去任何东西吗?

非常感谢到目前为止的回答,这有助于澄清我的问题。

4

4 回答 4

1

以我的经验,你唯一失去的就是不使用.prototype内存——每个对象最终都拥有自己的其中定义的函数对象的副本。

如果您只打算实例化“少量”对象,这不太可能是一个大问题。

关于您的具体问题:

  1. 对该链接文章的第二条评论是高度相关的。作者的基准测试是错误的——它正在测试运行同时声明四个内部函数的构造函数的开销。它不是测试这些功能的后续性能。

  2. 您的“关闭和公开”代码示例不是面向对象的,它只是一个带有一些封闭私有变量的命名空间。因为它不使用new它,所以如果您希望从中实例化对象,那么它是没有用的。

  3. 我无法回答这个问题——“这取决于”是你能得到的最好的答案。

于 2013-02-15T12:51:03.123 回答
1

答案:

  1. 你已经回答了这个问题:你失去了原型链。(实际上你并没有丢失它,但你的原型总是空的)。后果是:

    • 对性能/内存有一点影响,因为方法是为每个实例创建的。但这在很大程度上取决于 JavaScript 引擎,只有在需要创建大量对象时才应该担心它。

    • 您不能通过修改原型来修改补丁实例。也不是什么大问题,因为这样做会导致维护噩梦。

  2. 让我对你的问题做一个小小的修正:不是“原型与闭包”的问题,实际上闭包的概念与基于原型的语言是正交的。

    这个问题与您将如何创建对象有关:每次都从零开始定义一个新对象,或者从原型中克隆它。

    您展示的关于使用函数限制范围的示例是 JavaScript 中的常见做法,即使您决定使用原型,您也可以继续这样做。例如:

    var SomeObj = (function (flag) {
    
        /* all the stuff mentioned above, declared as 'private' `var`s */
    
        var MyObj = function() {}
        MyObj.prototype = {
            flag: flag,
            reset: reset
        };
    
        return {
           expose: function() { return new MyObj(); }
        }
    })();
    

    如果您担心模块化,请查看requirejs,它是一种称为 AMD(异步模块定义)的技术的实现。有些人不喜欢 AMD,有些人喜欢它。我对它的体验是积极的:它对我为浏览器创建模块化 JavaScript 应用程序帮助很大。

  3. 有一些库可以让您的原型生活更轻松:composejsdejavu和我自己的酒保(是的,这是一种无耻的自我宣传,但您可以查看源代码以了解处理对象定义的方法)。

    关于模式:由于您可以使用工厂方法轻松隐藏对象实例化,因此您仍然可以new在内部使用克隆原型。

于 2013-02-15T14:29:04.700 回答
1

前面我还失去了prototype什么?

我相信有人可以提供答案,但我至少会试一试。至少有两个理由使用prototype

  1. prototype方法可以静态使用
  2. 它们只创建一次

将方法创建为对象成员意味着它是为对象的每个实例创建的。每个对象的内存更多,并且会减慢对象的创建速度(因此提高了效率)。人们倾向于说prototype方法类似于类方法而成员方法类似于对象方法,但这是非常误导的,因为原型链中的方法仍然可以使用对象实例。

您可以将原型定义为对象本身,因此您可能更喜欢这种语法(但并不是完全不同):

SomeObj.prototype = {
    method1: function () {},
    method2: function () {}
}

你认为它似乎不受控制的论点对我来说是有效的。我知道有两个块参与创建对象是很奇怪的。但是,这有点虚假,因为无论如何都无法阻止某人覆盖您其他对象的原型。

//Your code
var SomeObj = function (flag) { //...

//Sneaky person's code
delete SomeObj.reset;
SomeObj.prototype.reset = function () { /* what now? */ }

前面的new

如果您只想通过{}符号动态创建特定的对象实例,那么它与使用并没有什么不同new。您需要使用new从类 ( function) 定义中创建同一对象的多个实例。这并不罕见,因为它适用于任何面向对象的编程语言,并且与重用有关。

对于您当前的应用程序,这可能会很好。但是,如果您想出了一些可跨上下文重用的很棒的插件,那么必须大量重写它可能会很烦人。我认为您正在寻找类似的东西require.js,它允许您定义可以使用该require函数导入的“模块”。您在函数闭包中定义了一个模块define,因此无论如何您都可以将构造函数和原型定义包装在一起,并且在导入该模块之前没有其他人可以接触它们。

封闭对比的优势prototype

它们不是相互排斥的:

var attachTo = {};
;(function (attachTo, window, document, undefined) {
    Plugin = function () { /* constructor */ };
    Plugin.prototype = { /* prototype methods */ };

    attachTo.plugin = Plugin;
})(attachTo, window, document);
var plugin = new (attachTo.plugin);

http://jsfiddle.net/ExplosionPIlls/HPjV7/1/

于 2013-02-15T13:00:36.200 回答
0

一问一答:

  1. 基本上,拥有该reset方法prototype意味着您的构造函数的所有实例将共享该方法的完全相同的副本。通过在构造函数中创建本地方法,您将拥有每个实例的方法副本,这将消耗更多内存(如果您有很多实例,这可能会成为问题)。除此之外,两个版本是相同的;更改function SomeObjvar SomeObj = functionSomeObj在其父范围上的提升方式有所不同。你说你“通过变量声明获得了一些隐私”,但我没有看到任何私有变量......

  2. 使用您提到的 IIFE 方法,您将失去检查 if 的能力instance instanceof SomeObj

  3. 不确定这是否回答了您的问题,但也有Object.create,您仍然可以在其中设置原型,但摆脱new关键字。但是,您将失去拥有构造函数的能力。

于 2013-02-15T12:54:05.713 回答