ES6新语法(一)

619 阅读8分钟

一、块级作用域和let声明

  • let 声明的变量的作用域是块级的;
  • let 不能重复声明已存在的变量;
  • let 有暂时死区,不会被提升。

for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

  • for( let i = 0; i< 5; i++) 这句话的圆括号之间,有一个隐藏的作用域
  • for( let i = 0; i< 5; i++) { 循环体 } 在每次执行循环体之前,JS 引擎会把 i 在循环体的上下文中重新声明及初始化一次。
  • 每次for循环中的i都是不同的i
for (let i = 0; i < 3; i++) {
  console.log(i);
}
=======>
for (let i = 0; i < 3; i++) {
  let i = 隐藏作用域的i
  console.log(i);
}

var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

二、四种声明(除了class,import)

JS变量会经历,创建create、初始化initialize 和赋值assign三个过程

function 声明:会在代码执行之前就「创建、初始化并赋值」。 

var 声明:会在代码执行之前就将「创建变量,并将其初始化为 undefined,之后遇到var a = 0语句赋值」。

let声明:

{
  let x = 1
  x = 2
}
  1. 找到所有用 let 声明的变量,在环境中「创建」这些变量
  2. 开始执行代码(注意现在还没有初始化),这个时间就是暂时死区。
  3. 执行 x = 1,将 x 「初始化」为 1(这并不是一次赋值,如果代码是 let x,就将 x 初始化为 undefined)
  4. 执行 x = 2,对 x 进行「赋值」

如果 let x let x = x的初始化过程失败了,那么 x 变量就将永远处于 created 状态。 你无法再次对 x 进行初始化(初始化只有一次机会,而那次机会你失败了)。

const声明:就是把初始化和赋值弄到一块,const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

如果真的想将对象冻结,应该使用Object.freeze方法。

const foo = Object.freeze({});

不过如果对象的属性是对象那么也应该将它冻结

var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。顶层对象就是window,ES5 之中,顶层对象的属性与全局变量是等价的。

三、解构赋值

把变量从数组和对象中提取出来,并对变量进行赋值

3.1数组的解构赋值

只要等号两边的模式相同,左边的变量就会被赋予对应的值。

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

不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

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

解构不成功,变量的值就等于undefined。

let [bar, foo] = [1];

3.2对象的解构赋值

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

如果变量名与属性名不一致,必须写成下面这样。

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。

因为let { foo}是let { foo: foo }的缩写

let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

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

let x;
{x} = {x: 1};

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

我们可以做一下很酷的事;

let { log, sin, cos } = Math;

轻松地拿到了Math的三个方法

3.3字符串的解构赋值

字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';

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

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

四、模板字符串

需要拼接字符串的时候尽量改成使用模板字符串:

const foo = 'this is a' + example;

const foo = `this is a ${example}`;

五、字符表示方法

JavaScript 共有 6 种方法可以表示一个字符。

'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

六、字符串的新API

传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
  • trimStart()和trimEnd()。它们的行为与trim()一致,trimStart()消除字符串头部的空格,trimEnd()消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
  • repeat()返回一个新字符串,表示将原字符串重复n次。

七、超级重要的数值API

ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。参数是字符串

Number.parseInt('12.34')
Number.parseFloat('123.45#')

遇到不会的数字处理时,查一下Math的API

八、rest修饰符

rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function add(...values) {
      //
}

add(2, 5, 3) // 10

rest是一个真正的数组,而arguments是伪数组

九、箭头函数

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

箭头函数可以让this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM 事件的回调函数封装在一个对象里面。

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

由于箭头函数使得this从“动态”变成“静态”,下面两个场合不应该使用箭头函数。

第一个场合是定义对象的方法,且该方法内部包括this。第二个场合是需要动态this的时候,也不应使用箭头函数。第二个场合是需要动态this的时候,也不应使用箭头函数。

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});

上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。

可以理解为我们点击的时候是浏览器调用的

九、函数name属性

函数的name属性,返回该函数的函数名

如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

Function构造函数返回的函数实例,name属性的值为anonymous

(new Function).name // "anonymous"

bind返回的函数,name属性值会加上bound前缀。

function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

十、try...catch...finally

catch子句包含try块中抛出异常时要执行的语句。也就是,你想让try语句中的内容成功, 如果没成功,你想控制接下来发生的事情,这时你可以在catch语句中实现。 如果在try块中有任何一个语句(或者从try块中调用的函数)抛出异常,控制立即转向catch子句。如果在try块中没有异常抛出,会跳过catch子句。

finally子句在try块和catch块之后执行但是在下一个try声明之前执行。无论是否有异常抛出或捕获它总是执行。

try和throw搭配使用

throw语句用来抛出一个用户自定义的异常。当前函数的执行将被停止(throw之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个catch块。如果调用者函数中没有catch块,程序将会终止。