美文网首页
ES6 let 和 const

ES6 let 和 const

作者: _ClariS_ | 来源:发表于2019-11-14 23:01 被阅读0次

    一、声明全局变量

    a = 1一定是在声明一个全局变量吗?

    function fn() {
      a = 1
    }
    fn()
    console.log(a) // 1
    

    上面的代码的确是在声明一个全局变量a,然后我们再来看看下面这种情况

    var a
    function fn() {
      var a
      function fn2() {
        a = 1
      }
    }
    fn()
    console.log(a) // undefined
    

    在这种情况下很明显就不是在声明全局变量aa = 1会向上寻找离它最近的一个声明,即函数fn里的var a,并没有到达最顶层,因此上面的代码实际上等价于下面这种写法

    var a
    function fn() {
      var a = 1
      function fn2() {
      }
    }
    fn()
    console.log(a) // undefined
    

    总结一下就是,如果在当前作用域内存在变量a的声明,那么a = 1就会直接去利用这个声明;如果在当前作用域内不存在对a的声明,那么就会按照“作用域链”一级一级地向上寻找所有父对象的变量。只有当找完了所有父对象的变量之后都没有发现有对变量a的声明,这时候才是在声明一个全局变量a(window.a)

    二、ES5 作用域

    ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

    第一种场景,内层变量可能会覆盖外层变量。

    var tmp = new Date();
    
    function f() {
      console.log(tmp);
      if (false) {
        var tmp = 'hello world';
      }
    }
    
    f(); // undefined
    

    上面代码的原意是,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

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

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

    上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。(可以使用立即执行函数来解决这个问题)

    由于以上var的种种缺陷,因此就有了下文的letconst以及“块级作用域”

    三、let 命令

    1. 块级作用域

    function f1() {
      let n = 5;
      if (true) {
        let n = 10;
      }
      console.log(n); // 5
    }
    

    上面的函数有两个代码块,都声明了变量n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。

    2. 暂时性死区

    var tmp = 123;
    
    if (true) {
      tmp = 'abc'; // ReferenceError
      let tmp;
    }
    

    上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

    ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

    总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

    3. 不允许重复声明

    let不允许在相同作用域内,重复声明同一个变量。

    // 报错
    function func() {
      let a = 10;
      var a = 1;
    }
    
    // 报错
    function func() {
      let a = 10;
      let a = 1;
    }
    
    image.png

    四、const 命令

    const声明一个只读的常量。一旦声明,常量的值就不能改变。而且必须在声明的时候立刻进行赋值。

    const PI = 3.1415;
    PI // 3.1415
    
    PI = 3;
    // TypeError: Assignment to constant variable.
    
    const foo;
    // SyntaxError: Missing initializer in const declaration
    

    五、面试题

    1. 下面代码的打印结果一定是1吗?
    var a = 1
    
    function fn() {
      console.log(a)
    }
    /*这里有一行代码*/
    fn()
    

    如果加的一行代码是a = 2呢?

    var a = 1
    
    function fn() {
      console.log(a) // 2
    }
    a = 2
    fn()
    
    1. 下面的代码为什么打印出i的值总是6?
    for (var i=0; i<6; i++){
      
    }
    console.log(i) // 6
    

    因为它等价于下面这种写法

    var i
    for (i=0; i<6; i++){
      
    }
    console.log(i) // 6
    
    1. 下面的代码一定会打印出0~5吗?
    for (var i=0; i<6; i++){
      function fn(){
        console.log(i) // ?
      }
      /*这里有一行代码*/
    }
    console.log(i) // 6
    

    如果是直接调用fn,会打印出1~5,我的理解是每进入for循环中一次,就执行一次fn,因此会打印出对应的i的值

    for (var i=0; i<6; i++){
      function fn(){
        console.log(i) // 0,1,2,3,4,5
      }
      fn()
    }
    console.log(i) // 6
    

    但如果加的代码是x.onclick = fn,则直接就打印出6,我的理解是当点击这个按钮的时候,i已经累加到6了

    /*<button id="x">x</button>*/
    for (var i=0; i<6; i++){
      function fn(){
        console.log(i) // 6
      }
      x.onclick = fn
    }
    console.log(i) // 6
    
    1. 一道面试题
    <ul>
      <li>导航1</li>
      <li>导航2</li>
      <li>导航3</li>
      <li>导航4</li>
      <li>导航5</li>
      <li>导航6</li>
    </ul>
    
    var liTags = document.querySelectorAll('li')
    
    for (var i = 0; i < liTags.length; i++) {
      liTags[i].onclick = function() {
        console.log(i) // 为什么打印出i的值总是6?
      }
    }
    

    这就是因为用来计数的循环变量i泄露为全局变量

    有以下三种解法:

    解法一:用ES5的立即执行函数来创造一个局部作用域

    var liTags = document.querySelectorAll('li')
    
    for (var i = 0; i < liTags.length; i++) {
      (function(j) {
        liTags[j].onclick = function() {
          console.log(j) // 0,1,2,3,4,5
        }
      })(i)
    }
    

    解法二:在for循环内部用另一个变量来接收i的值

    var liTags = document.querySelectorAll('li')
    
    for (var i = 0; i < liTags.length; i++) {
      let j = i // 注意这里依然只能用let,不能用var
      liTags[j].onclick = function() {
        console.log(j) // 0,1,2,3,4,5
      }
    }
    

    解法三:直接在for循环的条件中使用let来声明变量

    var liTags = document.querySelectorAll('li')
    
    for (let i = 0; i < liTags.length; i++) {
      liTags[i].onclick = function() {
        console.log(i) // 0,1,2,3,4,5
      }
    }
    

    注意:上面解法三的这种写法,i的作用域只在for后面的圆括号中,而下面的花括号又是另外一个作用域,可以理解为下面这样

    在这个过程中一共产生了7个i,块里面6个,圆括号里面1个

    参考:
    http://es6.ruanyifeng.com/#docs/let
    我用了两个月的时间才理解 let

    相关文章

      网友评论

          本文标题:ES6 let 和 const

          本文链接:https://www.haomeiwen.com/subject/euprictx.html