首页 > 技术文章 > es6新特性

lexiaoyao1992 2018-10-09 11:06 原文

1、块级作用域let和const

  let 用来声明变量,用法类似var,但所声明的变量,只在let命令所在的代码块内有效,没有变量提升,相同作用域内不能重复声明;

  let死区

  在块级作用域内如果使用let声明了某个变量,那么这个变量名必须在声明它的语句后使用,即使块外部的变量有与之重名的也不行。从块开头到声明变量的语句中间的部分,称为这个变量的“暂时性死区”。

  const

  const用于声明常量,同一常量只可声明一次,声明后不可修改;

  但是const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

  注意事项:

  A、es6有6中声明变量的方法var,function,let,const,import,class;

  B、let声明的全局变量,window不可以调用吗,let的作用域和window的作用域是分开的;

  C、const声明的变量,不可以重新赋值,声明的数组和对象可以赋值。

2、模板字符串

  es6之前用+来进行字符串拼接,ES6的字符串远不用如此麻烦,我们可以在反引号(~符的unshift键)中使用新语法 ${变量名称} 表示

  例如:

// ES6
var name = `Your name is ${first} ${last}. `;
var url = `http://localhost:3000/api/messages/${id}`;

3、多行字符串

// ES5 多行字符串需要 + 号连接
var roadPoem = 'Then took the other, as just as fair,nt'
    + 'And having perhaps the better claimnt'
    + 'Because it was grassy and wanted wear,nt'
    + 'Though as for that the passing therent'
    + 'Had worn them really about the same,nt';
var fourAgreements = 'You have the right to be you.n
    You can only be you when you do your best.';

然而在ES6中,仅仅用反引号就可以解决了:

var roadPoem = `Then took the other, as just as fair,
    And having perhaps the better claim
    Because it was grassy and wanted wear,
    Though as for that the passing there
    Had worn them really about the same,`;
var fourAgreements = `You have the right to be you.
    You can only be you when you do your best.`;

4、解构赋值

  ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

  数组的解构赋值

  只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

  对象的解构赋值

  对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。与数组一样,解构也可以用于嵌套结构的对象。由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。

  字符串的解构赋值

  字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

  数值和布尔值的解构赋值

  解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。

  函数参数的解构赋值

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

  

  圆括号问题

  解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,一个式子到底是模式,还是表达式,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。由此带来的问题是,如果模式中出现圆括号怎么处理。ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号。但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。

  以下三种解构赋值不得使用圆括号

  (1)变量声明语句

  (2)函数参数

  (3)赋值语句的模式

  可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。

  解构赋值用途

  (1)交换变量的值

  变量可以直接以赋值的形式交换,这样的写法不仅简洁,而且易读,语义非常清晰。

  (2)从函数返回多个值

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

  (3)函数参数的定义

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

  (4)提取 JSON 数据

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

  (5)函数参数的默认值

  指定参数的默认值,就避免了在函数体内部再声明

  (6)遍历 Map 结构

  任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。

  (7)输入模块的指定方法

  加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

5、箭头函数

  当箭头函数所在语句只有一行时,它就会变成一个表达式,它会直接返回这个语句的值。但是如果你有多行语句,你就要明确的使用return

  由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

  箭头函数与非箭头function函数间有一个细微的区别,箭头函数没有它们自己的arguments对象。

  箭头函数不可当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。原因是它没有属于自己的this。

  箭头函数无法使用call()、apply()、bind()等方法改变this指向,因为它并没有this值。this值从上下文获取

6、默认参数

//ES5我们这样定义默认参数
var link = function (height, color, url) {
    var height = height || 50;
    var color = color || 'red';
    var url = url || 'http://azat.co';
    ...
}

这样做是有一点小问题的,当默认参数为0时就暴露问题了,因为在JavaScript中,0表示false。

// ES6可以默认值写在函数声明里
var link = function(height = 50, color = 'red', url = 'http://azat.co') {
  ...
}

7、pormise对象

先来看看Promise长什么样子

var promise = new Promise(function (resolve, reject){
    if (success){
      return resolve(data);
    } else {
      return reject(data);
    }
});

promise.then(function(data){
    console.log(data);
}, function(err){
    // deal the err.
})

