You Don't Know JS: Scope & Closures (第3章: 函数 vs 块作用域)

第二章,作用域由一系列的bubbles组成。每一个都代表了一个container或bucket,装着被声明的identifiers(variables, functions)。这些bubbles相互嵌套。这种嵌套是在开发阶段写完的。

 

什么制造了一个新的bubble? 只是函数吗?其他的JS结构可以创建bubbles of scope吗?

 

Function Vs. Block Scope


 

 

Scope From Functions

探索函数作用域和它的暗示implications.

函数作用域内的所有变量都属于这个函数,并能够反复在这个函数内使用(甚至在嵌套的作用域中)。

这种设计方法非常有用,可以充分利用动态的自然javascript变量, 在需要时携带不同类型的值。

另一方面,如果不小心,变量跨出一个作用域将导致一些意外的陷阱。

 

Hiding in Plain Scope

软件界的设计原则: 最少的特权。最少的暴露。比如为一个对象/模块的API, 你应当只暴露那些必要的代码。

这个原则扩展到:哪个作用域包括变量和函数的选择!

因此,变量/函数不应该都在全局作用域中。这违背了原则!

 

 避免碰撞

无限循环了

function foo() {
    function bar(a) {
        i = 3; // changing the `i` in the enclosing scope's for-loop
        console.log( a + i );
    }

    for (var i=0; i<10; i++) {
        bar( i * 2 ); // oops, infinite loop ahead!
    }
}

foo();

 

 

Global "Namespaces"

全局作用域可能发生变量碰撞。比如多个库被引用进你的程序。导致一些函数/变量的名字相同。

可以使用‘namespace’,声明一个对象。在这个对象内使用库的方法:

var MyReallyCoolLibrary = {
    awesome: "stuff",
    doSomething: function() {
        // ...
    },
    doAnotherThing: function() {
        // ...
    }
};

 

 

Module 管理

避免变量碰撞的另一个方法是更现代的module方法。使用相关的managers。


 

 

Functions As Scopes

函数作为作用域 。

使用(声明的函数)();可以立即执行。这个方法用于解决2个问题:

  • 1. 只想把函数当作用域,并立即执行代码。
  • 2. 函数没有名字。通过(函数)();就无需名字了,⚠️这个函数只需要执行一次,才能这么写。

理解:

注意用括号()包裹了函数,因此这个函数不再是一个标准的声明declaration。而是当做函数表达式对待functio-expression。

⚠️,区别declaration和expression最简单的方法在于function这个词是否位于statment的第一个位置。是的话就是一个函数声明,否则就是函数表达式。

 

关键区别是 函数的名字作为一个identifier绑定在哪里?

var a = 2;

function foo() {        //绑定在这个enclosing scope, 因此可以直接使用foo()调用这个函数
    var a = 3;
    console.log( a ); // 3
} 
foo(); 

 
(function bar(){         // 名字bar没有绑定在enclosing scope,而是绑定在自己的函数内。
    var a = 3;         // 标志符bar只会在{..}的块作用域中出现。
    console.log( a ); // 3

})();


第4章hoisting的一个案例解释了表达式的identifier绑定在内部。
上面的立即执行函数等同于👇:


(function() {
  var bar = ...self...
})();

 

 

Anonymous Vs. Named

setTimeout( function() {..}, 1000)

 

异步函数表达式:函数没有名字,并不再enclosing scope中。

又一个区别:

  • 函数表达式可以异步
  • 函数声明不能缺少名字。

不过没有名字的函数表达式不容易维护。比如不能在stack traces中显示,让debug变困难。

所以使用Inline function expressions是完美的。就是加上名字。

 

Invoking Function Expressions Immediately

因此(function foo(){ .. })()

第一个括号用于写出一个函数表达式,第二个括号执行它。

 

过去社区统一使用IIFE 

 

第二个括号可以传递参数:

var a = 2;
undefined
(function IIFE(global){
    var a = 3;
    console.log(a);       //3
    console.log(global.a);  //得到全局变量a的值: 2
})( window );

 

也把一个函数表达式作为参数:

var a = 2;

(function IIFE( def ){
    def( window );
})(function def( global ){

    var a = 3;
    console.log( a ); // 3
    console.log( global.a ); // 2

});
//好理解,就是写起来太啰嗦

 

 


 

 

Blocks AS Scopes

函数是作用域中应用最广泛最普通的单位。还有其他作用域单位,甚至更好,更清楚来维护代码。 

块作用域(伪):

 

Block scope是一个工具,用来扩展早期“最少暴露原则”,从在函数中隐藏信息,到在块中隐藏信息。

 

这种写法if,for,把变量放入block scope,是一个对开发者的提醒,不用在其他地方使用这个i变量了。

 

因为,早期JS,没有块作用域的概念。(这就很不方便)

for(var i = 0; i < 10; i++) { ..  }
//这个for循环参数i的作用域是全局/函数作用域。而非块作用域。

for(let i = 0; i < 10; i++) { ..  }
//i是块作用域。
var foo = true;

if (foo) {
    var bar = foo * 2;
    console.log( bar );
}

//变量bar是全局作用域, 如果用let声明bar,则bar是块作用域。

注意:在ES6以前,JS没有真正的块作用域。后来加上了let声明方式,配合{ .. },才有块作用域, 

 

 

try/catch,早期javascript的一个例外,事实上的块作用域!

try {
    undefined(); // illegal operation to force an exception!
}
catch (err) {
    console.log( err ); // works!
}

console.log( err ); 

ReferenceError: `err` not found , 变量err在全局作用域不存在!

 

更多信息看附加B。catch非常有用!

 

let关键字(ES6)

 尽量使用明显的块作用域:

if (true) {
    {
        let bar =  2;
        console.log(bar);
    }
}
内部嵌套的{}花括号可以不写。但明确的写出来,视觉效果更好,代码更容易维护!!

 

 用let声明的变量不会hoisting!

 

 

Garbage Collection垃圾收集

block-scope很有用的另一个原因, 是关于闭包和垃圾收集,来恢复内存reclaim memory. 

function process(data) {
    // do something interesting
}
//在👇的块作用域let声明的变量,在块作用域结束后,清除它的内存!
{ let someReallyBigData = { .. }; process( someReallyBigData ); } ...略...
如果后续操作和前面的process()无关的话,
就没必要保存someReallyBigData变量在内存内,
通过块作用域可以限定它的使用范围,
在它用完后,自动释放它的内存。

 

 

let Loops

上文已经提到在循环中使用let, 就是使用块作用域。不过为了看起来更清除,方便今后的重构代码,

可以明确写上块{ .. }

{
    let j;
    for (j=0; j<10; j++) {
        let i = j; // re-bound for each iteration!
        console.log( i );
    }
}

 

 

Const

也是块作用域定义的变量,但值不能改变。可以改变type

{
    const a = '123';
    Number(a);
    console.log(typeof a);   // string
}

 

 


 

Reviews

从ES3的try/catch,到ES6的let/const,block-scope。

开发者应灵活使用var , let, const声明变量。

 

posted @ 2018-10-02 21:49  Mr-chen  阅读(183)  评论(0编辑  收藏  举报