死斗:自执行匿名函数 vs "new function"

20

答案已在UPDATE 2/ANSWER中嵌入。

感谢Joseph帮助我找到答案(尽管我不喜欢它=)。

原始问题

在研究JavaScript中使用命名空间的最佳实践时,我发现了“模块模式”的定义:http://yuiblog.com/blog/2007/06/12/module-pattern/

自从几年前我在YUI2中看到这种模式以来,我一直在普遍使用它,而这篇文章对该概念进行了很好的概述。但是它没有涉及为什么要使用“自执行匿名函数”代替“new function”。虽然有人在评论中提出了这个问题,但作者并没有做出清晰的解释。由于这篇文章已经存在4年之久(我也没有在其他地方找到答案),我想把这个问题带到这里。

它已经在闭包内部,所以呢?(见:为什么这个函数被括号包裹,后面跟着括号? 这篇文章也没有回答我的问题 =)。

假设以下是设置的代码...

var MyNamespace = window.MyNamespace || {};

哪种方式更好并为什么?

MyNamespace.UsingNew = new function() {
    var fnPrivate = function() {
        return "secrets1";
    };

    this.property = "value1";
    this.method = function() {
        return "property = " + this.property + ' ' + fnPrivate();
    }
};

MyNamespace.UsingSelfEx = (function() { //# <- Added "pre-parens" suggested by chuckj
    var fnPrivate = function() {
        return "secrets2";
    };
    var fnReturn = {};

    fnReturn.property = "value2";
    fnReturn.method = function() {
        return "property = " + this.property + ' ' + fnPrivate();
    }

    return fnReturn;
})();

更新:

看起来像 jQuery 一样,所有时髦的人都在使用“自执行匿名函数”(SEAF)!但我不理解这个,因为我觉得使用 .UsingNew 方法更加简洁,因为你可以在定义时公开函数(而不是在返回语句中下方需要单独维护或被迫使用内联对象表示法)。

对于不需要“that = this”的论点,我并不认同其中的一些原因:

  • 为了避免“var that = this”,你要么最终得到一个“var obj”和一个返回语句(需要维护一个单独的公共定义),要么被迫使用内联对象表示法(返回 {1,2,3})来定义公共属性/方法。
  • 您始终可以在类/命名空间顶部创建一个“var that = this”的私有变量,并始终使用“that”。

现在...我想我的开发风格可能更容易管理 .UsingNew 模式。我的“私有”函数几乎总是具有“静态”性质,因此我需要传递上下文(替代“this”)。我还养成了使用“缩写”命名空间的习惯,因此当我需要访问“this”时,我只需通过“缩写”命名空间引用完整对象,而不是通过其完整路径引用。例如:

var PrivateFunct = function() {
    var rThis = Cn._.val; //# The abbreviated form of Cn.Renderer.Form.Validation
    //...
};

如果它是一个私有静态函数...

var PrivateStaticFunct = function(oContext) {
    //...
};

除了上面提到的原因外,我个人发现在源代码中使用.UsingNew方法更易读。我的代码库很大,使用.UsingNew模式:http://code.google.com/p/cn-namespace/source/browse/Cn.Web/js/Cn/,其中验证功能可能是比较容易理解的,只需要快速阅读一遍即可。我也使用一些SEAF函数(请参见ErrorMessages.js.aspx),但仅在它们有意义的情况下使用。

我无法想象必须维护单独的返回值来公开接口!真恶心!

现在,不要误会,有很多地方使用SEAF非常有用,以强制执行闭包,但我个人认为在对象内部过度使用它。

更新2

经过进一步思考(并感谢与Joseph在他的答案下的详细讨论),似乎可以应用一些规则来适用于此DEATHMATCH:

根据Douglas Crockford(参见:JS we hardly new Ya),匿名函数永远不应使用“new”关键字,因为:

  • 使用对象文字更快。
  • 通过使用new来调用函数,对象将保留一个无用的原型对象。这浪费了内存而没有抵消优势。如果我们不使用new,我们就不会在链中保留浪费的原型对象。(注意:原型调用在构造函数定义之后进行,由于SEAF或“new”匿名函数即将被触发,因此不能与其一起使用prototype)
  • 使用对象文字需要的代码更少。(虽然是真的,但我不同意,因为我讨厌对象文字表示法,我更愿意使用;而不是,,因为它更易读)
  • 直接在函数前面放置new绝不是一个好主意。例如,新函数在构造新对象方面没有优势。

因此,问题的核心在于:由于速度和较少的开销(没有不必要的原型对象),SEAF比var obj = new function() {...};更可取。您必须遭受的是,您被迫使用对象文字表示法(因此,在公共成员之间使用','而不是';'),或者在返回对象中维护单独的公共对象列表。

当您打算将函数用作对象构造函数时,不建议使用SEAF(Self-Executing Anonymous Function),因为 instanceof 将无法按预期工作(请参见:从JS闭包创建对象:我应该使用“new”关键字吗?)。

  • 如果它是一个意图作为Singleton /全局静态实例的匿名函数,请使用SEAF。
  • 如果您打算将其用作构造函数(可能表示多个对象)或者您正在使用.prototype,则使用“标准”函数定义并使用“new”调用,例如:
  • function PseudoClass1() {}

    var PseudoClass2 = function() {};

    var myClass1 = new PseudoClass1();

    var myClass2 = new PseudoClass2();

