• 2.let和const命令


    1.let命令


    let声明的变量,只在let命令所在的代码块内有效。

    {
      let a = 10;
      var b = 1;
    }
    
    a // ReferenceError: a is not defined.
    b // 1

    上面的代码,let声明的变量a会报错,这表明,let声明的变量只在它所在的代码块有效。

    for循环里面,就可以用let

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

    上面代码中,计数器i只在for循环体内有效,在循环体外引用就会报错

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

    上面代码中,变量ivar命令声明的,在全局范围内都有效,所以全局只有一个变量i

    每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i)

    里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i

    导致运行时输出的是最后一轮的i的值,也就是10。

    如果使用let,声明的变量仅在块级作用域内有效,最后输出的是6

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

    上面代码中,变量ilet声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是

    一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,

    那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会

    记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。


    不存在变量提升

    var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined

    let所声明的变量一定要在声明后使用,否则报错。

    // var 的情况
    console.log(foo); // 输出undefined
    var foo = 2;
    
    // let 的情况
    console.log(bar); // 报错ReferenceError
    let bar = 2;

     

    暂时性死区 

    只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

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

    上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp

    导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

    ES6明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,

    从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错

    总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。

    这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

    ES6 规定暂时性死区和letconst语句不出现变量提升,主要是为了减少运行时错误,

    防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是

    很常见的,现在有了这种规定,避免此类错误就很容易了。

    总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,

    但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。


    不允许重复声明 

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

    // 报错
    function () {
      let a = 10;
      var a = 1;
    }
    
    // 报错
    function () {
      let a = 10;
      let a = 1

    因此,不能在函数内部重新声明参数。

    function func(arg) {
      let arg; // 报错
    }
    
    function func(arg) {
      {
        let arg; // 不报错
      }
    }

    2.块级作用域


    为什么会需要块级作用域?

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

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

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

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

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

    ES6的作用域

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

    上面输出解构表明了外层代码不受内层代码的影响

    块级作用域与函数声明

    ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,

    块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

    function f() { console.log('I am outside!'); }
    
    (function () {
      if (false) {
        // 重复声明一次函数f
        function f() { console.log('I am inside!'); }
      }
    
      f();
    }());

    上面代码在 ES5 中运行,会得到“I am inside!”,因为在if内声明的函数f会被提升到函数头部,实际运行的代码如下。

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

    ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于let

    对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢?

    原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的

    不兼容问题,ES6在附录B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式

    • 允许在块级作用域内声明函数。
    • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
    • 同时,函数声明还会提升到所在的块级作用域的头部。

    注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

    根据这三条规则,在浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var声明的变量。


    do表达式

    因为块级作用域不返回值,现在有一个提案,使得块级作用域可以变为表达式,

    也就是说可以返回值,办法就是在块级作用域之前加上do,使它变为do表达式。

    let x = do {
      let t = f();
      t * t + 1;
    };

    上面代码中,变量x会得到整个块级作用域的返回值。


    3.const命令


    基本用法

    const声明一个只读的常量。一旦声明,常量的值就不能改变。

    const PI = 3.1415;
    PI // 3.1415
    
    PI = 3;
    // TypeError: Assignment to constant variable.

    const一旦声明变量,就必须立即初始化,不能留到以后赋值。

    const的作用域与let命令相同:只在声明所在的块级作用域内有效。

    const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

    const声明的常量,也与let一样不可重复声明。


    本质

    const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。

    对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,

    因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,

    保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是

    可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

    const foo = {};
    
    // 为 foo 添加一个属性,可以成功
    foo.prop = 123;
    foo.prop // 123
    
    // 将 foo 指向另一个对象,就会报错
    foo = {}; // TypeError: "foo" is read-only

    上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,

    即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。


    ES6 声明变量的六种方法

    ES5 只有两种声明变量的方法:var命令和function命令。ES6除了添加letconst命令,

    后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一

    共有6种声明变量的方法。

  • 相关阅读:
    iOS 类知乎”分页”效果的实现?
    iOS 图解弹幕功能的实现
    iOS 为何使用runtime方法交换多次后却能按照交换顺序依次执行代码逻辑?
    iOS常用算法之单链表查找倒数第n个节点(图解)
    iOS常用算法之两个有序数组合并, 要求时间复杂度为0(n)
    iOS 常用算法之设计一个算法,验证某字符是否为合法IPV4字符
    iOS .Crash文件分析处理办法 (利用symbolicatecrash工具处理)
    iOS中UIWebview中网页宽度自适应的问题
    iOS开发
    安卓应用加固之反动态调试技术总结
  • 原文地址:https://www.cnblogs.com/jannryguo/p/7064528.html
Copyright © 2020-2023  润新知