读<你不知道的javascript>,想到哪里写到哪里。。。

块状作用域

提起ES6中的let关键字,第一个想法就是块状作用域。

说到作用域,以前提及的都是全局作用域和函数作用域。当进行作用域查找的时候,永远是一层一层往上查找,直到找到第一个匹配的标示符时停止。

全局作用域就是整个作用域的最上层,当进行作用域查找的时候,全局作用域永远是最上一层,如果在全局作用域中依然无法找到,那么就会抛出referenceError

而说到函数作用域,曾经读过一本书上面说到一个词“隐藏”变量和函数定义,觉得很贴切。把东西隐藏在函数中,避免污染外部作用域的同时,可以有效地进行模块化管理。

回到块状作用域,除了let,另外一个with关键字也创造了一个块状作用域。

function foo(obj) {
    var c = 6;
    with(obj) {
       a = b;
       c = 5;
       var d = 7;
       let e = 8;
    }
    console.log(c); // 5
    console.log(d); // 7
    console.log(e); // referenceError
}
var obj = { a:1, b:2 };
foo(obj);

我们都知道其中的ab的作用域查找(LHS, RHS)都是在obj中找到。而如果无法再obj中找到,那么就会向上作用域继续找,这里的c就会找到foo函数中的c。而如果通过var这些关键词声明的,那么也就会声明到with所在的作用域中。

对于with,就相当于在外面又包了一层作用域,里面包含提供对象中的所有属性。

终于回到let, let可以将变量绑定到所在的任意作用域中,通常是{}内部。没了。。。

提升

var声明变量和函数声明,let不会进行提升。看看下面的几个例子:

console.log(a); // referenceError
let a = 1;
console.log(b); // undefined;
var b = 1;
foo(); // 3
function foo(){ console.log("3") }
foo(); // type error
var foo = function(){ console.log("4") }
foo(); // referenceError
let foo = function(){ console.log("5") }

说到提升,提一句:“函数会首先被提升,然后才是变量”。像之前的书里的一个例子:

bar(); // 3
function bar(){ console.log(1) };

var bar = function(){ console.log(2) };

function bar(){ console.log(3) }

提升以后看起来就像:

function bar(){ console.log(1) };
function bar(){ console.log(3) };
bar(); // 3
bar = function(){ console.log(2) };

这里需要提一句,var bar这一段由于bar在之前已经被声明过了,所以这里var bar会被忽略,而不是bar设为null

说到提升,还有一种特殊的情况,虽然不建议这么写,但是肯定会有人会踩到雷:

var a = true;
if(a) {
  function foo(){ console.log(1) }
} else {
  function foo(){ console.log(2) }
}
foo();

原书中说这种情况,由于提升,所以会输出2,而不是我们想要的1。但是我在chromefirefox中试了一下,会按照想要的输出1。而在IE11中,却输出的是2

所以,很明显,chromefirefox对这种情况作了优化,但是还是有浏览器的差别,所以尽量不要这么声明函数。避免不必要的bug出现。

垃圾收集

当使用闭包的时候,会造成一部分的内存不会进行释放。这是闭包的好处,也是闭包的坏处。好处就是我们可以在外部使用这个变量,而坏处就是有些不需要的变量也不会被释放。

一个简单的闭包:

function Foo(){
   var a = [....];
   // .....和a做些互动
   var b = 0;
   return function(){
      return b++;
   }
}

这里在闭包中,我们其实只需要保存b的内存就好,而a是不需要的。按照理想情况,aFoo结束后就可以垃圾回收了(GC)。然而引擎有可能会保存着这个东西(取决于具体实现--你不知道的javascript),这就造成了内存的浪费。

那么就可以使用let块状作用域的特性,进行处理:

function Foo(){
   {
      let a = [....];
      // ....和a做些互动
   }
   var b = 0;
   return function(){
      return b++;
   }
}

这里,由于a的作用域只在{}中,所以当{}结束后就会释放a的内存,而不是长久占有内存。

循环

这里只写出一个很有意思的观点。

我们都知道在循环中用let可以解决某个老生常谈的问题。但是总是转变不过来,为什么会解决这个问题。

var a = [];
for(var i = 0; i < 10; i++) {
   a[i] = function(){ console.log(i) };
}
a[3](); // 10
var a = [];
for(let i = 0; i < 10; i++) {
   a[i] = function(){ console.log(i) };
}
a[3](); // 3

所以到底let做了什么神奇的事情,解决了这个问题?

书中说了一句话:

for循环头部的let不仅将i绑定到了for循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。

然后给了一个等价的例子,一目了然:

var a = [];
{
   let j;
   for(j=0; j<10;j++){
     let i = j; // 每个迭代重新绑定
     a[i] = function(){ console.log(i) };
   }
}
a[3](); // 3

最后啰嗦一句,const这个关键词,也是块状作用域,也是不提升。


Clark
3.4k 声望82 粉丝

[链接]