最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,里面分析了不少基础性的概念。闭包
能够更全面深刻的理解JavaScript深层面的知识点。异步
1)函数做用域函数
就是做用域在一个“Function”里,属于这个函数的所有变量均可以在整个函数的范围内使用及复用。spa
function foo(a) { var b = 2; function bar() { // ... } var c = 3; } bar(); // 失败 console.log( a, b, c ); // 三个全都失败
上面的“foo”函数内的几个标识符,放到函数外面访问就都会报错,查看源码。.net
2)当即执行函数表达式插件
在任意代码片断外部添加包装函数,能够将内部的变量和函数定义“隐藏”起来,外部做用域没法访问包装函数内部的任何内容。3d
例如上面的bar、a等几个标识符。这样可以保护变量不被污染。code
在写插件的时候常常会用到当即执行函数表达式,为的就是保护里面的变量。blog
var a = 2; (function foo() { var a = 3; console.log( a ); // 3 })(); console.log( a ); // 2
“foo”中第一个( )将函数变成表达式,第二个( )执行了这个函数。事件
有一个专用术语:IIFE,表明当即执行函数表达式(Immediately Invoked Function Expression);
1. 进阶用法是把它们看成函数调用并传递参数进去
(function IIFE( global ) { var a = 3; console.log( a ); // 3 console.log( global.a ); // 2 })( window );
2. 一种变化的用途是倒置代码的运行顺序,在CMD或AMD项目中被普遍使用。
(function IIFE(factory) { factory( window ); })(function def( global ) { var a = 3; console.log( a ); // 3 console.log( global.a ); // 2 });
JavaScript不支持块做用域。
for(var i=0; i<10; i++) { console.log( i ); }
上面的代码中的“i”至关于下面的
var i; for(i=0; i<10; i++) { console.log( i ); }
但也有例外,“try/catch”,catch就是一个块做用域。
try{ undefined(); // 执行一个非法操做来强制制造一个异常 } catch(err) { console.log( err ); // 可以正常执行! } console.log( err ); // ReferenceError: err not found
ES6改变了现状,引入了新的let关键字,let关键字能够将变量绑定到所在的任意做用域中(一般是{ .. }内部)。换句话说,let为其声明的变量隐式地了所在的块做用域。
函数做用域和块做用域的行为是同样的,能够总结为:任何声明在某个做用域内的变量,都将附属于这个做用域。
1)编译与执行
变量和函数的全部声明都会在任何代码被执行前首先被处理,能够看下面的代码事例。
a = 2; var a; console.log(a);//2
这段代码等价于:
var a;//定义声明是在编译阶段进行 a = 2;//赋值声明会被留在原地等待执行阶段 console.log(a);
2)函数优先
函数会首先被提高,而后才是变量。
foo(); // 1 var foo; function foo() { console.log( 1 ); } foo = function() { console.log( 2 ); };
var foo函数表达式尽管出如今function foo()的声明以前,但它是重复的声明(所以被忽略了),由于函数声明会被提高到普通变量以前。
而上面的代码至关于:
function foo() { console.log( 1 ); } foo(); // 1 foo = function() { console.log( 2 ); };
1)定义
当函数能够记住并访问所在的做用域时,就产生了闭包,即便函数是在当前做用域以外执行。
function foo() { var a = 2; function bar() { console.log( a ); } return bar; } var baz = foo(); baz(); // 2 —— 这就是闭包的效果。
1. 将函数“bar”赋值给“baz”,执行“baz”,当前做用域并不在“bar”的做用域,可是能够执行。
2. 闭包还会阻止垃圾回收,当“foo”执行完后,内部做用域仍然存在。这样才能让“baz”执行。
2)将函数做为参数传递
function foo() { var a = 2; function baz() { console.log( a ); // 2 } bar( baz ); } function bar(fn) { fn(); //这就是闭包! }
把内部函数baz传递给bar,当调用这个内部函数时(fn),它涵盖的foo()内部做用域的闭包就能够观察到了,由于它可以访问a。
若是将函数看成第一级的值类型并处处传递,你就会看到闭包在这些函数中的应用。
在定时器、事件监听器、Ajax请求、跨窗口通讯、Web Workers或者任何其余的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!
3)循环和闭包
for (var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i); }, i * 1000); }
每次打印出来都将会是6,延迟函数的回调会在循环结束时才执行,查看源码。
根据做用域的工做原理,实际状况是尽管循环中的五个函数是在各个迭代中分别定义的,可是它们都被封闭在一个共享的全局做用域中,所以实际上只有一个i。
如今用闭包来实现每次打印不一样的i。
for (var i = 1; i <= 5; i++) { (function(j) { setTimeout(function timer() { console.log(j); }, j * 1000); })(i); }
IIFE会经过声明并当即执行一个函数来建立做用域。setTimeout中的回调能够记住当前的做用域,每一个做用域中的参数“j”都是不一样的。