函数中的作用域
JS 具有基于函数的作用域,这意味着每一个函数都会为自己创建一个作用域。
内部作用域可以访问外部作用域的变量,但是外部作用域的变量无法访问内部作用域的变量
隐藏内部实现
可以将变量或者函数包裹在函数中,可以做到对函数外部的作用域隐藏这个变量或函数。
为什么要这样做? --- 最小暴露原则
规避冲突
隐藏作用域的另一个好处 - 防止同名标识符之间的冲突
命名空间
很多第三方的库都只会暴露一个全局的对象,然后将属性或者方法定义在这个全局的对象中
模块化
ES6 import/export/export default
函数作用域
在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。
这个方式可以解决一些问题,但是并不理想:
-
需要声明一个函数,但这个函数的函数名会污染所在作用域
-
必须要调用这个函数才能执行内部的代码
所以,综上:
如果函数不需要变量名且可以自动运行,那么效果将更加理想(立即执行函数)
区分函数声明和表达式最简单的方法是看 function 关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果 function 是声明中 的第一个词,那么就是一个函数声明,否则就是一个函数表达式。
function foo() {} // 函数声明
(function foo() {}); // 函数表达式,声明中的第一个词是()
匿名和具名
函数表达式可以是匿名的,但是函数声明则不可以省略函数名
匿名函数
缺点:
-
匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难
-
如果没有函数名,当函数需要引用自身时只能使用已经过期的 arguments.callee 引用, 比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身。
-
匿名函数省略了对于代码可读性 / 可理解性很重要的函数名。
我们也可以始终给函数表达式一个名字
立即执行函数表达式
由于一个函数被包裹在()内,此时就变成了一个表达式,()外面再加上一个()则表示立即执行该函数。
比如 (function foo(){ .. })()
。第一个 ( )
将函数变成表 达式,第二个 ( )
执行了这个函数。
很多人都更喜欢另一个改进的形式:(function(){ .. }())
。
两者功能完全一致
立即执行函数也可以在调用的时候传参
块级作用域
with
with 从对象中创建出的作用域仅在 with 声明中而非外 部作用域中有效。
try/catch
try/catch 的 catch 分句会创建一个块作用域,其中声明的变量仅在 catch 内部有效。
try {
undefined();
} catch (e) {
var err = e;
console.log(err); // TypeError: undefined is not a function
}
console.log(err); // TypeError: undefined is not a function
console.log(e); // ReferenceError: e is not defined
注意,仅仅是这里的 catch 的 e 参数是局部有效的,但是 catch 块中定义的 var 变量还是全局的
但是当同一个作用域中的两个或多个 catch 分句 用同样的标识符名称声明错误变量时,很多静态检查工具还是会发出警告。不允许重复定义变量
let
let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。换句话说,let 为其声明的变量隐式地了所在的块作用域。
但是使用 let 进行的声明不会在块作用域中进行提升。声明的代码被运行之前,声明并不 “存在”。
const
const,同样可以用来创建块作用域变量,但其值是固定的 (常量)。之后任何试图修改值的操作都会引起错误。
小结
函数(本身就是)和块(需要使用 let/const 定义变量)都可以形成作用域。可以很好的将变量/函数隐藏在块作用域中
网友评论