闭包(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();