从`prototype`和`new`转变为闭合和曝光模式

时间:2013-02-15 12:31:19

标签: javascript design-patterns closures prototypal-inheritance

我一直在重新考虑其他人的JavaScript代码。

BEFORE:

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`...*/

AFTER:

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属性添加像prototype这样的方法似乎受到的控制要少得多,因为它可能发生在脚本的任何地方。我重构的代码(上面的第二个例子)看起来更整洁,因此它更容易阅读,因为它是独立的。我已经通过变量声明获得了一些隐私,但我已经失去了原型链的可能性。

...

问题:

  1. 首先,我有兴趣知道前面prototype还有什么遗失,或者对原型链的丢失有更大的影响。 This article已有6年历史,但声称使用prototype属性比封闭模式更有效率。

  2. 上述两个示例仍然会由new运算符实例化;他们都是'经典'建构者。最后我甚至想把它从一个模型中移开到一个模型中,其中所有的属性和函数都被声明为var并且我有一个方法,我公开它能够返回一个对象,打开所有的属性和方法我需要,具有(通过关闭)特权的那些私有。像这样:

    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上有一些答案可以解决'原型与封闭'问题(例如herehere)。但是,与prototype属性一样,我感兴趣的是朝着这个方向迈进,远离new运算符意味着我的代码效率和任何可能性的损失(例如{{1}失去了)。如果我不打算使用原型继承,我实际上在instanceof运算符前面会丢失任何东西吗?

  3. 一个更宽松的问题,如果我被允许,因为我要求上述具体细节:如果newprototype确实是最有效的方式,有更多的优势(无论你认为他们可能是什么,而不是关闭,是否有任何指导方针或设计模式以便以更整洁的方式编写它们?

  4. ...

    更新:

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

    所以重复问题2:如果我不打算继续使用原型继承,那么在expose运算符前面我是否真的丢失了什么?

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

4 个答案:

答案 0 :(得分:1)

根据我的经验,不使用.prototype而丢失的唯一内容是内存 - 每个对象最终都拥有自己定义的函数对象的副本。

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

关于您的具体问题:

  1. 关于该链接文章的第二条评论具有高度相关性。作者的基准测试是错误的 - 它测试运行构造函数的开销,声明了四个内部函数。它测试这些功能的后续性能。

  2. 您的“闭包和公开”代码示例不是OO,它只是一个带有一些封闭私有变量的命名空间。由于它不使用new,如果您希望从中实例化对象,则没有用。

  3. 我无法回答这个问题 - “它取决于”就像你能得到的那样好。

答案 1 :(得分:1)

  

前面提到prototype还有什么遗失?

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

  1. prototype方法可以静态使用
  2. 只创建一次
  3. 将方法创建为对象成员意味着它是为对象的每个实例创建的。这是每个对象更多的内存,它减慢了对象的创建速度(因此提高了效率)。人们倾向于说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/

答案 2 :(得分: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和我自己的barman(是的,这是一种无耻的自我推销,但你可以看一下进入源代码,看看处理对象定义的方法)。

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

答案 3 :(得分:0)

问题提问:

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

  2. 根据您提到的IIFE方法,您将无法检查是否instance instanceof SomeObj

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