自执行匿名函数与原型


26

在Javascript中,有几种在JavaScript中创建和管理类/命名空间的突出技术。

我很好奇哪种情况需要使用一种技术而不是另一种技术。我想选一个并坚持前进。

我编写了由多个团队维护和共享的企业代码,并且我想知道编写可维护的javascript时的最佳实践是什么?

我倾向于使用自执行匿名功能,但是我很好奇社区对这些技术的投票。

原型:

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

自闭式匿名函数:

//Self-Executing Anonymous Function 
(function( skillet, $, undefined ) {
    //Private Property
    var isHot = true;

    //Public Property
    skillet.ingredient = "Bacon Strips";

    //Public Method
    skillet.fry = function() {
        var oliveOil;

        addItem( "\t\n Butter \n\t" );
        addItem( oliveOil );
        console.log( "Frying " + skillet.ingredient );
    };

    //Private Method
    function addItem( item ) {
        if ( item !== undefined ) {
            console.log( "Adding " + $.trim(item) );
        }
    }     
}( window.skillet = window.skillet || {}, jQuery ));   
//Public Properties      
console.log( skillet.ingredient ); //Bacon Strips  

//Public Methods 
skillet.fry(); //Adding Butter & Fraying Bacon Strips 

//Adding a Public Property 
skillet.quantity = "12"; console.log( skillet.quantity ); //12   

//Adding New Functionality to the Skillet 
(function( skillet, $, undefined ) {
    //Private Property
    var amountOfGrease = "1 Cup";

    //Public Method
    skillet.toString = function() {
        console.log( skillet.quantity + " " + 
                     skillet.ingredient + " & " + 
                     amountOfGrease + " of Grease" );
        console.log( isHot ? "Hot" : "Cold" );
     };     

}( window.skillet = window.skillet || {}, jQuery ));
//end of skillet definition


try {
    //12 Bacon Strips & 1 Cup of Grease
    skillet.toString(); //Throws Exception 
} catch( e ) {
    console.log( e.message ); //isHot is not defined
}

我觉得我应该提到自执行匿名函数是jQuery团队使用的模式。

更新 当我问这个问题时,我并没有真正理解我想要理解的重要性。真正的现实问题是是否使用new来创建对象的实例或使用不需要构造函数/无需使用new关键字的模式。

我添加了自己的答案,因为我认为我们应该使用不使用new关键字的模式。

有关更多信息,请参见我的答案。


1
您能否给出所描述的两种技术的简短示例?
Hey 2012年

不要因为我的简单样本而低估了原型。
Robotsushi 2012年

1
它本身没有执行= /
2012年

2
我看不到任何括号可以关闭表达式或将其称为...

1
(+1)许多开发人员都忽略了命名空间。
umlcat '02

Answers:


22

自执行匿名函数用于自动执行脚本,而无需陷入外部事件(例如window.onload)。

在此示例中,它用于形成经典的模块模式,其主要目的是将命名空间引入全局环境,并为未“导出”或未附加到命名空间的任何内部属性提供封装

另一方面,修改对象原型用于建立继承(或扩展本机)。此模式用于产生具有常用方法或属性的1:n对象。

您不应优先选择一种模式,因为它们执行不同的任务。就命名空间而言,自执行功能是一个适当的选择。


7
注意,“自我执行匿名函数”通常称为立即调用函数表达式(IIFE)
voithos 2014年

我避免使用它们,说实话,我没有得到IIFE的支持。它们在Eclipse中调试和破坏代码概述很麻烦。如果需要命名空间,则将其粘贴在对象上,如果需要执行它,则只需调用它,而封装并不能真正为我带来任何好处。
Daniel Sokolowski

4

这是我刚开始使用的模式(直到昨天一直在使用它的变体):

