我不知道的js-函数作用域、块作用域、变量提升

175 阅读3分钟
原文链接: qxfuture.com

函数作用域说明

任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。

立即执行函数(IIFE)

(function foo() {})();(function foo() {}()); 是最常见的IIFE,其实还有其方式的IIFE,比如可以用 ! ~ 等等这些运算符。

立即执行函数传参

可以向IIFE传递参数,如下

(function IIFE( win ) {

})( window );

因此在代码风格上对全局 对象的引用变得比引用一个没有“全局”字样的变量更加清晰,其次还能减少作用域查找变量(RHS)的次数。

这个模式的另外一个应用场景是解决undefined 标识符的默认值被错误覆盖导致的异常,将一个参数命名为undefined,但是在对应的位置不传入任何值,这样就可以 保证在代码块中 undefined标识符的值真的是undefined

undefined = true; // 这样会把 undefined 覆盖
(function IIFE( undefined ) {
    var a;
    if (a === undefined) {
        console.log( "Undefined is safe here!" );
    }
})();

块作用域说明

在ES6之前javascript严格来说并没有块级作用域,或者说它某些语法可以生成块级作用域,js仅可以通过withtry/catch产生块级作用域。
with 从对象中创建出的作用域仅在 with 声明中而非外部作用域中有效。
ES3规范中规定try/catchcatch 分句会创建一个块作用域,其中声明的变量仅在catch内部有效。
在es6出来后let为其声明的变量隐式地了所在的块作用域。

块作用域垃圾收集

function process(data) {
// do somethings
}
var someReallyBigData = { a:1 };
process( someReallyBigData );
var btn = document.getElementById( "my_button" );
      btn.addEventListener( "click", function click(evt) {
          console.log("button clicked");
}, /*capturingPhase=*/false );

click 函数的点击回调并不需要 someReallyBigData 变量。理论上这意味着当 process(..) 执 行后,在内存中占用大量空间的数据结构就可以被垃圾回收了。但是,由于 click 函数形成 了一个覆盖整个作用域的闭包,JavaScript 引擎极有可能依然保存着这个结构(取决于具体 实现)。

块作用域可以打消这种顾虑,可以让引擎清楚地知道没有必要继续保存someReallyBigData了,如下:

function process(data) {
// 在这里做点有趣的事情
}
// 在这个块中定义的内容可以销毁了! {
let someReallyBigData = { .. }; process( someReallyBigData );
}
var btn = document.getElementById( "my_button" );
     btn.addEventListener( "click", function click(evt){
         console.log("button clicked");
}, /*capturingPhase=*/false );

变量提升

函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个
“重复”声明的代码中)是函数会首先被提升,然后才是变量。
无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。 可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的 最顶端,这个过程被称为提升。
声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。

参考文献

你不知道的js