读<你不知道的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);
我们都知道其中的a
和b
的作用域查找(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
。但是我在chrome
和firefox
中试了一下,会按照想要的输出1
。而在IE11
中,却输出的是2
。
所以,很明显,chrome
和firefox
对这种情况作了优化,但是还是有浏览器的差别,所以尽量不要这么声明函数。避免不必要的bug
出现。
垃圾收集
当使用闭包的时候,会造成一部分的内存不会进行释放。这是闭包的好处,也是闭包的坏处。好处就是我们可以在外部使用这个变量,而坏处就是有些不需要的变量也不会被释放。
一个简单的闭包:
function Foo(){
var a = [....];
// .....和a做些互动
var b = 0;
return function(){
return b++;
}
}
这里在闭包中,我们其实只需要保存b
的内存就好,而a
是不需要的。按照理想情况,a
在Foo
结束后就可以垃圾回收了(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
这个关键词,也是块状作用域,也是不提升。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。