一、声明提升
声明提升有变量声明提升和函数声明提升,只有声明本身会被提升,而赋值或其他运行逻辑会留在原地.
1、变量声明提升
var a = 2; //JavaScript 实际上会将其看成两个声明:var a; 和 a = 2; function foo() { console.log( a ); // undefined var a = 2; }
2、函数声明提升
foo(); // TypeError bar(); // ReferenceError var foo = function bar() { // ... }; //函数声明会被提升,但是函数表达式却不会被提升 //声明后 var foo; foo(); // TypeError bar(); // ReferenceError foo = function() { var bar = ...self... // ... }
3、函数首先被提升,然后才是变量
foo(); // 1 var foo; function foo() { console.log( 1 ); } foo = function() { console.log( 2 ); }; //会输出 1 而不是 2 !这个代码片段会被引擎理解为如下形式: function foo() { console.log( 1 ); } foo(); // 1 foo = function() { console.log( 2 ); };
二、作用域
函数作用域
var a = 2; //函数作用域和块作用域 | 27 (function foo(){ // <-- 添加这一行 var a = 3; console.log( a ); // 3 })(); // <-- 以及这一行 console.log( a ); // 2
三、闭包
1、闭包的理解
function foo() { var a = 2; function bar() { console.log( a ); } return bar; } var baz = foo(); baz(); // 2 —— 朋友,这就是闭包的效果。
函数 bar() 的词法作用域能够访问 foo() 的内部作用域。然后我们将 bar() 函数本身当作 一个值类型进行传递。
在这个例子中,我们将 bar 所引用的函数对象本身当作返回值。 在 foo() 执行后,
其返回值(也就是内部的 bar() 函数)赋值给变量 baz 并调用 baz(),
实 际上只是通过不同的标识符引用调用了内部的函数 bar()。
bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方执行。
2、闭包随处可见
function wait(message) { setTimeout( function timer() { console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
将一个内部函数(名为 timer)传递给 setTimeout(..)。timer 具有涵盖 wait(..) 作用域 的闭包,因此还保有对变量 message 的引用。
function setupBot(name, selector) { $( selector ).click( function activator() { console.log( "Activating: " + name ); } ); } setupBot( "Closure Bot 1", "#bot_1" ); setupBot( "Closure Bot 2", "#bot_2" );
在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!
四、循环和闭包
循环时,我们试图假设循环中的每个迭代在运行时都会给自己“捕获”一个 i 的副本。但是 根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的, 但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。我们需要更多的闭包作用域,特别是在循环的过程中每个迭代都需要一个闭包作用域。
for (var i=1; i<=5; i++) { (function(j) { setTimeout( function timer() { console.log( j ); }, j*1000 ); })( i ); } //在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的 作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。
for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); } //很酷是吧?块作用域和闭包联手便可天下无敌。