【书名】:你不知道的JavaScript(上卷)
【作者】:Kyle Simpson
【本书总页码】:213
【已读页码】:52
1. 函数中的作用域
JavaScript 具有基于函数的作用域。函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。
从所写的代码中挑选出一个任意的片段,然后用函数声明对它进行包装,实际上就是把这些代码“隐藏”起来了。实际的结果就是在这个代码片段的周围创建了一个作用域气泡,也就是说这段代码中的任何声明(变量或函数)都将绑定在这个新创建的包装函数的作用域中。换句话说,可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来“隐藏”它们。
这里提一下最小暴露原则。这个原则是指在软件设计中,应该最小限度地暴露必要内容,而将其他内容都“隐藏”起来,比如某个模块或对象的 API 设计。
“隐藏”作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突,两个标识符可能具有相同的名字但用途却不一样,无意间可能造成命名冲突。冲突会导致变量的值被意外覆盖。
2. 避免变量冲突的方式
2.1. 全局命名空间
在全局作用域中声明一个名字足够独特的变量,通常是一个对象。这个对象作为命名空间,所有需要暴露给外界的功能都会成为这个对象(命名空间)的属性,而不是将自己的标识符暴漏在顶级的词法作用域中。
2.2. 模块管理
另外一种避免冲突的办法和现代的模块机制很接近,就是从众多模块管理器中挑选一个来使用。使用这些工具,任何库都无需将标识符加入到全局作用域中,而是通过依赖管理器的机制将库的标识符显式地导入到另外一个特定的作用域中。
3. 函数表达式
3.1. 匿名函数表达式存在的问题:
1. 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。
2. 如果没有函数名,当函数需要引用自身时只能使用已经过期的arguments.callee引用,比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身。
3. 匿名函数省略了对于代码可读性/可理解性很重要的函数名。
给函数表达式指定一个函数名可以有效解决以上问题。始终给函数表达式命名是一个最佳实践。
3.2. 立即执行函数表达式(IIFE)
可以把IIFE 当作函数调用并传递参数进去;IIFE 还有一种变化的用途是倒置代码的运行顺序,将需要运行的函数放在第二位,在 IIFE执行之后当作参数传递进去。
4. 块作用域
4.1. with
用 with 从对象中创建出的作用域仅在 with 声明中而非外部作用域中有效。
4.2. try/catch
try/catch 的 catch 分句会创建一个块作用域,其中声明的变量仅在 catch 内部有效。
4.3. let
let 关键字可以将变量绑定到所在的任意作用域中,为其声明的变量隐式地定义了所在的块作用域。
用 let 将变量附加在一个已经存在的块作用域上的行为是隐式的。在开发和修改代码的过程中,如果没有密切关注哪些块作用域中有绑定的变量,并且习惯性地移动这些块或者将其包含在其他的块中,就会导致代码变得混乱。
为块作用域显式地创建块可以部分解决这个问题,使变量的附属关系变得更加清晰。通常来讲,显式的代码优于隐式或一些精巧但不清晰的代码。使用的方式是添加{},并在其中使用 let 声明。
let 进行的声明不会在块作用域中进行提升。
1. let用于内存垃圾收集
块作用域非常有用的原因和闭包及回收内存垃圾的回收机制相关。为变量显式声明块作用域,并对变量进行本地绑定,可以在闭包作用域存在的情况下进行垃圾回收。
2. let循环
for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。
4.4. const
const同样可以用来创建块作用域变量,但其值是固定的(常量)。之后任何试图修改值的操作都会引起错误。
网友评论