这里的promise变量是Promise的实例。

  Promise对象在new的时候,接受一个回调函数作为参数,在Pomise对象创建的时候回执行回调函数里面的逻辑。

  现在来认识两个Promise内的回调函数:resolve、reject,这两个函数什么时候调用由你自己来控制,如上代码,假如success为你的接口获取的数据,当它为真的时候调用resolve,else不用说了。也就是resolve是逻辑成功的回调,reject是捕获异常的回调。在实例化的promise.then(),then接受两个函数式参数,第一个即为resolve,同理反之。有的同学说,这也没什么用啊,我自己写回调也可以。那么请看下面的代码。

promise.then(function(data){
    console.log(data) .....................(1)
    return data + 'first'
}).then(function(data){
    console.log(data) .....................(2)
}).catch(function(err){
    console.log(err)  .....................(3)
})

此时的Promise就变成了另外一种链式调用的写法,相信聪明的同学已经看出来其中的奥妙了。

在Promise对象链式调用中,.then()即为resolve的回调。Promise的一个特殊性在于,Promise的then方法返回的依然是一个Promise对象。如果同一个resolve获取的数据,要经过处理,可以把处理过得数据return出来,传递给下一个.then()来用,这样上边的代码就不难理解了吧,第二个.then()其实是第一个.then()方法return的Promise对象,所以可以用.then来连接。在一个链式调用结构中,每一个then都返回一个Promise对象,那么每一个函数体内都有两个回调函数resolve和reject,如果是这样,那不又变成回调地狱了。

其实Promise还有另一个方法catch,用这个函数接受一个回调函数来统一处理错误。

现在假设上边的promise 获取的数据为一个字符串’get success’,即 data=’get success’,这样上边的三个console.log输出的结果为:

(1) 'get success'
(2) 'get success first'
(3) 'error'

注意: Promise对象是异步的,什么是异步的呢?就是一个.then()内的逻辑没执行完,不会执行下一个。

控制并发的Promise

Promise有一个”静态方法”——Promise.all(注意并非是promise.prototype), 这个方法接受一个元素是Promise对象的数组。

这个方法也返回一个Promise对象,如果数组中所有的Promise对象都resolve了,那么这些resolve的值将作为一个数组作为Promise.all这个方法的返回值的(Promise对象)的resolve值,之后可以被then方法处理。如果数组中任意的Promise被reject,那么该reject的值就是Promise.all方法的返回值的reject值.

很op的一点是:
then方法的第一个回调函数接收的resolve值(如上所述,是一个数组)的顺序和Promise.all中参数数组的顺序一致,而不是按时间顺序排序。

还有一个和Promise.all相类似的方法Promise.race,它同样接收一个数组,只不过它只接受第一个被resolve的值。

8、export,import

 ES6的模块化的基本规则或特点, 欢迎补充:

    1:每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;

    2:每一个模块内声明的变量都是局部变量, 不会污染全局作用域;

    3:模块内部的变量或者函数可以通过export导出;

    4:一个模块可以导入别的模块

import和export基本语法

 第一种导出的方式:

  在lib.js文件中, 使用 export{接口} 导出接口, 大括号中的接口名字为上面定义的变量, importexport是对应的;

//lib.js 文件
let bar = "stringBar";
let foo = "stringFoo";
let fn0 = function() {
    console.log("fn0");
};
let fn1 = function() {
    console.log("fn1");
};
export{ bar , foo, fn0, fn1}

//main.js文件
import {bar,foo, fn0, fn1} from "./lib";
console.log(bar+"_"+foo);
fn0();
fn1();

第二种导出的方式:

   在export接口的时候, 我们可以使用 XX as YY, 把导出的接口名字改了, 比如: closureFn as sayingFn, 把这些接口名字改成不看文档就知道干什么的:

//lib.js文件
let fn0 = function() {
    console.log("fn0");
};
let obj0 = {}
export { fn0 as foo, obj0 as bar};

//main.js文件
import {foo, bar} from "./lib";
foo();
console.log(bar);

第三种导出的方式:

  这种方式是直接在export的地方定义导出的函数,或者变量:

//lib.js文件
export let foo = ()=> {console.log("fnFoo") ;return "foo"},bar = "stringBar";

//main.js文件
import {foo, bar} from "./lib";
console.log(foo());
console.log(bar);

 第四种导出的方式:

  这种导出的方式不需要知道变量的名字, 相当于是匿名的, 直接把开发的接口给export;
  如果一个js模块文件就只有一个功能, 那么就可以使用export default导出;

//lib.js
export default "string";

//main.js
import defaultString from "./lib";
console.log(defaultString);

