浅谈ES6 常用 新特性 并了解其兼容性解决方案

4,517 阅读6分钟

es6 兼容性 如何使用babel来编译我们的js(es6)代码

首先来谈一下es6 es6在大多数情况是没问题,据了解国内的环境,大部分业务(银行系统除外)支持到IE8就可以了,如果要兼容到IE6+,可以借助babel插件来完成,只要babel支持的都没问题的。

ES6新特性在Babel下的兼容性列表

ES6特性 兼容性
箭头函数 支持
类的声明和继承 部分支持,IE8不支持
增强的对象字面量 支持
字符串模板 支持
解构 支持,但注意使用方式
参数默认值,不定参数,拓展参数 支持
let与const 支持
for of IE不支持
iterator, generator 不支持
模块 module、Proxies、Symbol 不支持
Map,Set 和 WeakMap,WeakSet 不支持
Promises、Math,Number,String,Object 的新API 不支持
export & import 支持
生成器函数 不支持
数组拷贝 支持

那么我们来说一下如何使用这个babel来兼容我们的es6 代码(当然前提是babeljs文件已经存在在项目中,这里只是说明如何在webpack中让babel编译我们的js或者是jsx文件)

上一小小段代码:

module: {
        loaders: [
            //
            {
                test: /\.(jsx|js)$/,
                loader: ['babel-loader'],
                exclude: /node_modules/
            }
        ]
    }

一般处理我们的打包文件,我们都会放在module下的loader里去处理,上段代码中将以jsx/js为结尾的文件都会做一个babel来编译。

es6好用的新特性

1.var let const

在之前的js的版本中,我们多会选择用var来声明定义一个变量,这么做的弊端会造成浪费和占用了大量内存,在新的js版本es6中出现了两个新的用来声明标识符的方式:let 和 const

其实很好理解let其实定义了一个拥有着自己代码块的变量,所谓代码块其实就是当你在一个{}中使用let 定义一个变量后,let只在{}中存在,但是与js的函数作用域不同的是let定义的变量不会被提升。

var a = 1;
function fn1(){
    console.log(a);
    a = 2;
}
console.log(a);//1
fn1();  // 2

var b = 2;
function fn2(){
    console.log(b);
    let b = 1;
}
console.log(b)//2
fn2();  //

从这个例子中我们可以看到分别打印a,b变量 用let改的值在出了函数{}后失去了作用,而不是用let的赋值在出了{}后依然生效。

再举一个好用的应用场景,我们在一些特定需求时,会避免不了使用闭包,不了解闭包请移步跨域 闭包 ,例如一个常见的烂大街的问题,一个ul里的li打印它的索引或者内容。在es5中我们是这样做的:

var arr = [];

for(var i=0; i<5; i++){
    arr.push((function (a){
        return function (){
            console.log(a);
        }
    })(i))
}
arr[1]()  // 1
arr[2]()  // 2
arr[3]()  // 3

闭包的缺点时会占用内存不是放掉,那么我们来换一种写法:

var arr = [];  // let arr = [] 都可以
for(let i=0; i<5; i++){
    arr.push(function (){
        console.log(i)
    })
}
arr[0]()  // 0
arr[1]()  // 1
arr[2]()  // 2

使用let会完美的解决这个问题。
再说说const就很好理解了,它就是定义一个常量的声明方式,拥有其他语言基础这个概念就不难理解,再次就不在做多余赘述。

2.解构赋值

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

let [a, b, c] = [1, 2, 3];
//等同于
let a = 1;
let b = 2;
let c = 3;

对象的解构赋值:获取对象的多个属性并且使用一条语句将它们赋给多个变量。

var {
  StyleSheet,
  Text,
  View
} = React;
等同于
var StyleSheet = React.StyleSheet;

var Text = React.Text;
var View = React.View;

3.箭头函数

ES6中新增箭头操作符用于简化函数的写法,操作符左边为参数,右边为具体操作和返回值。

var sum = (num1, num2) => { return num1 + num2; }
//等同于
var sum = function(num1, num2) {
    return num1 + num2;
 };

同时箭头函数还有修复了this的指向,使其永远指向词法作用域:

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
obj.getAge(); // 25

4.类 class

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类,与多数传统语言类似。
//定义类

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

同样的继承使用的是extends关键字

5.字符串

新增字符串处理函数:

startsWith() //检查是否以指定字符串开头,返回布尔值
let str = 'javascript';
str.startsWith('java'); 
endsWith() //检查是否以指定字符串结尾,返回布尔值
let str = 'javascript'
str.endsWith('pt');
includes() //检查字符串是否包含指定字符串 ,返回布尔值
let str = 'javascript';
str.includes('j');
repeat() //指定字符串重复次数
let str = 'javascript';
str.repeat('3');

Template Literals 字符串模板
使用 字符串模板字面量,我可以在字符串中直接使用特殊字符,而不用转义。

var text = "This string contains \"double quotes\" which are escaped.";
let text = `This string contains "double quotes" which don't need to be escaped anymore.`;

字符串模板字面量 还支持直接插入变量,可以实现字符串与变量的直接连接输出.

var name = 'Tiger';
var age = 13;

console.log('My cat is named ' + name + ' and is ' + age + ' years old.');
更简单的版本:
const name = 'Tiger';
const age = 13;

console.log(`My cat is named ${name} and is ${age} years old.`);

ES5中,我们要这样生成多行文本:

var text = (
    'cat\n' +
    'dog\n' +
    'nickelodeon'
);
或者:
var text = [
    'cat',
    'dog',
    'nickelodeon'
].join('\n');

字符串模板字面量 让我们不必特别关注多行字符串中的换行转义符号,直接换行即可:

let text = ( `cat
dog
nickelodeon`
);

