学习自《你不知道的javascript》
一、函数作用域
1.1 规避冲突
|
|
上面的代码会造成无限循环,为什么呢?
for
循环第一次调用bar()
时,i
值是 0 传入bar()
中,而bar()
第一句就对i
进行了重新赋值为3,然后继续循环调用bar()
,i
值始终为3不会改变,进入无限循环。
规避冲突的方法
使用第三方库时,为了避免和其他库冲突,通常在全局作用域中声明一个独特名字的变量,一般是对象;这个对象是库的命名空间,所有需要暴露给外界的功能都会成为该对象的属性,而不是把自己的标识符暴露在顶级的词法作用域中。
模块管理。和现代的模块机制很接近,就是从众多的模块管理器中挑选一个来使用。利用作用域的规则强制所有标识符都不能注入到共享作用域中,而是保持在私有、无冲突的作用域中。
1.2 函数作用域深入
函数有两种申明方式,一种是函数声明,另一种是函数表达式。
如何区分呢?
看function
关键字是不是声明时的第一个词,是的话就是函数申明,不是的话就叫函数表达式。匿名函数和具名函数
|
|
以上代码内的函数就是匿名函数表达式。函数表达式可以省略函数名,但是函数申明不能省略函数名。
匿名函数的缺点:
- 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试困难。
- 没有函数名,难以引用自身。比如在递归中和事件触发后事件监听器需要解绑自身。
- 可读性/可理解性差。
立即执行函数表达式
|
|
上述代码第一个()
将函数变成表达式,第二个()
执行了这个函数。这种模式有个专业术语叫IIFE。该模式还有一个变种:
IIFE普遍的进阶方法是把它们当做函数调用并传参进去。参照下面的代码,其实就是把window
对象当做参数传进函数内。
IIFE还有另一种进阶方法,即倒置代码的运行顺序,将需要运行的函数放在第二位,在IIFE执行后当作参数传进去。
1.2 块作用域
先看一段代码:
结合上段代码可以看到,定义在for
循环中的变量i
被绑定到了全局作用域上,成为了全局作用域的属性。
这就是块作用域的用处。变量的声明应该距离使用的地方越近越好,并最大限度地本地化。
另一个例子:
在上面的例子中,bar
变量仅仅在if
声明的上下文中使用,因此如果能将它声明在if
块内部中会是一个很有意义的事情。但是,当使用var
声明变量时,它写在哪里都是一样的,因为它们最终都会属于外部作用域。其实就是无法将bar
变量限制在if
判断的作用域内,使得外部无法访问。
开发过程中,一些只在特定作用域内才用到的变量等,可能最终成为了外部甚至全局作用域的属性,这样容易污染全局。如果存在块作用域这些将不是烦恼,但可惜,js并没有像其他语言一样直接提供块作用域,但依然可以通过一些方法来实现块作用域。
1.2.1 with
1.2.2 try/catch
try/catch
的catch
分句会创建一个块作用域,其中声明的变量仅在catch
内部有效。
例如:
1.2.3 let
let
关键字可以将变量绑定到所在的任意作用域中(通常是{...}
内部)。换句话说,let
为其声明的变量隐式地劫持了所在的块作用域。
例子:
用let
将变量附加在一个已经存在的块作用域上的行为是隐式的。如果害怕代码变得混乱的话可以显式的创建块。上面的代码改为:
但是使用let
进行的声明不会在块作用域中进行提升。声明的代码被运行前,声明并不“存在”
例子:
let
在循环里也有着非常大的优势。
for
循环头部的let
不仅将i
绑定到for
循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。
类似于:
1.2.4 const
const
也同样可以用来创建块作用域变量,但是其值是固定的。
|
|