《React+Redux前端开发实战》—1.1.7 ES 6语法

举报
华章计算机 发表于 2019/07/24 23:04:53 2019/07/24
【摘要】 本节书摘来自华章计算机《React+Redux前端开发实战》一书中的第1章,第1.1.7节,作者是徐顺发.

1.1.7  ES 6语法

  ES 6(ECMAScript 6.0)是一个历史名词,也是一个泛指,指代ECMAScript 5.1版本之后JavaScript的下一代标准。其中包含ES 2015、ES 2016和ES 2017等,而这些年份表示在当年发布的正式版本的语言标准。

  最早的ECMAScript 1.0于1997年发布,ECMAScript 2.0于1998年发布,ECMAScript 3.0于1999年发布。有意思的是,2000年ECMAScript 4.0的草案由于太过于激进没有被发布。到了2007年,ECMAScript 4.0草案发布。当时以Microsoft和Google为首的互联网“巨头”反对ES的大幅升级,希望能小幅改动;而Brendan Eich(JavaScript创造者)为首的Mozilla公司坚持当时的草案。由于分歧太大,2018年而中止了对ECMAScript 4.0的开发,将其中激进的部分放到以后的版本,将其中改动小的部分发布为ECMAScript 3.1,之后又将其改名为ECMAScript 5,并于2009年12月发布。在2015年6月,ECMAScript 6正式通过。但很多人不知道,时至今日,JavaScript初学者学习的其实就是ES 3.0版本。目前为止,各大浏览器厂商对ES 6语法特性的支持已经超过90%。

  以上是对ECMAScript语言规范的简单历史介绍。

  由于本书使用的示例代码会涉及ES 6相关语法,因此下面对项目中经常使用到的几点特性进行简单介绍。

  1.变量声明let和const

  ES 6之前,通常用var关键字来声明变量。无论在何处声明,都会被视为在所在函数作用域最顶部(变量提升)。那么为什么需要用let和const关键词来创建变量或常量呢?理由是:

  •  可以解决ES 5使用var初始化变量时会出现的变量提升问题;

  •  可以解决使用闭包时出错的问题;

  •  ES 5只有全局作用域和函数作用域,却没有块级作用域;

  •  可以解决使用计数的for循环变量时会导致泄露为全局变量的问题。

  let命令表示被声明的变量值在作用域内生效。比如:

  

  {

   let a = 1;

   var b = 2;

  }

  a     // 报错ReferenceError

  b    // 2

提示:以上代码可以在浏览器开发工具的Console模式中调试。

  从上述代码可以看出,let声明的代码只能在其所在的代码块内有效,出了代码块,就会出错。

  另外,对于let来说,不存在变量提升。在一般代码逻辑中,变量应该是定义后才可以使用,但var的变量提升却可以先使用再定义,多少有些“奇怪”,而let就更改了这种语法行为。要使用一个变量必须先声明,不然就报错,显然这样更合理。

  var和let的对比示例:

  

  Console.log(a);    // undefined

  var a = 1;

  Console.log(a)            // 报错 ReferenceError

  let a = 1;

  此外,let不允许重复声明,比如:

  

  // 报错

  function func(){

   let a = 1;

   var a = 2;

  }

  // 报错

  function func(){

   let a = 1;

   let a = 2;

  }

  

  在代码块内,使用let声明变量之前,该变量都是不可用的(不可访问、不可赋值等)。在语法上,这被称为“暂时性死区”(Temporal Dead Zone,TDZ)。

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

  if (true) {

   // TDZ开始,不可访问,不可赋值

   temp = "hello";                     // ReferenceError

   console.log(temp);                   // ReferenceError

   let temp;                                 // TDZ结束

   console.log(temp);                   // 输出undefined,可访问

  

   temp = 1;                                // 可赋值

   console.log(temp);                   // 输出1,访问

  }

  

  在ES 5中,变量提升可能还会导致内层变量覆盖外层变量,比如:

  

  var i = 1;

  function func() { 

      console.log(i); 

      if (true) { 

          var i = 1; 

      } 

  } 

  func();                         // undefined

  

  let还引入了块级作用域的概念,传统ES 5中不存在块级作用域。假如没有块级作用域,可能会碰到这种问题:

  

  var arr = [1, 2, 3, 4]

  for (var i = 0; i < arr.length; i++){

   console.log(i);

  }

  console.log(i);              // 4

  

  上述代码希望达到的结果是,在for循环之后变量i被垃圾回收机制回收。但用来计数的循环变量在循环结束后并没有被回收,内存泄露为了全局变量。这种场景非常适合使用块级作用域let:

  var arr = [1, 2, 3, 4]

  for (let i = 0; i < arr.length; i++){

   console.log(i);

  }

  console.log(i);       // Uncaught ReferenceError: i is not defined

  

  从上面的示例代码可以看出,当循环结束后,就可以将不需要的用于计数的变量回收,让它消失。虽然一个简单的变量泄漏并不会造成很大危害,但这种写法是错误的。

  块级作用域的出现无疑带来了很多好处,它允许作用域的任意嵌套,例如:

  

  {{{{ 

      {let i = 1;} 

      console.log(i);      // 报错 

  }}}}

  

  内层作用域可以使用跟外层同名的变量名,比如:

  

  {{{{ 

   let i =1;

   console.log(i);            // 1

   {

    let i = 2;

    console.log(i);   // 2

   } 

  }}}}

  

  块级作用域还使立即执行函数表达式(IIFE)不再成为必要项,比如:

  

  // 立即执行函数

  (function () {

   var a = ...;

     ...

  }());

  // 块级作用域写法

  {

    let a = ...;

    ...

  }

  

  再来看看const。const用于声明只读的常量,一旦声明就不能改变。和let一样,const只在块级作用域内有效,不存在变量提升,存在暂时性死区和不可重复声明。

  2.解构赋值

  按照一定模式从数组或对象中提取值,对变量进行赋值,叫做解构赋值(Destructuring)。