我不得不说,我对这个答案并不满意;) 我发现在代码库中,.UsingNew模式更可读,但是由于未使用的原型引用被实例化并留在对象链中,因此它比SEAF慢且使用更多内存。


1
(我知道我来晚了..)这里有很多有趣的东西...既然你强烈偏爱UsingNew,我认为仅仅因为未使用的原型而改变你的方法是不充分的理由...它只占用非常少量的内存(为了比较,它比使用模块模式的程序员愿意牺牲的内存要少得多,因为他们不使用原型)。 - Matt Browne
1
就匿名函数的方法而言,如果您将返回变量称为“self”而不是“fnReturn”,可能会更易读。您甚至可以将任何空对象传递给函数,并将“self”作为参数;然后除了返回语句和使用“self”而不是“this”之外,函数体与您的第一个示例完全相同。 - Matt Browne
你绝对不应该使用new function - Bergi
3个回答

8

首先,这个模式:

MyNamespace.UsingNew = new function() {
    var fnPrivate = function() {

        //what's this in here?

        return "secrets1";
    };

    this.property = "value1";
    this.method = function() {

        //what's this in here?

        return "property = " + this.property + ' ' + fnPrivate();
    }
};
  • 使用"new"关键字创建对象的实例,该对象由构造函数建模。如果忘记使用"new",你会得到一个名为MyNamespace.UsingNew()的函数,而不是一个对象。

  • 它是一个构造函数,还不是对象的实例(直到使用new创建它)。你需要使用"this"来指定它将成为的对象。当你在其中嵌套更多的函数时,它只会增加作用域的问题,并且"this"的值会不时地改变(直到控制台告诉你之前你都看不出来)。熟悉self=thisthat=this来保存"this"的值吗?在使用这种模式时,这几乎是必须的。

另一方面,下一个模式对我来说有点更好,因为:

  • 你不需要使用"new",因为它返回一个对象。

  • 不使用"this",因为它已经返回了一个对象。你甚至不需要"this"。

此外,我更喜欢以这种方式构建另一种模式:

ns.myobj = (function(){

    //private
    var _privateProp = '';
    var _privateMeth = function(){};

    //public
    var publicProp = '';
    var publicMeth = function(){};

    //expose public
    return {
        prop:publicProp,
        meth:publicMeth
    };
}());

是的,正是 Crockford 的一个视频让我不再使用 "new"。 - Joseph
@chuckj 那只是一个例子。我实际上不使用公共属性。 - Joseph
我认为你在SEAF的最后一行中犯了一个语法错误。在}();处,我没有看到之前函数结束的地方,尽管我将其插入控制台时没有出现错误。正确的语法是})();。匿名函数确实会变得棘手,不是吗? - Braden Best
@B1KMusic 实际上,两种方法都是可行且被接受的。大多数开发人员会将 () 放在外面以指示立即执行的表达式。我更喜欢放在里面,这样我就不会意外删除它们或看起来奇怪。 - Joseph
@JosephtheDreamer 嗯...很奇怪它能工作,因为第一次我尝试编写一个SEAF时,我没有在整个函数定义周围放括号,然后它说类似于unexpected token ( after }。哈哈,难怪WTF JS这么受欢迎 :P - Braden Best
显示剩余10条评论

2

两者几乎是相同的,性能特点应该相对一致。只是一个风格问题。个人来说,我更喜欢第二个但它写得有些奇怪。我会这样写:

MyNamespace.UsingSelfEx = (function () {
    function fnPrivate() {
        return "secrets2";
    }
    return {
        property: "value2";
        method: function () { 
            return "property = " + this.property + " " + fnPrivate();
        }
    }
})();

你其实不需要在函数周围加括号,但是自执行函数通常会这样做,这让读者一开始就知道这是一个自执行函数。


啊,你是正确的!自执行函数通常确实需要在它们周围加上“额外”的括号!不过我很惊讶这似乎成为了这种功能的首选方法。虽然我使用另一种方法,但有时我需要“this = that”的方法,但我发现与做对象返回任何形式相比,这要少得多。而且,当我构建名称空间时,我通常只是引用我需要使用的完整名称空间地址中的this.function。我倾向于将我的“私有”函数设计为“静态”,这无疑有助于.UsingNew方法的使用。 - Campbeln
请查看:链接。另外,我还使用了一个“缩写”命名空间(在代码中表示为“Cn..*”),以帮助进行“rThis = Cn..short”赋值(而不是“rThis = Cn.Some.Long.Namespace.Path”)。 - Campbeln
由于这是形成一个命名空间,并且只会在执行一次,因此这显然只是一个样式选择。选择你喜欢的那个。我更喜欢上面的方法,但那只是一个人的意见。 - chuckj
嘿,chuckj,谢谢!但是经过进一步的研究,发现你的方法有优势,因为在使用SEAF时未使用的原型对象不会被实例化并放入对象链中,而new function则具有这种额外开销。所以你的方法是有道理的 =) - Campbeln

0

我看到了这个,我喜欢它

var MyThing = (function(){

    function MyThing(args){
    }

    MyThing.prototype = {
        prototypeMethod: function() {}
    };

    return MyThing;
})();

你也可以这样做

MyThing.create = function(args){
    return new MyThing(args);
}

通常它被包含在另一个自调用函数中,以避免命名冲突。

(function({

})(window);

而且将window对象作为参数传递,以便将其分配给上层作用域。

window.MyThing = MyThing;

在这个例子中,你可以使用静态变量、私有等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接