function MyClass() {
    // attributes
    var privateVar = null;

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

MyClass.prototype = new ParentClass(); // if required

关于此的一些想法:

  1. 您不应(anonymous)在调试器堆栈跟踪中获得任何跟踪,因为所有内容都已命名(没有匿名函数)。
  2. 这是我见过的最干净的模式
  3. 您可以轻松地将公开的API进行分组,而无需将其实现与声明耦合在一起(这意味着某人可以轻松浏览您的公共类接口而无需滚动)

我唯一prototype真正使用过的时间就是定义继承。


5
这有一些问题。每次调用构造函数时,都会为每个“方法”创建一个新的函数对象。另外,调用构造函数以获取原型对象的副本以进行继承的做法也令人讨厌。使用Object.create(ParentClass.prototype)或Object.create的填充程序,例如function clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Hey

@GGG:是的,您的第一点是正确的。我想(应该在我的帖子中提到)应该仔细考虑实现的每个特定用例。prototype如您所建议,我的方法问题是(除非有一种我不熟悉的方法,可能是这样),您失去了封装属性的能力,这意味着一切都以公开方式公开(这不是世界末日) ,只是个人喜好)。
Demian Brecht 2012年

此外,在查看了以下关于jsperf的工作后(jsperf.com/object-create-vs-constructor-vs-object-literal/12),我几乎每天都可以将性能提高超过其他副本的内存开销。非常具体的用例)。
Demian Brecht 2012年

现在,说了这么多,我才刚走过ECMA-262,所以可能有些东西我没看到。.而且,我不相信克罗克福德的话是福音。 (当然是最重要的专家之一),但这并不总是使他始终100%正确。还有其他专家(我不是其中之一;))在论点上存在令人信服的论点。
Demian Brecht 2012年

2
您可能对我正在研究的这件东西感兴趣。
Hey

3

我使用原型是因为它们更干净并且遵循标准的继承模式。自我调用功能非常适合浏览器开发或您不知道代码在何处执行的情况,但是这只是噪音。

例:

var me;

function MyObject () {
    this.name = "Something";
}

MyObject.prototype.speak = function speak () {
    return "Hello, my name is " + this.name;
};

me = new MyObject();
me.name = "Joshua";
alert(me.speak());

1
自执行匿名函数对于允许您具有可用于类的私有函数非常有用。
Zee 2012年

1

我会使用自我执行功能,但有一点区别:

MyClass = (function() {
     var methodOne = function () {};
     var methodTwo = function () {};
     var privateProperty = "private";
     var publicProperty = "public";

     return function MyClass() {
         this.methodOne = methodOne;
         this.methodTwo = methodTwo;
         this.publicProperty = publicProperty;
     };
})();

