ES5和ES6作用域
ES5的块级作用域
ES5的块级作用域是一个伪块级作用域,代码块:{},它的块里面和块外面都是共用一个作用域,即:
Example:
{
var a =3;
console.log(a); //结果为3
}
console.log(a); //结果为3
Example:
ES5的伪块级作用域另外一个典型的例子如下:
var a = [];
for(var i=0;i<10;i++){
a.push(function(){
console.log(i);
});
}
a[0](); //预期结果是0,实际结果是10
注意:出现上述现象的主要原因是使用var定义的变量 i 是一个全局变量,导致真正在调用的时候虽然循环了10次,可是最终十次都是只修改了全局变量 i ,并没有区分出私有作用域。与之相反的两个例子如下:
Example1: 这个是利用ES6的let申明的变量会限制在当前块级作用域内,每次重新申明了变量,申明了10次
var a =[];
for(let i=0;i<10;i++){
a.push(function(){
console.log(i);
});
}
a[0](); //结果为10
Example2: 这个是利用构建闭包来生成私有作用域
var a = [];
for(var i=0;i<10;i++){
a.push((function($i){//$i是该匿名函数的入参,等同下面的i
function k(){
console.log($i);
}
return k;
})(i));
}
a[0](); //结果为0
注意:如果上面的Example2不使用参数传递的方式创建私有作用域,那也可以内部定义变量的方式来代替,值得注意的是如果是直接引用外部变量创建的结果最终是一个空作用域,并不能达到隔变量 i 的效果
Example3:错误例子
var a = [];
for(var i=0;i<10;i++){
a.push((function(){//$i是该匿名函数的入参,等同下面的i
function k(){
console.log(i);
}
return k;
})());
}
a[0](); //结果为10
ES5中的块级作用域有效的只有try catch,如:
Example:
try{
throw 3;
}catch(e){
console.log(e); //结果为3
}
console.log(e); //结果为ReferenceError
注意:正因为try catch有如上的功能,所以有时候有些框架为了区分块级 私有 作用域就是用它来解决的,例如google的Traceur项目。
综上所述:ES5伪块级作用域的问题是没有创建私有作用域,污染了它的父级甚至可能是全局作用域覆盖window对象的原生方法或属性。
ES5函数作用域
ES5虽然不存在真正意义上的块级作用域,但是存在函数作用域,为了解决上述ES5伪块级作用域的问题,使用函数解决法如下:
Exapmle1
var a = 1;
function fun(){
var a = 5;
console.log(a); //结果为5
}
fun();
console.log(a); //结果为1
注意:以上生成函数作用域的写法存在两个问题,第一:申明了全局的具名函数fun,污染了全局作用域。第二:如果要实现这个私有作用域,必须要函数调用才行。
Example2:改良上述两个问题的写法如下:
var a = 1;
(function (){
var a =5;
console.log(a); //结果为5
})();
console.log(a); //结果为1
注意:例子2的写法在JS社区规定的术语是立即执行函数表达式——IIFE,它可以无伤地创建一个块级私有作用域替代ES5的伪块级作用域,还有虽然它般用和闭包结合使用,但注意和闭包的区分。
立即执行函数表达式另一个常用技巧
倒置立即执行函数代码的运行顺序,将需要运行的函数放在第二位,在立即执行函数执行之后当作参数传递进去。这种模式在UMD(Universal Module Definition)项目中被广泛使用。
Example
var a=2;
(function IIFE( def ){
def( window );
})(function def( global ) {
var a=3;
console.log( a ); // 3
console.log(global.a); //2
});
代码解读:例子中将代码主体放在了立即执行函数的参数部分,当做参数传递给执行函数,执行函数的内部又立即调用了这个函数,并将全局的window对象当做参数传递给参数主体函数,并匿名window为global来调动全局a变量,达到不会与参数主体函数的作用域冲突。