1、新增let命令

  • 与var不同的是:let声明的变量只在let命令所在的代码块内有效。

{
    let a = 10;
    var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

有一种情况很适合用let命令:for循环的计数器。

for(let i =0; i<arr.length; i++){
}
console.log(i) //ReferenceError: i is not defined

下面就用一个例子来比较一下let和var在for循环计数器的不同,为什么let更适合:

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

2、只要块级作用域内存在let命令,那么它所声明的变量就不再受外部的影响,形成了封闭作用域

if (true){
    //封闭作用域开始
    tmp = 'abc'; //ReferenceError
    console.log(tmp); //ReferenceError
    let tmp; 
    //封闭作用域结束
    console.log(tmp); // undefined
    tmp = 123;
    console.log(tmp); //123
}

在let命令声明变量tmp之前,都属于变量tmp的"死区"。

  • 此处需要注意一下:
    函数的作用域是其声明时所在的作用域。
    来个例子思考一下:

let foo = 'outer';
function bar(func = x => foo){
    let foo = 'inner';
    console.log(func()); // outer
}
bar();

func 默认是一个匿名函数,返回值为foo,作为bar函数的参数。func的作用域不是bar,func声明是在外层作用域,所以bar内部的foo指向函数体外的foo的值,这段代码等同于下面这段代码:

let foo = 'outer'
let f = x => foo;
function bar(func = f){
    let foo = 'inner';
    console.log(func()); //outer
}
bar();

总之,暂时性死去的本质就是:只要已进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。


3、新增块级作用域
ES5只有全局作用域和函数作用域,这样会产生很多不合理的场景。
第一种场景:内层变量可能会覆盖外层变量

var tmp = "hello";
(function(){
  console.log(tmp);
  var tmp = "world";
})();  //undefined

思考一下,为什么结果会是undefined呢?

  • 第一种情况可能就是函数内的tmp变量覆盖了全局tmp,如果去掉function我们看看变量的结果:

var tmp = "hello";
if(true){
    console.log(tmp);
    var tmp = "world";    
}  //  hello
  • 第二种情况就是存在变量提升,这又是怎么回事呢,也就是说最初的代码等同于下面这段代码:

var tmp = "hello";
(function(){
  var tmp; //变量提升
  console.log(tmp);
  tmp = "world";
})(); //undefined

第二种场景,用来计数的循环变量泄露为全局变量。

var s = 'hello';
for (var i = 0; i < s.length; i++){
  console.log(s[i]);
}
console.log(i); // 5

变量i只是用来控制循环的,循环结束它就没有用了,但是却泄露成了全局变量,这个很不好。


可以说let为js增加了块级作用域,来看下面这段代码吧:

function a(){
    let s = 1;
    if(true){
        let s = 3;
    }
    console.log(s);  //1
}
function a(){
    var s = 1;
    if(true){
        var s = 3;
    }
    console.log(s);  //3
}

let 声明的变量,使外层的代码不受内层的代码影响,而var则没有这样的效果。


ES6允许块级作用域的任意嵌套,并且外层作用域无法读取内层作用域的变量

{{{{
  {let s = 'Hello World'}
  console.log(s); // 报错
}}}};

内层作用域可以定义外层作用域的同名变量

{{{{
  let s = 'Hello World';
  {let s = 'Hello World';}
}}}};

块级作用域可以代替立即执行函数

//立即执行函数
(function(){
    var tmp = ...;
    ...
}());
//块级作用域
{
    let tmp = ...;
    ...
}

ES6中函数本身的作用域在其所在的块级作用域之内

function f() { console.log('I am outside!'); }
(function () {
  if(false) {
    function f() { console.log('I am inside!'); }
  }

  f();
}());

ES5中的运行结果:I am inside!
ES6中的运行结果:I am outside!
造成这样结果的原因就是:

  • 在ES5中存在变量提升,不论会不会进入if代码块,函数声明都会提升到当前作用域的顶部得到执行.

  • 在ES6中支持块级作用域,不论会不会进入if代码块,其内部声明的函数皆不会影响到作用域的外部

{
    let a = 'hello';
    function b(){
        return a;
    }
}
f(); //报错

因为函数的调用在块级作用域的外部,所以无法调用块级作用域内部定义的函数。

let b;
{
  let a = 'world';
  b = function () {
    return a;
  }
}
b() // "world"

如果代码进行上面这样的处理,即把函数b的声明与函数调用放在同一块级作用域就可以了。

注:如果在严格模式下,函数只能在顶层作用域和函数内声明,其他情况(比如if代码块、循环代码块)的声明都会报错。


shane_xu
1k 声望32 粉丝

喜欢美食的一个前端