如果发现这种方法更简洁,那么当我将返回的全局变量与任何输入参数(例如jQuery)分开时(您编写它的方式就等同于返回void并在C#中使用ref参数,我发现有点不对劲,或者将指针传递给指针,然后在C ++中将其重新分配)。如果我随后将附加的方法或属性附加到类上,则将使用原型继承(例如jQuery的$ .extend方法,但是很容易滚动自己的extend()):

var additionalClassMethods = (function () {
    var additionalMethod = function () { alert('Test Method'); };
    return { additionalMethod: additionalMethod };
})();

$.extend(MyClass.prototype, additionalClassMethods);

var m = new MyClass();
m.additionalMethod(); // Pops out "Test Method"

这样,您就可以清楚地区分添加的方法和原始方法。


1
我是唯一认为使用NFE这样的主意的人吗?
Hey

1

现场例子

(function _anonymouswrapper(undefined) {

    var Skillet = {
        constructor: function (options) {
            options && extend(this, options);
            return this; 
        },
        ingredient: "Bacon Strips",
        _isHot: true,
        fry: function fry(oliveOil) {
            this._addItem("\t\n Butter \n\t");
            this._addItem(oliveOil);
            this._addItem(this.ingredient);
            console.log("Frying " + this.ingredient);
        },
        _addItem: function addItem(item) {
            console.log("Adding " + item.toString().trim());
        }
    };

    var skillet = Object.create(Skillet).constructor();

    console.log(skillet.ingredient);
    skillet.fry("olive oil");

    var PrintableSkillet = extend(Object.create(Skillet), {
        constructor: function constructor(options) {
            options && extend(this, options);
            return this;
        },
        _amountOfGrease: "1 Cup",
        quantity: 12,
        toString: function toString() {
            console.log(this.quantity + " " +
                        this.ingredient + " & " +
                        this._amountOfGrease + " of Grease");
            console.log(this._isHot ? "Hot" : "Cold");
        }
    });

    var skillet = Object.create(PrintableSkillet).constructor();

    skillet.toString();

    function extend(target, source) {
        Object.getOwnPropertyNames(source).forEach(function (name) {
            var pd = Object.getOwnPropertyDescriptor(source, name);
            Object.defineProperty(target, name, pd);
        });
        return target;
    }
}());

您可以使用IIFE在代码周围模拟“模块作用域”。然后,您可以像平常一样使用对象。

不要使用闭包“模拟”私有状态,因为这样做会占用大量内存。

如果您正在编写企业应用程序,并且希望将内存使用量保持在1GB以下,请避免不必要地使用闭包来存储状态。


您的代码伙伴中有几次错别字。我也不赞成您断言闭包会自动导致过多的内存使用,我认为这取决于您使用它们的程度以及处理范围问题的能力(通常将闭包内部的内容与外部的内容混合在一起)不好的例子(例如,您使用全局控制台就是一个很好的例子,如果您将控制台作为变量传递,则上面的代码会更有效)。
Ed James

@EdWoodcock我认为代码是错误的,只是对其进行了重构和修复。
雷诺斯

@EdWoodcock本地console与全球之间的效率差异console是微观优化。这样做是值得的,您可以最小化,console但这是另一回事
Raynos 2012年

如果您在其中复制对象,则@EdWoodcock闭包速度很慢。缓慢的是在不需要时在函数内部创建函数。与直接在对象上存储状态相比,闭包在存储状态方面也具有较小的内存开销
Raynos 2012年

是的,只是想指出一点,因为您的回答是处理问题的合理方法(尽管不是我选择使用的方法)。(我已经进行了编辑,但仍不确定该特定SE网站上的人员的礼节)。
Ed James

0

更新 ,现在我对javascript有更好的理解,并觉得我可以正确解决这个问题。我认为这是措辞不佳,但非常重要的javascript主题。

如果您避免在函数外部使用此功能,则自执行匿名功能模式不是要求使用new关键字的模式。我同意使用新技术是一种旧技术的想法,我们应该努力使用避免使用新技术的模式。

自执行匿名功能满足此条件。

这个问题的答案是主观的,因为javascript中有太多的编码样式。但是,根据我的研究和经验,我建议选择使用自执行匿名函数来定义您的API,并尽可能避免使用new。


1
新关键字有什么不好的地方?我很好奇。
Ally 2014年

0

这就是我将如何IIFE SelfExecutingFunction扩展Myclasswith的方法Myclass.anotherFunction()

MyClass = (function() {
     var methodOne = function () {};
     var methodTwo = function () {};
     var privateProperty = "private";
     var publicProperty = "public";

    //Returning the anonymous object {} all the methods and properties are reflected in Myclass constructor that you want public those no included became hidden through closure; 
     return {
         methodOne = methodOne;
         methodTwo = methodTwo;
         publicProperty = publicProperty;
     };
})();

@@@@@@@@@@@@@@@@@@@@@@@@
//then define another IIFE SEF to add var function=anothermethod(){};
(function(obj){
return obj.anotherfunction=function(){console.log("Added to Myclass.anotherfunction");};
})(Myclass);

//the last bit : (function(obj){})(Myclass); obj === Myclass obj is an alias to pass the Myclass to IIFE

var myclass = new Myclass();
myclass.anotherfunction(); //"Added to Myclass.anotherfunction"

1
程序员是游览概念性问题的答案,期望答案能够解释问题。抛出代码转储而不是进行解释就像将代码从IDE复制到白板一样:它看起来很熟悉,甚至有时是可以理解的,但是感觉很奇怪……只是很奇怪。白板没有编译器
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.