第五种导出方式:

  export也能默认导出函数, 在import的时候, 名字随便写, 因为每一个模块的默认接口就一个:

//lib.js
let fn = () => "string";
export {fn as default};

//main.js
import defaultFn from "./lib";
console.log(defaultFn());

第六种导出方式:

  使用通配符*  ,重新导出其他模块的接口 (其实就是转载文章, 然后不注明出处啦);

//lib.js
export * from "./other";
//如果只想导出部分接口, 只要把接口名字列出来
//export {foo,fnFoo} from "./other";

//other.js
export let foo = "stringFoo", fnFoo = function() {console.log("fnFoo")};

//main.js
import {foo, fnFoo} from "./lib";
console.log(foo);
console.log(fnFoo());

其他:ES6的import和export提供相当多导入以及导出的语法;

  在import的时候可以使用通配符*导入外部的模块:

import * as obj from "./lib";
console.log(obj);

ES6导入的模块都是属于引用:

  每一个导入的js模块都是活的, 每一次访问该模块的变量或者函数都是最新的, 这个是原生ES6模块 与AMDCMD的区别之一,

9、类

 现在就来看看如何用ES6写一个类吧。ES6没有用函数, 而是使用原型实现类。我们创建一个类baseModel ,并且在这个类里定义了一个constructor 和一个 getName()方法:

class baseModel {
  constructor(options, data) {
    this.name = 'Base';
    this.url = 'http://azat.co/api';
    this.data = data;
    this.options = options;
   }
 
    getName() { // class method
        console.log(`Class name: ${this.name}`);
    }
}

AccountModel 从类baseModel 中继承而来:

class AccountModel extends baseModel {
    constructor(options, data) {
    //为了调用父级构造函数,可用super()参数传递:
    super({private: true}, ['32113123123', '524214691']); //call the parent method with super
       this.name = 'Account Model';
       this.url +='/accounts/';
    }
// 可以把 accountData 设置成一个属性:
  get accountsData() { //calculated attribute getter
     return this.data;
   }
}

----------------------


let accounts = new AccountModel(5);
accounts.getName();    //Class name: Account Model
console.log('Data is %s', accounts.accountsData);    //Data is  32113123123,524214691

 

10、iterable类型

为了统一集合类型,ES6标准引入了新的iterable类型,Array、Map和Set都属于iterable类型,具有iterable类型的集合可以通过新的for … of循环来遍历。

var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
    alert(x);
}
for (var x of s) { // 遍历Set
    alert(x);
}
for (var x of m) { // 遍历Map
    alert(x[0] + '=' + x[1]);
}

Map相关操作方法如下,Set同理:

var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined

11、延展操作符

通过它可以将数组作为参数直接传入函数:

var people=['Wayou','John','Sherlock'];
function sayHello(people1,people2,people3){
    console.log(`Hello ${people1},${people2},${people3}`);
}
//改写为
sayHello(...people);//输出:Hello Wayou,John,Sherlock 

在函数定义时可以通过…rest获取定义参数外的所有参数:

function foo(a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 结果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]

12、对象字面量增强

相对于ES5,ES6的对象字面量得到了很大程度的增强。这些改进我们可以输入更少的代码同时语法更易于理解。对象增强的功能:对象字面量简写(Object Literal Shorthand)、对象方法简写(Object Method Shorthand)、计算对象键(Object key)、对象解构(Object Destructuring)。

ES6对象字面量简写

javascript中的对象使用对象字面量很容易创建,现在来使用ES5来创建一个对象,在music中两个对象属性 typeheat,他们的分别也是来之于typeheat。代码如下:

//es5对象字面量
var type = 'rock';
var heat = '50%';
var music = {
  type: type,
  heat: heat
};
console.log(music);  // Object {type: "rock", heat: "50%"}

ES6对象字面量

然而我们现在可以使用ES6的对象重新写这个例子。在ES6中如何你的对象属性名和当然作用域中的变量名相同,那么现在必须要在把这个typeheat书写两次。ES6的对象会自动的帮你完成键到值的赋值。这样看起来代码更优雅也能节省一半的字符输入量。代码如下:

var type = 'rock';
var heat = '50%';
var music = {
  type,
  heat
};
console.log(music);  // Object {type: "rock", heat: "50%"}

ES5返回一个对象

假如我们创建一个函数他做了一些运算然后要返回这个函数中某些完成运算的变量为一个对象(函数返回多个值),比如:typeheat。在ES5中我们是这样写的。

