首页 > 代码库 > JavaScript 的closure 和 hoisting

JavaScript 的closure 和 hoisting

闭包(closure)

当声明一个函数时,其内部的变量的声明也在它的scope中被截获。比如下面的代码中,变量 x 绑定到了外部scope的值,然后对 x 的引用在bar的上下文中被截获。

var x = 4; // declaration in outer scope

function bar() {
    console.log(x); // outer scope is captured on declaration
}

bar(); // prints 4 to console

“截获”的概念很有意思,因为我们可以从外层scope使用和修改变量,甚至外层scope已经不存在了,比如:

function foo() {
    var x = 4; // declaration in outer scope

    function bar() {
        console.log(x); // outer scope is captured on declaration
    }

    return bar;

    // x goes out of scope after foo returns
}

var barWithX = foo();
barWithX(); // we can still access x

上面的例子中,当foo() 被调用时,其上下文在函数 bar() 中被截获。所以在它返回之后, bar() 仍然可以访问和修改变量 x。函数 foo(),其上下文在另一个函数中被截获,叫做闭包。

  • 私有数据
    让我们做一些有意思的事,比如定义private变量,让它只对特定的函数可见:
function makeCounter() {
    var counter = 0;

    return {
        value: function () {
            return counter;
        },
        increment: function () {
            counter++;
        }
    };
}

var a = makeCounter();
var b = makeCounter();

a.increment();

console.log(a.value());
console.log(b.value());

输出

1
0

当 makeCounter() 被调用时,这个函数的快照会被保存。所有其内部的代码在执行过程中都使用这份快照。两次调用创建两个不同的快照,每个快照都有自己的counter变量。

  • IIFE
    闭包也用于防止全局全名空间的污染,通常通过IIFE做到这一点。
    IIFE是特殊的闭包,在声明之后立即调用。
    假设我们想用 “$” 引用 jQuery,考虑下面的方法,不用IIFE。
var $ = jQuery;
// we‘ve just polluted the global namespace by assigning window.$ to jQuery

下面的例子中,IIFE用于确保 “$” 绑定到了闭包创建的 jQuery。

(function ($) {
    // $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn‘t exist, so no pollution

Hoisting

Hoisting是这样一个机制,把所有变量和函数的声明移动到它们的scope的顶部,但是,赋值仍然发生在声明的地方。比如:

    console.log(foo);
    // - > undefined
    var foo = 42;
    console.log(foo);
    // - > 42

上面的代码相当于:

    var foo; // Hoisted variable declaration
    console.log(foo);
    // - > undefined
    foo = 42; // variable assignment remains in the same place
    console.log(foo);
    // - > 42

注意因为上面的undefined 与运行下面代码得到的 not defined 不同:

    console.log(foo);
    // - > foo is not defined 

类似的理念也适用于函数。当函数被赋值给一个变量时,变量的声明就被hoisted了,而赋值还发生在原处。下面的两块代码是一样的:

    console.log(foo(2, 3));
    // - > foo is not a function
    var foo = function(a, b) {
        return a * b;
    }
    var foo;
    console.log(foo(2, 3));
    // - > foo is not a function
    foo = function(a, b) {
        return a * b;
    }

当声明function statements时,是不同的场景,不像function statements,函数的声明在它自己的scope内被hoisted,看下面的代码:

    console.log(foo(2, 3));
    // - > 6
    function foo(a, b) {
        return a * b;
    }

上面的代码与下面的相同:

    function foo(a, b) {
        return a * b;
    }
    console.log(foo(2, 3));
    // - > 6

下面的例子解释了hoisting是什么,hoisting不是什么

// (A) valid code:
foo();

function foo() {}


// (B) **invalid**:
bar(); TypeError: bar is not a function
var bar = function () {};


// (C) valid:
foo();
function foo() {
    bar();
}
function bar() {}


// (D) **invalid**:
foo();
function foo() {
    bar(); // TypeError: bar is not a function
}
var bar = function () {};


// (E) valid:
function foo() {
    bar();
}
var bar = function(){};
foo();
<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    JavaScript 的closure 和 hoisting