字符串模板字面量 内部可以使用表达式,像这样:

let today = new Date();
let text = `The time and date is ${today.toLocaleString()};

6.第七种基本类型 Symbol

Symbols和其它基本类型大不一样。
从创建开始就是不可变的。你不能为它设置属性(如果你在严谨模式下尝试,会报类型错误)。它可以作为属性名。这是它的类字符串性质。
另一方面,每一个symbol都是唯一的。与其他的不同(就算他们的描述是一样的)你可以很容易地新创建一个。这是它的类对象性质。
ES6 symbols与Lisp和Ruby中的更传统的symbols很类似,但是没有如此紧密地集成到语言中。在Lisp中,所有的标识符都是symbols。在JS中,标识符和大多数属性的键值的首先仍是字符串,Symbols只是为开发人员提供了一个额外选择。
关于symbols的一个忠告:与JS中的其它类型不同,它不能被自动转换为字符串。试图拼接symbol与字符串将会引起类型错误。

(表示读过文档后依然蒙圈 先选择性放弃这个点)

7.Promise

一个 Promise 对象可以理解为一次将要执行的操作(常常被用于异步操作),使用了 Promise 对象之后可以用一种链式调用的方式来组织代码,让代码更加直观。而且由于 Promise.all 这样的方法存在,可以让同时执行多个操作变得简单。接下来就来简单介绍 Promise 对象。

直观些直接上代码:

function helloWorld (ready) {
    return new Promise(function (resolve, reject) {
        if (ready) {
            resolve("Hello World!");
        } else {
            reject("Good bye!");
        }
    });
}

helloWorld(true).then(function (message) {
    alert(message);
}, function (error) {
    alert(error);
});

上面的代码实现的功能非常简单,helloWord 函数接受一个参数,如果为 true 就打印 "Hello World!",如果为 false 就打印错误的信息。helloWord 函数返回的是一个 Promise 对象。
在 Promise 对象当中有两个重要方法————resolve 和 reject。
resolve 方法可以使 Promise 对象的状态改变成成功,同时传递一个参数用于后续成功后的操作,在这个例子当中就是 Hello World!字符串。
reject 方法则是将 Promise 对象的状态改变为失败,同时将错误的信息传递到后续错误处理的操作。

Promise 的三种状态
上面提到了 resolve 和 reject 可以改变 Promise 对象的状态,那么它究竟有哪些状态呢?
Promise 对象有三种状态:

  • Fulfilled 可以理解为成功的状态
  • Rejected 可以理解为失败的状态
  • Pending 既不是 Fulfilld 也不是 Rejected 的状态,可以理解为 Promise 对象实例创建时候的初始状态
    helloWorld 的例子中的 then 方法就是根据 Promise 对象的状态来确定执行的操作,resolve 时执行第一个函数(onFulfilled),reject 时执行第二个函数(onRejected)。
    then 和 catch
    then
    helloWorld 的例子当中利用了 then(onFulfilld, onRejected) 方法来执行一个任务打印 "Hello World!",在多个任务的情况下 then 方法同样可以用一个清晰的方式完成。
    function printHello (ready) {
    return new Promise(function (resolve, reject) {
        if (ready) {
            resolve("Hello");
        } else {
            reject("Good bye!");
       }
    });
    }

上述例子通过链式调用的方式,按顺序打印出了相应的内容。then 可以使用链式调用的写法原因在于,每一次执行该方法时总是会返回一个 Promise 对象。另外,在 then onFulfilled 的函数当中的返回值,可以作为后续操作的参数,因此上面的例子也可以写成:

printHello(true).then(function (message) {
    return message;
}).then(function (message) {
    return message  + ' World';
}).then(function (message) {
    return message + '!';
}).then(function (message) {
    alert(message);
});
function printWorld () {
    alert("World");
}

function printExclamation () {
    alert("!");
}

printHello(true)
    .then(function(message){
    alert(message);
    })
    .then(printWorld)
    .then(printExclamation);

同样可以打印出正确的内容。
catch
catch 方法是 then(onFulfilled, onRejected) 方法当中 onRejected 函数的一个简单的写法,也就是说可以写成 then(fn).catch(fn),相当于 then(fn).then(null, fn)。使用 catch 的写法比一般的写法更加清晰明确。
Promise.all 和 Promise.race
Promise.all 可以接收一个元素为 Promise 对象的数组作为参数,当这个数组里面所有的 Promise 对象都变为 resolve 时,该方法才会返回。

var p1 = new Promise(function (resolve) {
    setTimeout(function () {
        resolve("Hello");
    }, 3000);
});

var p2 = new Promise(function (resolve) {
    setTimeout(function () {
        resolve("World");
    }, 1000);
});

Promise.all([p1, p2]).then(function (result) {
    console.log(result); // ["Hello", "World"]
});

上面的例子模拟了传输两个数据需要不同的时长,虽然 p2 的速度比 p1 要快,但是 Promise.all 方法会按照数组里面的顺序将结果返回。
日常开发中经常会遇到这样的需求,在不同的接口请求数据然后拼合成自己所需的数据,通常这些接口之间没有关联(例如不需要前一个接口的数据作为后一个接口的参数),这个时候 Promise.all 方法就可以派上用场了。
还有一个和 Promise.all 相类似的方法 Promise.race,它同样接收一个数组,不同的是只要该数组中的 Promise 对象的状态发生变化(无论是 resolve 还是 reject)该方法都会返回。
兼容性
最后是关于 Promise 对象的兼容性问题。

在浏览器端,一些主流的浏览器都已经可以使用 Promise 对象进行开发,在 Node.js 配合 babel 也可以很方便地使用。
如果要兼容旧的浏览器,建议可以寻找一些第三方的解决方案,例如 jQuery 的 $.Deferred。