《你不知道的JavaScript》第一部分作用域和闭包第2篇。
昨天讲到作用域,回顾下概念:作用域是一套用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找的规则。
在JS中,最常见的作用域是函数作用域,其他结构通常不会创建作用域。但随着js的迭代,现在也有了块作用域,将在后面讲到。
而函数作用域的含义是指:属于这个函数的全部变量都可以在整个函数的范围内使用及复用,包括在嵌套的函数作用域中也可以使用。
函数作用域的常规套路是,先声明一个函数,然后向函数中添加代码实现。
但这个套路反过来也是很有用,即选取所写的一部分代码用函数声明来包装,从而将这些代码中的所有变量都绑定在新创建的包装函数的作用域中,而非先前所在的作用域中,目的就是通过新建作用域隐藏这些变量,能够尽可能少的暴露变量,这是符合软件开发的最小特权原则的。除此以外,"隐藏"作用域中的变量和函数还能规避同名标识符之间可能存在的冲突问题。
函数作用域的创建需要声明一个函数,而声明函数这个行为又有函数声明和函数表达式两种操作方式。
函数声明和函数表达式的辨别,可以通过一个小技巧来一眼分辨:看function
关键字出现在声明中的位置,注意,不仅仅是一行代码,而是整个声明中的位置,如果function
是声明中的第一个词,那就是函数声明,否则就是函数表达式。
function foo(){}; //函数声明
var foo = function(){}; //函数表达式
(function foo(){}); //函数表达式
(function(){ //匿名函数表达式
});
函数声明和函数表达式的区别是它们的名称标识符将会绑定在何处。举个例子:
var a = 10;
function foo(a){
var b = a * 2;
return b;
}
console.log(foo); //正确打印foo函数
foo();
(function fn(a){
var c = a + 10;
console.log(fn); //正确打印fn函数
return c;
})();
console.log(fn); //ReferenceError: fn is not defined
上例中,假设代码所处作用域为全局作用域,foo
函数的访问作用域是全局作用域,fn
函数的访问作用域被绑定在函数表达式自身的函数中而非所在的全局作用域。此时,fn
变量被隐藏在自身作用域中就意味着不会非必要的污染外部作用域。
在前文的函数表达式举例中,我还列出了匿名函数表达式,这种函数表达式的常用之地是回调函数,它是没有名称标识符的。函数表达式可以省略函数名,但函数声明则不可以省略函数名,否则会报错。
匿名函数表达式的应用非常常见,很多工具或库都有用到,但其也存在几个缺点:
arguments.callee
,但这个已非官方推荐实践,将被彻底废弃;正是由于以上三个缺点,所以比较推荐为匿名函数表达式加了名称标识符,这个操作不会对代码实现有任何影响,还能一举解决上面三个缺点,何乐不为:
setTimeout(function foo(){
console.log("哈哈,我有名称了.")
}, 1000)
//1秒后打印:
//哈哈,我有名称了.
在ES5及之前版本中,js中的块作用域形同于无,实在要说的话,也只有try-catch
中的catch
部分定义的变量所在作用域是catch块中的,其他的都只是样子像,而本质上都不是块作用域,例如
for(var i=0; i<10; i++){
console.log(i);
}
console.log('外部:'+i);
// 外部:10
上例外部作用域可以访问到i
的值为10
。
但在ES6版本开始,有了let
和const
,终于可以明目张胆的定义块级作用域了,想必用惯了其他语言块级作用域的同学,心里的别扭终于可以舒口气了吧。
let
关键字可以将变量绑定到所在的任意作用域中,通常是{...}
内部,也就是说,let
关键字为其声明的变量隐式的定义了所在的块级作用域。
let
关键字发挥作用的典型在于for
循环。
for(let i=0; i<10; i++){
console.log(i);
}
console.log(i); //ReferenceError:i is not defined
你看,在外部作用域访问变量标识符i
时,就直接报未定义的语法错误。
事实上,for循环头部的let不仅将i绑定到for循环的块中,在每次循环开始时,还将其重新绑定到新的循环迭代中去,确保使用上一个循环迭代结束时的值重新赋值。
至于const
也是可以创建块作用域中,不同于let
的是,其值是固定的常量,任何对其值的修改都会引起错误。
js中的作用域,主要有函数作用域和块级作用域,当然还有全局作用域。
函数作用域的使用,可以隐藏代码实现,减少变量暴露,避免命名冲突,符合软件设计的最小特权原则。关于函数作用域,还讲了函数声明与函数表达式的辨别方法和区别。在函数表达式中,还分出了命名函数表达式和匿名函数表达式。
块级作用域的实现,有赖于ES6的版本进步,提供let
和const
关键字,可以实现同其他语言相同的由{...}
包裹起来的块级作用域。比较典型的就是let
版的for循环和var
版的for循环,感兴趣的可以自行了解。
-------------------------------- 热门文章 --------------------------------