函数作用域说明
任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。
立即执行函数(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仅可以通过with
和try/catch
产生块级作用域。
用 with
从对象中创建出的作用域仅在 with 声明中而非外部作用域中有效。
ES3规范中规定try/catch
的catch
分句会创建一个块作用域,其中声明的变量仅在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 );
变量提升
函数声明和变量声明都会被提升。但是一个值得注意的细节(这个细节可以出现在有多个
“重复”声明的代码中)是函数会首先被提升,然后才是变量。
无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。 可以将这个过程形象地想象成所有的声明(变量和函数)都会被“移动”到各自作用域的 最顶端,这个过程被称为提升。
声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。