JavaScript函数声明选项

3

我曾见过专家使用以下方式来声明函数:

(function () {
  function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }
  //etc

}());

例如:https://github.com/douglascrockford/JSON-js/blob/master/json.js

有人能帮我理解何时应使用上述模式以及如何利用它吗?

谢谢。


1
基本思想是:当您不希望该函数在全局命名空间中对其他代码可见时 - 即保持其私有性和/或避免污染全局命名空间。 - Rup
3个回答

2
好的,由于ECMA6尚未到来,函数大约是在JS中创建作用域的最佳方法。如果您将某种变量声明包装在IIFE(立即调用的函数表达式)中,则该变量将不会被全局创建。 函数声明也是如此。
如果您被赋予了清除脚本中所有全局变量的看似艰巨的任务,那么您只需要在一个简单的(function(){ / *这里放置脚本* /}()); 中包装整个脚本,就不会创建任何全局变量,除非它们是隐含的全局变量,但这只是一种懒惰的解决方法。 这种模式更加强大。

我已经更详细地解释了IIFE的使用方式,这里这里这里

基本的JS函数调用生命周期工作方式如下:

f();//call function
 ||
 ====> inside function, some vars are created, along with the arguments object
        These reside in an internal scope object
     ==> function returns, scope object (all vars and args) are GC'ed

与JS中的所有对象一样,只要该对象不再被引用,该对象就会被标记为垃圾回收( GC)。但请考虑以下情况:
var foo = (function()
{
    var localFoo = {bar:undefined};
    return function(get, set)
    {
        if (set === undefined)
        {
            return localFoo[get];
        }
        return (localFoo[get] = set);
    }
}());

当IIFE返回时,foo被赋予了另一个函数作为其返回值。现在localFoo是在IIFE的作用域中声明的,没有直接访问该对象的方法。乍一看,您可能希望localFoo被垃圾回收。
但请注意,被返回并分配给foo的函数仍然引用该对象,因此它不能被垃圾回收。换句话说:作用域对象超出函数调用的生命周期,从而创建了闭包。
因此,localFoo对象只有在变量foo超出范围或重新分配另一个值且所有对返回的函数的引用都丢失时才会被垃圾回收。
请查看其中一个链接答案(带有图表的答案),在该答案中有一篇文章的链接,我从中窃取了所使用的图像。如果这还没有解决问题,那么这应该为您澄清问题。
IIFE可以返回空,但仍然公开其作用域:
var foo = {};
(function(obj)
{
    //obj references foo here
    var localFoo = {};
    obj.property = 'I am set in a different scope';
    obj.getLocal = function()
    {
        return localFoo;
    };
}(foo));

这个IIFE没有返回任何值(隐含的undefined),但是console.log(foo.getLocal())会输出空对象字面量。foo本身也将被分配property。但等等,我可以做得更好。假设foo已经通过上面的代码一次:
var bar = foo.getLocal();
bar.newProperty = 'I was added using the bar reference';
bar.getLocal = function()
{
    return this;
};
console.log(foo.getLocal().newProperty === bar.newProperty);
console.log(bar ==== foo.getLocal());
console.log(bar.getLocal() === foo.getLocal().getLocal());
//and so on

这段文字的翻译如下:

这会记录什么?实际上,它将一次又一次地记录true。在JS中,对象永远不会被复制,它们的引用被复制,但对象始终是相同的。在某些范围内更改它一次,这些更改将在所有引用之间共享(逻辑上)。
这只是为了向您展示闭包可能一开始很难理解,但这也显示了它们可以有多么强大:您可以通过各种IIFE传递对象,每次设置一个新方法,该方法具有访问其自己唯一作用域的权限,其他方法无法访问。

注意
对于JS引擎来说,闭包并不容易进行垃圾回收,但是最近,这已经不再是一个大问题
同时花些时间搜索这些术语:

JavaScript中的模块模式使用它的一些原因,JavaScript中的闭包第二个链接,JavaScript函数作用域第一个链接,JavaScript函数上下文可怕的this引用。IIFE也可以是命名函数,但你只能在该函数的作用域内引用该函数。
(function init (obj)
{
    //obj references foo here
    var localFoo = {};
    obj.property = 'I am set in a different scope';
    obj.getLocal = function()
    {
        return localFoo;
    };
    if (!this.wrap)
    {//only assign wrap if wrap/init wasn't called from a wrapped object (IE foo)
        obj.wrap = init;
    }
}(foo));
var fooLocal = foo.getLocal();
//assign all but factory methods to fooLocal:
foo.wrap(fooLocal);
console.log(fooLocal.getLocal());//circular reference, though
console.log(init);//undefined, the function name is not global, because it's an expression

这只是一个基本示例,说明您如何使用闭包创建包装器对象...

1

以上模式称为立即函数。此函数执行3个操作:

该代码的结果是一个表达式,可以在单个语句中完成以下所有操作:

  1. 创建函数实例
  2. 执行该函数
  3. 丢弃该函数(因为语句结束后不再有任何对它的引用)

JS开发人员使用此方法创建变量和函数,而不会污染全局空间,因为它为变量和函数创建了自己的私有作用域。

在上面的示例中,函数f(){}处于立即函数的私有作用域中,您无法在全局或窗口范围内调用此函数。


立即调用函数表达式,准确地说。 - Christoph
通常被称为立即函数,意思是同一件事情。 - pvnarula
没有标准名称。在早期它被称为“立即函数”,但现在IIFE这个术语变得更加流行,可能是由于这篇文章:http://benalman.com/news/2010/11/immediately-invoked-function-expression/。 - Felix Kling
@pvnarula:IIFE不需要被垃圾回收:如果它有名称,并且在其函数体中被引用,它就不能被垃圾回收。 - Elias Van Ootegem

1
基于浏览器的JavaScript只有两个可用范围:全局和函数。这意味着您创建的任何变量都在全局范围内或限于当前所在函数的范围内。
有时,在初始化期间经常需要一堆仅需要一次的变量。将它们放在全局范围内不合适,但您不想要一个特殊的函数来执行此操作。
进入立即函数。这是定义并立即调用的函数。这就是您在Crockford(和其他人)的代码中看到的内容。它可以是匿名的或命名的,而不会破坏避免污染全局范围的目的,因为函数名称将局限于函数体内部。
它提供了一个范围来包含您的变量,而不会留下一个函数。保持整洁。

IIFE也可以是有名字的函数。 - Elias Van Ootegem
他们可以这样做,但那样做有点违背初衷。 - Dancrumb
1
不,它不会。我在答案底部添加了一个(糟糕的)示例:它仍然是一个函数表达式,只不过现在是一个命名函数表达式。这样,您仍然可以在严格模式下引用调用的函数表达式(在其中arguments.callee会抛出错误)。您可以使用此技术为单个对象创建包装器方法... jQuery 做了类似的事情:$ instanceof jQuery 为 true,$({}) instanceof jQuery 也是true,但是 $(window) 可以工作,而 $({})(window)$({}).(window) 不行。 - Elias Van Ootegem
@EliasVanOotegem 哦,原来如此 - 我没有意识到这一点。找到了关于function运算符的参考资料,解释了这个问题。感谢您提供的信息 - 我会更新我的答案。 - Dancrumb

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