JavaScript学习笔记3--函数作用域与块作用域

  |  

前言
这部分继续《你不知道的JavaScript》的第三章,函数作用域和块作用域


13



函数中的作用域

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。这种设计方案是非常有用的,能充分利用JavaScript便来给你可以根据需要改变值类型的“动态”特性

需要强调的是,外部作用域访问不了内部作用域中的变量,但是内部作用域能访问外部作用域中的变量,直到最外部的全局作用域

隐藏内部实现

使用基于作用域的隐藏方法多是从最小特权原则中引申出来的。

最小特权原则:在软件设计汇总,应该最小限度的暴露必要内容,而将其它内容都“隐藏”起来,不如某个模块或对象的API设计。

这个原则可以延申到如何选择作用域来包含变量和函数。如果所有变量和函数都在全局作用域中,当然可以在所有的内部嵌套作用域中访问到它们。但这样会破坏前面提到的最小特权原则,因为可能会暴露过多的变量或函数,而这些变量或函数本应该是私有的,正确的代码应该是可以阻止这些变量或函数进行访问。

规避冲突

“隐藏”作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突,两个标识符可能具有同样的名字但用途却不一样,无意间可能造成命名冲突。冲突会导致变量的值被意外覆盖。

全局命名空间

变量冲突的一个典型例子存在与全局作用域中。当程序中加载了多个第三方库时,如果他们没有妥善地将内部私有的函数或变量隐藏起来,就会容易引发冲突。

这些库通常会在全局作用域中声明要给名字足够独特的变量,通常是一个对象。这个对象被用作库的命名空间,所有需要暴露给外界的功能都会成为这个对象属性,而不是将自己的标识符暴露在顶级的词法作用域中

模块管理

从众多模块管理器中挑选要给使用。使用这些工具,任何库都无需将标识符加入到全局作用域中,而是通过依赖管理器的机制将库的标识符显示地导入到另外一个特定的作用域。

函数作用域

区分函数声明和表达式最简单的方法是看function关键字出现在声明中的位置。如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。

匿名和具名

匿名函数表达式示例:

1
2
3
setTimeout( function() {
console.log("I waited 1 second!");
}, 1000);

函数表达式可以是可以是匿名的,而函数声明则不可以省略函数名

匿名函数表达式书写起来快捷方便,但是它有几个无法避免的缺点:

  • 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难
  • 如果没有函数名,函数在自身递归调用时只能使用已经过期的arguments.callee引用
  • 匿名函数省略了对于代码可读性/可理解性很重要的函数名

行内函数表达式非常强大且有用——匿名和具名之间的区别并不会对这点有任何影响。函数表达式指定一个函数名可以解决上述问题,且不会污染作用域。

立即执行函数表达式

立即执行函数表达式示例

1
2
3
4
(function foo() {
var a = 2;
console.log(a);
})();

社区给这种形式规定了要给术语:IIFE,代表立即执行函数表达式(Immediately Invoked Function Expression)

函数名对IIFE当然不是必须的,IIFE最常见的用法是使用一个匿名函数表达式。虽然使用剧名函数的IIFE并不常见,但它具有上述匿名函数表达式的所有优势。

相较于传统的IIFE,还有一种改进形式(function(){ .. }())。他将用来调用的()括号移进了用来包装的()括号中。

这两种形式在功能上是一致的

IIFE的另一个非常普遍的进阶用法是把他们当作函数调用并传递参数进去。

IIFE还有一种变化的用途是倒置代码的运行顺序,将需要运行的函数放在第二位在IIFE执行之后当作参数传递进去。这种模式在UMD(Universal Module Definition)项目中被广泛使用。尽管这种模式略显冗长,但有些人认为它更容易理解

1
2
3
4
5
6
7
8
var a = 2;
(function IIFE(def){
def(window);
})(function def(global) {
var a = 3;
console.log(a); //3
console.log(global.a); //2
})

函数表达式定义在片段的第二部分,然后作为参数(这个参数也叫做def)被传递进IIFE函数定义的第一部分。最后,参数def(也就是传递进去的函数)被调用,并将window传入当作global参数的值。

块作用域

with

用with从对象中创建出的作用域尽在with声明中而非外部作用域中有效

try/catch

ES3规范中规定try/catch分句会创建要给块作用域,其中声明的变量仅在catch内部有效

1
2
3
4
5
6
try {
undefined();//执行一个非法操作来强制创造一个异常;
} catch(err) {
console.log(err); //能过正常执行
}
console.log(err); //ReferenceError: err not found

err仅存在catch分句内部

let

ES6引入了新的let关键字,提供了除var以外的另一种变量声明方式。

let关键字可以将变量绑定到所在的任意作用域中(通常是{…}内部)。用let将变狼附加在一个已经存在的块作用域上的行为是隐式的。

使用let进行声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不存在

const

除了let以外,ES6还引入了const,同样可以用来创建块作用域变量,但其值是固定的(常量)。之后任何视图修改值的操作都会引起错误。

文章目录
  1. 1. 函数中的作用域
  2. 2. 隐藏内部实现
    1. 2.1. 规避冲突
      1. 2.1.1. 全局命名空间
      2. 2.1.2. 模块管理
  3. 3. 函数作用域
    1. 3.1. 匿名和具名
    2. 3.2. 立即执行函数表达式
  4. 4. 块作用域
    1. 4.1. with
    2. 4.2. try/catch
    3. 4.3. let
    4. 4.4. const