第三章 函数作用域和块作用域
3.函数作用域和块作用域
3.1 函数中的作用域
JavaScript 具有基于函数的作用域。
函数作用域: 属于这个函数的全部变量走可以在整个函数的范围内使用及复用(嵌套的作用域也可以使用)。
3.2 隐藏内部实现
变量和函数被包裹在一个函数的作用域中,然后用这个作用域来隐藏变量和函数。所谓隐藏,体现了函数的封装性。
“隐藏”变量和函数是个有用的技术 原因:
- 由最小授权或最小暴露原则促成。 这个原则是指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都隐藏起来,比如某个模块或对象的API设计。
- 避免同名标识符之间的冲突,冲突会导致变量的值会被意外覆盖。
- 当程序中加载了多个第三方库时,如果他们没有妥善的将内部私有的函数或变量隐藏起来,就会很容易引发冲突。
这些库通常会在全局作用域中声明一个名字足够独特的变量,通常是个对象。这个对象被用作明明空间。所有需要暴露给外界的功能都会成为这个对象的属性,而不是将自己的标识符暴露在顶级的词法作用域中。例如
varPeople={
name ="Snowden",
age =30,
doSomething:function(){
// ...
- }
};
- 模块管理。从众多模块管理器中挑选一个来使用。使用这些工具,任何库都无需将标识符加入到全局作用域中,而是通过依赖管理器的机制将库的标识符显式的导入到另外一个特定的作用域中。
3.3 函数作用域
在任意代码片段的外部添加包装函数,可以将内部的变量核函数定义“隐藏起来”,外部作用域无法访问包装函数内部的任何内容。
会出现的问题:
必须要声明一个具名函数,这样会污染所在的作用域,另外,必须显式的通过函数名调用这个函数才能运行其中的代码。
解决方案:
- // 片段1
var a =2;
(function foo(){
// ...
var a =3;
console.log(a);// 3
})();
console.log(a);// 2
此处 foo: 函数会被的当做函数表达式处理,而不是一个标准的函数声明来处理。
函数声明和函数表达式的区别: 标识符会绑定在何处
// 片段2
var a =2;
function foo(){
var a =3;
console.log(a);// 3
}
foo();
console.log(a);// 2
片段2:foo被绑在所在作用域中,可以通过foo()来调用。
片段1:foo被绑定在函数自身的函数中,意味着foo只能在"..."所代表的位置中访问,外部作用域不能访问。==>不会非必要的污染外部作用域。
3.3.1 匿名和具名
- setTimeout(function(){
- console.log('just wait one second');
- },1000);
setTimeout里匿名表达式。缺点:
- 匿名函数中在栈追踪中不会显示出有意义的函数名,调试困难。
- 引用自身时,只能使用过期的arguements.callee()引用,比如递归。引用自身的例子:在事件触发后监听器需解绑自身。
- 描述性的名称可以让代码不言自明。
行内函数表达式强大并有用:可以给函数表达式命名来解决上述问题。
3.3.2 立即执行函数表达式
IIFE: Immediately Invoked Function Expression
上述片段1为立即执行函数,格式同。
用法:
- 使用一个匿名函数表达式
- var a = 2;
- (function IIFE(){
- var a = 3;
- console.log(a); //3
- })();
- 把他们当做函数调用并传递参数进去
- 倒置代码的运行顺序,将需要运行的函数放在第二位,在IIFE执行之后当做函数传递进去。
- var a = 2;
- (function IIFE(def){
- def(window);
- })(function def(global){
- var a = 3;
- console.log(a);// 3
- console.log(global.a);// 2
- });
3.4 块作用域
很多编程语言都普遍支持块作用域。
- for(var i=0; i<10; i++){
- console.log(i);
- }
i 会绑定在外部作用域(函数或全局),会造成污染。(见下文用let改造)
3.4.1 with
也是块作用域。用with从对象中创建出的作用域仅在with声明中有效。
3.4.2 try/catch
当同一个作用域中的两个或多个catch分别用同样的标识符声明错误变量时,很多静态检查工具会发出警告,一般将catch的参数命名为err1,err2等。
3.4.3 let
ES6引入了let关键字。提供了除var以外的另一种变量声明方式。
let 关键字可以将变量绑定到所在的任意作用域中(通常是{..}内部),即let为其声明的变量隐式地放在了所在的块作用域。let声明附属于一个新的作用域而不是当前的函数作用域。
隐式的缺点:代码混乱
解决:显式创建
//显式
var foo = true;
if(foo){
{ // 显式
let bar = foo*2;
bar = something(bar);
console.log(bar)
- }
}
console.log(bar);// ReferenceError
使用let 进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明不会存在。
改造3.4块作用域
- for(let i = 0; i<10;i++){
- console.log(i);
- }
不会造成污染。
3.4.4 const
ES6引入的用于创建块作用域的变量,其值是常量。任何试图修改值的操作都会引起错误。