注意:解构赋值的对象是数组或对象,作用是赋值。

  用于对象的解构赋值示例:

  

  const cat = {

   name: 'Tom',

   sex: 'male',

   age: 3

  };

  let { name, sex, age }  = cat;

  console.log(name, sex, age);      // Tom male 3

  

  上述代码将cat中的属性解构出来并赋值给name、sex和age。同样的示例,传统写法如下:

  

  const cat = {

   name: 'Tom',

   sex: 'male',

   age: 3

  };

  let name = cat.name;

  let sex = cat.sex;

  let age = cat.age;

  

  对象解构也可以指定默认值:

  

  var {a =1} = {};

  a     // 1

  var {a,  b = 2} = {a: 1}

  a     // 1

  b    // 2

  

  当解构不成功时,变量的值为undefined:

  

  let {a} = {b: 1};

  a     // undefined

  

  ES 6的解构赋值给开发者带来了很大的便捷,这就是解构赋值的魅力。同样,解构赋值也能在数组中使用。

  数组的解构赋值示例:

  

  let [a, b , c] = [1, 2, 3];

  a     // 1

  b    // 2

  c     // 3

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

  x     // 1

  y     // 3

  let [e, f, …g] = ["hello"];

  e     // "hello"

  f     // undefined

  g    // [ ]

  

  以上代码表明可以从数组中提取值,按照对应位置赋值给对应变量。如果解构失败就会赋值为undfined 。如果等号右边是不可遍历的结构,也会报错。

  

  // 报错

  let [a] = 1;

  let [a] = false;

  let [a] = {};

  let [a] = NaN;

  let [a] = undefined;

  

  以上都会报错。

  在解构赋值中也允许有默认值,例如:

  let {a = [1, 2, 3]} = { };

  a     // [1, 2, 3]

  let [x, y = 'hi'] = ["a"];

  x     // x='a', y='b'

  3.拓展运算符(spread)…

  拓展运算符(spread)是3个点(…)。可以将它比作rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。下面来看看它有哪些作用。

  (1)合并数组。

  在ES 5中要合并两个数组,写法是这样的:

  

  var a = [1, 2];

  var b = [3, 4];

  a.concat(b);   // [1, 2, 3, 4]

  

  但在ES 6中拓展运算符提供了合并数组的新写法:

  

  let a = [1, 2];

  let b = [3, 4];

  [...a, …b];       // [1, 2, 3, 4]

  

  如果想让一个数组添加到另一个数组后面,在ES 5中是这样写的:

  

  var x = ["a", "b"];

  var y = ["c", "d"];

  Array.prototype.push.apply(arr1, arr2);

  arr1               // ["a", "b", "c", "d"]

  

  上述代码中由于push()方法不能为数组,所以通过apply()方法变相使用。但现在有了ES 6的拓展运算符后就可以直接使用push()方法了:

  

  let x = ["a", "b"];

  let y = ["c", "d"];

  x.push(…y);    // ["a", "b", "c", "d"]

  

  (2)数组复制。

  拓展运算符还可以用于数组复制,但要注意的是,复制的是指向底层数据结构的指针,并非复制一个全新的数组。

  数组复制示例:

  

  const x = ['a', 'b'];

  const x1 = x;

  x1[0];     // 'a'

  x1[0] = 'c';

  x            // ['c', 'b']

  

  (3)与解构赋值结合。

  拓展运算符可以和解构赋值相结合用于生成新数组:

  

  const [arr1, …arr2] = [1, 2, 3, 4];

  arr1              // 1

  arr2              // [2, 3, 4]

  

  注意,使用拓展运算符给数组赋值时,必须放在参数最后的位置,不然会报错。例如:

  const […arr1, arr2] = [1, 2, 3, 4];         // 报错

  const [1, …arr1, arr2] = [1, 2, 3, 4];            // 报错

  

  (4)函数调用(替代apply()方法)。

  在ES 5中要合并两个数组,写法是这样的:

  

  function add(a, b) {

   return a + b;

  }

  const num = [1, 10];

  add.apply(null, num);   // 11

  

  在ES 6中可以这样写:

  

  function add(a, b) {

   return a + b;

  }

  const num = [1, 10];

  add(…num);                 // 11

  

  上述代码使用拓展运算符将一个数组变为参数序列。当然,拓展运算符也可以和普通函数参数相结合使用,非常灵活。比如:

  

  function add(a, b, c, d) {

   return a + b + c + d;

  }

  const num = [1, 10];

  add(2, …num, -2);               // 11

  

  拓展运算符中的表达式如下:

  

   […(true  [1, 2] : [3]), 'a'];    // [1, 2, 'a']

  4.箭头函数

  ES 6对于函数的拓展中增加了箭头函数=>,用于对函数的定义。

  箭头函数语法很简单,先定义自变量,然后是箭头和函数主体。箭头函数相当于匿名函数并简化了函数定义。

  不引入参数的箭头函数示例:

  

  var sum = () => 1+2;                       // 圆括号代表参数部分

  // 等同于

  var sum = function() {

   return 1 + 2;

  }

  

  引入参数的箭头函数示例:

  

  // 单个参数

  var sum = value => value;         // 可以不给参数value加小括号

  // 等同于

  var sum = function(value) {

   return value;

  };

  // 多个参数

  var sum = (a, b) => a + b;

  // 等同于

  var sum = function(a, b) {

   return a + b;

  };

  

  花括号{}内的函数主体部分写法基本等同于传统函数写法。

注意:如果箭头函数内要返回自定义对象,需要用小括号把对象括起来。例如:

  var getInfo = id =>({

   id: id,

   title: 'Awesome React'

  });

  // 等同于

  var getInfo = function(id) {

   return {

    id: id,

    title: 'Awesome React'

   }

  }

  

  箭头函数与传统的JavaScript函数主要区别如下:

  •  箭头函数内置this不可改变;

  •  箭头函数不能使用new关键字来实例化对象;

  •  箭头函数没有arguments对象,无法通过arguments对象访问传入的参数。

  这些差异的存在是有理可循的。首先,对this的绑定是JavaScript错误的常见来源之一,容易丢失函数内置数值,或得出意外结果;其次,将箭头函数限制为使用固定this引用,有利于JavaScript引擎优化处理。

  箭头函数看似匿名函数的简写,但与匿名函数有明显区别,箭头函数内部的this是词法作用域,由上下文确定。如果使用了箭头函数,就不能对this进行修改,所以用call()或apply()调用箭头函数时都无法对this进行绑定,传入的第1个参数会被忽略。

  更多详情,可参考阮一峰的《ECMAScript 6入门》一书。

注意:词法作用域是定义在词法阶段的作用域,它在代码书写的时候就已经确定。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。