function getMusic() {
  var type = 'rock';
  var heat = '50%';
  // 一些运算
  return { type: type, heat: heat };
}
console.log(getMusic().type);   // rock
console.log(getMusic().heat);   // 50%

ES6返回一个对象

现在使用ES6简洁优雅的重写这个函数返回一个对象,这里还是使用上面的函数,只是在返回对象的时候使用ES6的语法

function getMusic() {
  var type = 'rock';
  var heat = '50%';
  // 一些运算
  return { type, heat };
}
console.log(getMusic().type); // rock
console.log(getMusic().heat); // 50%

 

ES6对象方法简写

对象不仅仅是用来保存数据,他还可以用来保存函数。在ES5中我们也是通过给定一个键然后再给定一个匿名函数或命名函数。代码如下:

//ES5对象方法
var type = 'rock';
var heat = '50%';
var music = {
  type: type,
  heat: heat,
  description: function() {
    return '当前音乐风格为' + this.type + ',' + this.heat + '都喜欢';
  }
}
console.log(music.description()); // 当前音乐风格为rock,50%都喜欢

ES6对象方法

使用ES6语法重写上面的music对象,必须要在写上对象键然后还要写上function关键字。只需要方法名和圆括号再跟上花括号即可。代码如下:

var type = 'rock';
var heat = '50%';
var music = {
  type,
  heat,
  description() {
    return '当前音乐风格为' + this.type + ',' + this.heat + '都喜欢';
  }
}
console.log(music.description()); // 当前音乐风格为rock,50%都喜欢

ES6对象方法简写和字符串模板

还是使用上面的对象music,我们有一个方法description他返回的是一个字符串,但是字符串的拼接过程可以说是相当吃力的,如果稍微不注意很容易出错。使用ES6的字符串模将完美解决这个问题。字符串模板使用``将字符串包裹起来里面的变量使用${}包裹起来。代码如下:

var type = 'rock';
var heat = '50%';
var music = {
  type,
  heat,
  description() {
    return `当前音乐风格为${this.type},${this.heat}都喜欢'`;
  }
}
console.log(music.description()); // 当前音乐风格为rock,50%都喜欢'

ES6计算对象键(Keys)

在ES5中对象可以理解为一个关联数组或一个hashmaps。在ES5中创建对象的键就三种object.xxobject['xx']Object.defineProperty可以用来构建对象的键。在ES6中可以使用更多的方法来创建。

ES6计算对象键

在这次的music对象中,我们要使用一个变量field作为我们对象的键,heat作为这个键的值。代码如下:

var heat = '50%';
var field = 'rock';
var music = {
  [field]: heat
}
console.log(music); // Object {rock: "50%"}

 

在ES5中也可以使用如下代码定义,但是~~额。

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {value: value, enumerable: true, configurable: true, writable: true});
  } else {
    obj[key] = value;
  }
  return obj;
}
var heat = '50%';
var field = 'rock';
var music = _defineProperty({}, field, heat);
console.log(music)

 

ES6对象键计算表达式

可以在对象键的变量上调用方法!

var heat = '50%';
var field = 'Rock and Roll';
var music = {
  [field.toLowerCase()]: heat
}
console.log(music); // Object {rock and roll: "50%"}

 

ES5同样也是可以实现,只是~~

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {value: value, enumerable: true, configurable: true, writable: true});
  } else {
    obj[key] = value;
  }
  return obj;
}
var heat = '50%';
var field = 'Rock and Roll';
var music = _defineProperty({}, field.toLowerCase(), heat);
console.log(music); // Object {rock and roll: "50%"}

 

还可以使用不同的数组方法为我们的对象键赋值,使用[]将会计算对象的键值。代码如下:

let people = [20, 25, 30];
let music = {
  people,
  [people]: 'They all love rock and roll',
  [people.length]: 'object key is 3',
  [{}]: 'empty object'
}
console.log(music);
console.log(music.people);
console.log(music['people']);
console.log(music[people]);
console.log(music[people.length]);
console.log(music['[object Object]']);
console.log(music[music]);
/*
Object {3: "object key is 3", people: Array[3], 20,25,30: "They all love rock and roll", [object Object]: "empty object"}
[20, 25, 30]
[20, 25, 30]
They all love rock and roll
object key is 3
empty object
empty object
*/

 

推荐阅读