探索 JavaScript 新增声明命令与解构赋值的魅力:从 ES5 迈向 ES6

探索 JavaScript 新增声明命令与解构赋值的魅力:从 ES5 迈向 ES6

码农世界 2024-05-24 前端 71 次浏览 0个评论



个人主页:学习前端的小z

个人专栏:JavaScript 精粹

本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结,欢迎大家在评论区交流讨论!


ES5、ES6介绍


文章目录

  • 💯声明命令 let、const
    • 🍟1 let声明符
      • 🎲1.1 基础特性
      • 🎲1.2 不进行变量提升
      • 🎲1.3 致死域(暂时性死区)
      • 🎲1.4 无法重复声明
      • 🎲1.5 块作用域 (block scope)
      • 🎲1.6 块作用域下的函数声明
      • 🍟2 const声明符
        • 🎲2.1 声明时必须赋值
        • 🎲2.2 值为对象
        • 💯解构赋值
          • 🍟1 数组解构
            • 🎲1.1 基础变量赋值
            • 🎲1.2 声明分别赋值
            • 🎲1.3 解构默认值
            • 🎲1.4 交换变量值
            • 🎲1.5 解析函数返回的数组
            • 🎲1.6 忽略返回值/跳过某项
            • 🎲1.7 赋值数组剩余值给一个变量
            • 🎲1.8 嵌套数组解构
            • 🍟2 对象解构
              • 🎲2.1 基础对象解构
              • 🎲2.2 无声明解构
              • 🎲2.3 赋值给新变量名
              • 🎲2.4 解构默认值
              • 🎲2.5 赋值给新变量名的同时提供默认值
              • 🎲2.6 嵌套对象和数组解构
              • 🎲2.7 可计算对象属性名与解构
              • 🎲2.8 同时使用数组和对象解构
              • 🎲2.9 注意点
              • 🍟3 函数参数的解构赋值
              • 🍟4 解构的用途
                • 🎲4.1 交换变量的值
                • 🎲4.2 从函数返回多个值
                • 🎲4.3 函数参数的定义
                • 🎲4.4 提取 JSON 数据
                • 🎲4.5 函数参数的默认值

                  💯声明命令 let、const

                  🍟1 let声明符

                  ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

                  🎲1.1 基础特性

                  if(true){
                    let a = 'let';
                    var b = 'var';
                  }
                  console.log(b); //var
                  console.log(a); //Uncaught ReferenceError: a is not defined
                  ---------------------------------------------------------
                  //使用let,声明的变量仅在块级作用域内({ })有效,最后输出的是 6。
                  for(let i = 0; i < 10; i++){
                      
                  }
                  console.log(i); //Uncaught ReferenceError: i is not defined
                  

                  上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。

                  // 在循环体表达式中let声明迭代变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量
                  var a = [];
                  for (let i = 0; i < 10; i++) {
                    a.push(function () {
                      console.log(i);
                    });
                  }
                  a[5](); // 5
                  
                  // 在循环体表达式中let声明 和循环体中let 声明同名变量是不冲突的 设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。相当于每次循环{}内部都会单独开一个局部作用域
                  for (let i = 0; i < 3; i++) {
                    let i = 'abc';
                    console.log(i);
                  }
                  

                  🎲1.2 不进行变量提升

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

                  let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。 这一点需要特殊注意

                  // var 的情况
                  console.log(num); // 输出undefined
                  var num = 2;
                  -------------------------
                      
                  // let 的情况
                  console.log(age); // 报错ReferenceError
                  let age = 16;
                  

                  🎲1.3 致死域(暂时性死区)

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

                  //正常调用
                  let num = 10;
                  if (true) {
                      console.log(num); //10
                  }
                  ------------------
                  //if { }内部let为独立局部作用域 访问为 20
                  let num = 10;
                  if (true) {
                      let num = 20;
                      console.log(num); //20
                  }
                  ------------------
                  //发生致死 因为fn局部作用域中有let声明num 所以在作用域内 不能先调用再使用let声明。
                  let num = 10;
                  function fn() {
                      console.log(num); //Uncaught ReferenceError: Cannot access 'num' before initialization
                      let num = 20;
                  }
                  fn();
                  ------------------
                  //typeof操作影响
                  typeof num;
                  let num; //Uncaught ReferenceError: Cannot access 'num' before initialization
                  

                  当程序的控制流程在新的作用域(module function 或 block 作用域)进行实例化时,在此作用域中用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区。

                  es6规定 let/const 会使区域形成封闭的作用域 若在声明之前使用变量就会发生错误, 在代码块内使用let命令声明变量之前 该变量都无法使用,称为“暂时性死区”(temporal dead zone,简称 TDZ)。总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。使用let 确保先 声明 再 使用 变量 不要混用let和var在同一个作用域链上 避免死区

                  🎲1.4 无法重复声明

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

                  //发生致死 因为var会变量提升。
                  let num = 10;
                  if (true) {
                      var num = 20; //Uncaught SyntaxError: Identifier 'num' has already been declared
                      console.log(num);
                  }
                  

                  🎲1.5 块作用域 (block scope)

                  在ES5中,只全局作用域和函数作用域。这会导致函数作用域覆盖了全局作用域;亦或者循环中的变量泄露为全局变量。

                  EcmaScript 6引入了块级作用域(block scope),块级作用域只能在块中被访问,以下两种情况可以创建块级作用域的变量。

                  • 在函数中

                  • 在被{和}包裹的块中

                    {
                        var x = 10;
                    }
                    console.log(x); //10
                    ------------------
                    {
                        let x = 10;
                    }
                    console.log(x);//x is not defined
                    ------------------
                    //函数有两个代码块,都声明了变量num,运行后输出 3。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量num,最后输出的值才是 6。
                    function fn() {
                      let num = 3;
                      if (true) {
                        let num = 6;
                      }
                      console.log(num); // 3
                    }
                    

                    🎲1.6 块作用域下的函数声明

                    ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。

                    ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

                    function a() {
                        console.log('我是全局作用域内声明的函数a');
                    }
                    (function () {
                        if (false) {
                            function a() {
                                console.log('我是块级作用域内声明的函数a');
                            }
                        }
                        a();
                    })();
                    ----------------------
                    function a() {
                        console.log('我是全局作用域内声明的函数a');
                    }
                    (function () {
                        var a;
                        if (false) {
                            a = function () {
                                console.log('我是块级作用域内声明的函数a');
                            }
                        }
                        a();
                    })();
                    

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

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

                      🍟2 const声明符

                      const声明一个只读的常量。一旦声明,常量的值就不能改变。 const与let在 块作用域 重复声明 致死域的问题上是一致的 const的作用域与let命令相同:只在声明所在的块级作用域内有效。

                      const DATA = '我是常量 不能改变哦';
                      DATA //  '我是常量 不能改变哦'
                      DATA = '改一个试试看';
                      // TypeError: Assignment to constant variable.
                      

                      const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

                      🎲2.1 声明时必须赋值

                      const X;
                      //Uncaught SyntaxError: Missing initializer in const declaration
                      

                      🎲2.2 值为对象

                      const KEY_MAP = {
                          a:1,
                          b:2
                      }
                      KEY_MAP['a'] = 2; //不报错
                      -----------
                          
                      const ARRAY = [];
                      ARRAY.push('something'); // 可执行
                      ARRAY.length = 0;    // 可执行
                      ARRAY = ['something'];    // 报错
                      


                      💯解构赋值

                      ES6提供了更简洁的赋值模式, 从数组和对象中提取值. 这被称为解构(Destructuring)。

                      [a, b] = [50, 100];
                      console.log(a);
                      // expected output: 50
                      console.log(b);
                      // expected output: 100
                      [a, b, ...rest] = [10, 20, 30, 40, 50];
                      console.log(rest);
                      // expected output: [30, 40, 50]
                      

                      我们有很多种不同的方式使用 JavaScript 解构。

                      🍟1 数组解构

                      数组解构是极为简单整洁的,在复制表达式的左侧使用数组字面量。数组字面量中的每个变量名称映射为解构数组的相同索引项。

                      🎲1.1 基础变量赋值

                      let foo = ['one', 'two', 'three'];
                      let [red, yellow, green] = foo;
                      console.log(red); // "one"
                      console.log(yellow); // "two"
                      console.log(green); // "three"
                      

                      🎲1.2 声明分别赋值

                      你可以通过变量声明分别解构赋值。举例:首先,声明变量,然后分别赋值。

                      // 声明变量
                      let a, b;
                      // 然后分别赋值
                      [a, b] = [1, 2];
                      console.log(a); // 1
                      console.log(b); // 2
                      

                      🎲1.3 解构默认值

                      如果解构取出的值是 undefined,可以设置默认值:

                      let a, b;
                      // 设置默认值
                      [a = 5, b = 7] = [1];
                      console.log(a); // 1
                      console.log(b); // 7
                      

                      在上面的例子中,我们给 a 和 b 设置了默认值。这种情况下,如果 a 或 b 的值是 undefined,它将赋值默认值 5 给 a 7 给 b

                      🎲1.4 交换变量值

                      解构可以在一个解构表达式中交换两个变量值

                      let a = 1;
                      let b = 3;
                      [a, b] = [b, a];
                      console.log(a); // 3
                      console.log(b); // 1
                      

                      如果你想不使用解构交换变量值,将必须提供一个缓存变量或者同解构一起使用

                      🎲1.5 解析函数返回的数组

                      可以解构函数返回的数组。

                      function c() {
                        return [10, 20];
                      }
                      let a, b;
                      [a, b] = c();
                      console.log(a); // 10
                      console.log(b); // 20
                      

                      在上面的例子中,c() 的返回值 [10, 20] 可以在单独的一行代码中使用解构解析。

                      🎲1.6 忽略返回值/跳过某项

                      你也可以跳过一些没有用的返回值。

                      function c() {
                        return [1, 2, 3];
                      }
                      let [a, , b] = c();
                      console.log(a); // 1
                      console.log(b); // 3
                      

                      在罕见的情况下,你想忽略所有的值。

                      [, ,] = c();
                      

                      当然,这是不会发生的。

                      🎲1.7 赋值数组剩余值给一个变量

                      当你使用数组解构,你可以赋值数组剩余部分给一个单独的变量。

                      let [a, ...b] = [1, 2, 3];
                      console.log(a); // 1
                      console.log(b); // [2, 3]
                      

                      小心结尾的逗号语法错误,它将爆发如果在剩余元素的左侧使用结尾逗号:

                      let [a, ...b,] = [1, 2, 3];
                      // SyntaxError: rest element may not have a trailing comma
                      

                      🎲1.8 嵌套数组解构

                      像对象一样,你也可以使用数组嵌套解构。这里有一个例子:

                      const color = ['#FF00FF', [255, 0, 255], 'rgb(255, 0, 255)'];
                      // Use nested destructuring to assign red, green and blue
                      // 使用嵌套解构赋值 red, green, blue
                      const [hex, [red, green, blue]] = color;
                      console.log(hex, red, green, blue); // #FF00FF 255 0 255
                      

                      🍟2 对象解构

                      🎲2.1 基础对象解构

                      let x = { y: 22, z: true };
                      let { y, z } = x; // let {y:y,z:z} = x;的简写
                      console.log(y); // 22
                      console.log(z); // true
                      

                      🎲2.2 无声明解构

                      你可以使用解构分别从它的声明赋值变量。这意味着在上面的例子中不需要创建变量 x。

                      let y, z;
                      ({ y, z } = { y: 1, z: 2 });
                      

                      注意:圆括号 (...) 包裹赋值声明是必须的当使用对象字面量解构赋值无声明变量。

                      {a, b} = {a: 1, b: 2} 不是有效的独立语法,左侧的 {a, b} 被考虑为代码块而不是一个对象字面量。

                      因此,({a, b} = {a: 1, b: 2}) 是有效的, 等价于 var {a, b} = {a: 1, b: 2}。

                      (...) 表达式需要前置分号或者它可能用于在前一行执行函数。

                      🎲2.3 赋值给新变量名

                      当使用对象解构时你也可以改变变量的名称,如下例子:

                      let o = { p: 22, q: true };
                      let { p: foo, q: bar } = o;
                      console.log(foo); // 22
                      console.log(bar); // true
                      

                      例子中,var {p: foo} = o 获取对象 o 的属性名 p,然后赋值给一个名称为 foo 的变量。

                      🎲2.4 解构默认值

                      如果解构取出的对象值是 undefined 你也可以设置默认值。

                      let { a = 10, b = 5 } = { a: 3 };
                      console.log(a); // 3
                      console.log(b); // 5
                      

                      🎲2.5 赋值给新变量名的同时提供默认值

                      let { a: aa = 10, b: bb = 5 } = { a: 3 };
                      console.log(aa); // 3
                      console.log(bb); // 5
                      

                      🎲2.6 嵌套对象和数组解构

                      const metadata = {
                        title: 'Scratchpad',
                        translations: [
                          {
                            locale: 'de',
                            localization_tags: [],
                            last_edit: '2014-04-14T08:43:37',
                            url: '/de/docs/Tools/Scratchpad',
                            title: 'JavaScript-Umgebung',
                          },
                        ],
                        url: '/en-US/docs/Tools/Scratchpad',
                      };
                      let {
                        title: englishTitle, // 重命名
                        translations: [
                          {
                            title: localeTitle, // 重命名
                          },
                        ],
                      } = metadata;
                      console.log(englishTitle); // "Scratchpad"
                      console.log(localeTitle); // "JavaScript-Umgebung"
                      

                      🎲2.7 可计算对象属性名与解构

                      当使用解构改变对象属性的名称时,可以使用对象计算属性名。

                      let key = 'z';
                      let { [key]: foo } = { z: 'bar' };
                      console.log(foo); // "bar"
                      

                      在上面的例子中,我们计算变量键值并改变它的名称为 foo。

                      🎲2.8 同时使用数组和对象解构

                      在解构中数组和对象可以联合使用:

                      const props = [
                        { id: 1, name: 'Fizz' },
                        { id: 2, name: 'Buzz' },
                        { id: 3, name: 'FizzBuzz' },
                      ];
                      const [, , { name }] = props;
                      console.log(name); // "FizzBuzz"
                      

                      所有的解构赋值语法是相同的,是在赋值符号左侧声明从源变量取出的值。举例来说:

                      let x = [1, 2, 3, 4, 5];
                      let [y, z] = x;
                      console.log(y); // 1
                      console.log(z); // 2
                      

                      🎲2.9 注意点

                      (1)如果要将一个已经声明的变量用于解构赋值,必须非常小心。

                      // 错误的写法
                      let x;
                      {x} = {x: 1};
                      // SyntaxError: syntax error
                      

                      上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。

                      // 正确的写法
                      let x;
                      ({x} = {x: 1});
                      

                      上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。

                      (2)解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。

                      ({} = [true, false]);
                      ({} = 'abc');
                      ({} = []);
                      

                      上面的表达式虽然毫无意义,但是语法是合法的,可以执行。

                      (3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

                      let arr = [1, 2, 3];
                      let {0 : first, [arr.length - 1] : last} = arr;
                      first // 1
                      last // 3
                      

                      上面代码对数组进行对象解构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”

                      🍟3 函数参数的解构赋值

                      函数的参数也可以使用解构赋值。

                      function add([x, y]){
                        return x + y;
                      }
                      add([1, 2]); // 3
                      

                      上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。

                      下面是另一个例子。

                      [[1, 2], [3, 4]].map(([a, b]) => a + b); 
                      // [ 3, 7 ]
                      

                      函数参数的解构也可以使用默认值。

                      function move({x = 0, y = 0} = {}) {
                        return [x, y];
                      }
                      move({x: 3, y: 8}); // [3, 8]
                      move({x: 3}); // [3, 0]
                      move({}); // [0, 0]
                      move(); // [0, 0]
                      

                      上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。

                      注意,下面的写法会得到不一样的结果。

                      function move({x, y} = { x: 0, y: 0 }) {
                        return [x, y];
                      }
                      move({x: 3, y: 8}); // [3, 8]
                      move({x: 3}); // [3, undefined]
                      move({}); // [undefined, undefined]
                      move(); // [0, 0]
                      

                      上面代码是为函数move的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。

                      undefined就会触发函数参数的默认值。

                      [1, undefined, 3].map((x = 'yes') => x);
                      // [ 1, 'yes', 3 ]
                      

                      🍟4 解构的用途

                      变量的解构赋值用途很多。

                      🎲4.1 交换变量的值

                      let x = 1;
                      let y = 2;
                      [x, y] = [y, x];
                      

                      上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。

                      🎲4.2 从函数返回多个值

                      函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

                      // 返回一个数组
                      function example() {
                        return [1, 2, 3];
                      }
                      let [a, b, c] = example();
                      // 返回一个对象
                      function example() {
                        return {
                          foo: 1,
                          bar: 2
                        };
                      }
                      let { foo, bar } = example();
                      

                      🎲4.3 函数参数的定义

                      解构赋值可以方便地将一组参数与变量名对应起来。

                      // 参数是一组有次序的值
                      function f([x, y, z]) { ... }
                      f([1, 2, 3]);
                      // 参数是一组无次序的值
                      function f({x, y, z}) { ... }
                      f({z: 3, y: 2, x: 1});
                      

                      🎲4.4 提取 JSON 数据

                      解构赋值对提取 JSON 对象中的数据,尤其有用。

                      let jsonData = {
                        id: 42,
                        status: "OK",
                        data: [867, 5309]
                      };
                      let { id, status, data: number } = jsonData;
                      console.log(id, status, number);
                      // 42, "OK", [867, 5309]
                      

                      上面代码可以快速提取 JSON 数据的值。

                      🎲4.5 函数参数的默认值

                      jQuery.ajax = function (url, {
                        async = true,
                        beforeSend = function () {},
                        cache = true,
                        complete = function () {},
                        crossDomain = false,
                        global = true,
                        // ... more config
                      } = {}) {
                        // ... do stuff
                      };
                      

                      指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';这样的语句。



                      JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript


转载请注明来自码农世界,本文标题:《探索 JavaScript 新增声明命令与解构赋值的魅力:从 ES5 迈向 ES6》

百度分享代码,如果开启HTTPS请参考李洋个人博客
每一天,每一秒,你所做的决定都会改变你的人生!

发表评论

快捷回复:

评论列表 (暂无评论,71人围观)参与讨论

还没有评论,来说两句吧...

Top