函数作用域
Javascript具有基于函数的作用域,每声明一个函数,都会产生一个对应的作用域。
//全局作用域包含f1 function f1(a) { var b = 1; //f1作用域包含a,b,f2 function f2() { //f2有自己的 //...代码 } return a + b; } //无法从外部访问内部作用域 console.log(b); //error
IIFE
如果需要封装某些变量,但同时不想多出一个函数名与调用函数,可以使用IIFE,立即执行函数。
var a = 1; //为了封装a变量需要声明一个函数并同时调用 function fn() { var a = 2; console.log(a); //2 } fn(); console.log(a); //1 //可以使用IIFE (function() { var a = 2; console.log(a); //2 })();
当函数被括号包起来时,被当成一个函数表达式了,所以可以立即执行,区分函数声明和函数表达式最简单的方法就是看function关键字是否是第一个词。
console.log(fn()); //1 function fn() { return 1; } console.log(fn2()); //error //匿名函数表达式 不会提升声明 (function fn2() { return 1; })
匿名函数表达式使用很方便,但是也有几个缺点。
1、调试时无法根据函数名进行追踪。
2、没有函数名,调用自身只能使用被废弃的arguments.callee。
3、缺失可读性。
所以说,作者建议给匿名函数加个函数名就可以了。
IIFE还有另外一种改进形式。
(function() { var a = 1; console.log(a); //1 }());
这两种功能上是一致的。
IIFE的一个非常普遍的进阶用法是把它们当做函数调用并传递参数进去。
var a = 2; (function(global) { var a = 1; console.log(a); //1 //将传进去的window更名为global //使得语义更明确 并且解析全局变量时不会一层层作用域查找 console.log(global.a); //2 }(window));
另外一个应用场景是解决undefined标识符的默认值被错误覆盖。(测试不出undefined可以被改变值,暂不贴代码)
IIFE还有一种变化的用途是倒置代码的运行顺序。将需要运行的函数放后面,IIFE执行之后当参数传进去。
var a = 2; (function IIFE(def) { def(window); })(function(global) { var a = 3; console.log(a); //3 console.log(global.a); //2 }); //相当于 (function(global) { var a = 3; console.log(a); //3 console.log(global.a); //2 })(window);
jQuery源码就是用这种形式,当初一脸懵逼,根本看不懂。。。
块作用域
除Javascript外很多编程语言都支持块作用域。
//块状作用域 for (var i = 0; i < 10; i++) { console.loe(i); }
然而,JS并没有,表面上没有。
1、with语句会创建出块级作用域。
2、try/catch的catch分句会创建一个块级作用域。
let
ES6引入了let关键字,提供了另外一种变量声明方式。
let关键字可以将变量绑定到所在的任意作用域中,简单来说,就是提供了块级作用域。
{ var a = 1; } console.log(a); //1 { let b = 1; } console.log(b); //error
let还有两个特性,首先,不会有变量提升。
{ console.log(a); //error let a = 1; }
第二个就是暂时性死区。
简单来说,就是在有let的块级作用域中,变量在声明前被暂时锁住,不允许在声明前进行使用。
var a = 2; //声明全局变量 { console.log(a); //还是报错了! let a = 1; }
ES6还提供了const关键字,也可以提供块级作用域,意义参照C++。