cover

起因

最近在进行 underscore.js 源码分析,也顺便看了些别的库的大致封装方式
underscore:

1
2
3
4
5
6
7
8
9
(function() {
var root = this;
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};
// blabla
}());

早期的 jQuery:

1
2
3
4
5
6
7
8
(function( window, undefined ) {
var jQuery = (function() {console.log('hello');});
window.jQuery = window.$ = jQuery;
if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
define( "jquery", [], function () { return jQuery; } );
}
// blabla
})( window );

这个封装和调用方式一看就是匿名函数嘛,可是匿名函数,然后呢?
于是趁着下午比较闲翻开高程从匿名函数这个地方开始看,发现了一系列的知识点……
大概脉络是: 函数定义方式 → 匿名函数的创建 → 匿名函数自执行还是IIFE? → IIFE 的应用

函数定义方式

  1. 函数声明

    1
    function foo () {}

    函数声明提升 :可以把函数声明放在调用它的语句后面

  2. 函数表达式

    1
    var foo = function () {}

    也属于匿名函数

  3. 使用Function构造函数

    1
    var foo = new Function ()

匿名函数的创建

  1. 上述 使用函数表达式创建

  2. (function (x, y) {alert(x + y)})(2, 3)
    在第一个括号内创建匿名函数,第二个括号用于调用该匿名函数,并传入参数

    • function (x,y) {alert(x + y)}部分为所声明的函数
    • 然后用括号把函数声明包起来 (function (x,y) {alert(x + y)})
    • 最后调用此函数 (function (x, y) {alert(x + y)})(2, 3)

在这里我们也可以看到,创建匿名函数的方式有两种,第二种方式才会声明后立即执行,即不是所有的匿名函数都是可以立即执行的

“匿名函数自执行” vs IIFE

自执行,有的地方会写为自调用,是一个经常会听到的的概念,我们再深究一下它吧。所谓的“匿名函数自执行”指的是 Self-executing anonymous function ,经常与 IIFE 混淆, Immediately-Invoked Function Expression立即调用的函数表达式 ,含义上的辨析 先戳一下中文wiki 吧。

IIFE 的具体的例子:

1
2
3
4
// 第一个例子
(function (x) { return x + 1;}(2)) // => 3
// 第二个例子
var result = function (x) { return x + 1; }(2); // => result 的值为3

把第一个例子敲到控制台中,我们可以看到这个 () 中的代码立即执行了,返回3。对于第二个例子,它实际上执行的是:

1
2
var foo = function (x) {return x + 1;}
var result = foo(2)

这里有一个知识点: 对于函数类对象,不论是一个现场定义的匿名函数,还是一个之前定义的有名字的函数,它们在不加括号的时候都代表了这个函数对象本身,而加了括号就代表调用这个函数,也就是这个函数 return 的对象。

再回到匿名函数自执行与 IIFE 的辨析上,匿名函数自执行(Self-executing anonymous function)是一个不够准确的概念:

1
2
3
4
// 它是一个自执行的匿名函数,它必须用 arguments.callee() 来递归地调用自己
var foo = function() { arguments.callee(); };
// 它是一个立即执行的匿名函数,只是立即执行了一段 code ,并不是调用自己
(function () { /* code */ } ());

因此 自执行 意味着调用自己, 立即调用,立即执行 强调的是定义函数的时候就直接执行了函数,与函数体内是否调用自身无关。只能说这是两个看起来有些类似的概念,但实际上所强调的内容完全不一样。IIFE 也可以调用自己,匿名函数可以立即执行。

IIFE 的应用

立即调用的函数表达式的根本作用是 创建一个独立的作用域

  1. 模拟块级作用域,在ES6之前 JavaScript 是没有块级作用域的。
    在实际应用场景中,项目引用了很多不同的库 ,库与库之间是如何保证变量不被覆盖呢?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // libA.js
    (function(){
    var num = 1;
    // blabla
    })();
    // libB.js
    (function(){
    var num = 2;
    // blabla
    })();

    这样的话,就如同我们在前面看到的 jquery underscore 那样,使自身作用域独立,不会互相覆盖。

  2. 解决闭包的坑: 闭包只能取得包含函数中任何变量的最后一个值
    这次改造一下《高程三》中提到的例子作为参照吧:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function test () {
    var result = new Array();
    var foo = null;
    for (var i = 0; i < 10; i++) {
    foo = function () {
    console.log(i)
    }
    result.push(foo)
    };
    return result
    }
    var res = test()
    for (var i =0,len = res.length; i < len; i++){
    res[i]()
    }
    // 控制台中打出来的是10个10,而不是1,2,3...
    // i 是贯穿整个作用域的,而不是给每个 foo 分配了一个 i
    // test()执行完毕后才调用 console.log() 一定是发生在for循环已循环结束后,此时i值为10

    在线例子可以戳 JSFiddle
    这个坑该怎么处理呢?引入IIFE!下面代码是可用的,把 i 的值作为索引锁住了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 解决思路是给每个foo函数创建一个独立的作用域
    function test () {
    var result = new Array();
    var foo = null;
    for (var i = 0; i < 10; i++) {
    // 添加一个IIFE
    (function(index) {
    foo = function() {console.log(index);};
    result.push(foo)
    })(i);
    };
    return result
    }
    var res = test()
    for (var i =0,len = res.length; i < len; i++){
    res[i]()
    }

    参考内容