发布日期 » 2018年4月5日 星期四

版权声明 » 帅华君原创文章,未经允许不得转载。

浅谈ES6标准规范

简介

ECMA?

European Computer Manufacturers Association,欧洲计算机制造商协会(ECMA),这个组织的目标是评估,开发和认可电信和计算机标准。

ECMAScript

ECMAScript 是脚本语言,javascript便是基于此拓展而来的。

ECMAScript 由ECMA国际标准化组织参与标准化的。

其他基于ECMAScript实现的脚本包括 SpiderMonkey、V8、JScript、QtScript、InScript 和 ActionScript。

ECMAScript6 (ES6)

ECMAScript6 是ECMAScript标准的未来版本。在ES5的基础上增加新的语法,满足编写复杂应用的需求,还新增了类、模块等概念。

该标准亦称为 ECMAScript 6 (ES6) 或 ECMAScript 2015 (ES2015)

Template Strings

ES6 新增两个字面量

  • template 字面量
  • tagged 字面量

Template

这是一种新的字符串字面量,它允许开发者使用多行字符串和表达式。

Template 字面量被两个反单引号(也称重音号)前后包裹住。

`...`

字面量中的表达式用美元符号和花括号组成的操作符包裹住。

`...${a+b}...`

这是一个使用 Template 字面量的例子

const firstname = 'gns';
console.log(`Hello ${firstname},
How are you?`);

//output
Hello gns,
How are you?

上方例子中ES6的写法换做ES5就会是这样

var firstname = 'gns';
console.log("Hello " + firstname + ", \nHow are you?");

//output
Hello gns,
How are you?

下面是另一个使用 Template 字面量的例子

var a = 10, b = 20;
console.log(`Addition value for ${a} and ${b} is ${a+b}`);
// Addition value 10 and 20 is 30.

var user = {name: 'gns'};
console.log(`Current User: ${user.name.toUpperCase()}.`);
// Current User: GNS.

Tagged Template

关于 Template 字面量另一个高级用法是 Tagged Template 字面量,有了这个高级用法,你可以使用函数修改 Template 字面量的输出规则。该函数的第一个参数是包含一组字符串字面量的数组(类似 “Hello”、“world”、“in this example”)。除了第一个类型为数组的参数外,其后的所有参数表示在 Template 字面量中处理过的数据。最后函数需要返回你希望返回的内容。需要注意的是,这里的函数名可定义为任何你想要的名字。

var a = 5;
var b = 10;
function tagFun(strings, ...values){
    console.log(strings[0]); // "Hello "
    console.log(strings[1]); // "world "
    console.log(strings[2]); // ""
    console.log(values[0]); // 15
    console.log(values[1]); // 50
    return "shuaihua";
}

tagFun `Hello ${a+b} world ${a*b}`;

Raw 字符串

raw 是一个特殊的属性,应用在 tagged 字面量函数中第一个字符串数组上,它能允许你获取到未转义前的字符串而非转义后的。

function tagFun(strings, ...values){
    console.log(strings.raw[0]); // "string text line 1 \n string text line 2"
}

tagFun `string text line 1 \n string text line 2`;

Let

let 语句声明块级作用域(而非函数级作用域 var)

let count = 2;
console.log(value); // 2
function fun(){
    let count = 4; // 为 count 赋予不同的值
    console.log(count);
}
fun();
console.log(count); // 2

在相同的函数或块级作用域中重复声明变量编译器将抛出语法错误。

let count = 2;
let count = 3; // Identifier 'count' has already declared

提升

let 将提升变量到块的顶部,然而,当尝试在变量声明之前访问变量,编译器将抛出引用错误。

function fun(){
    console.log(count); // ReferenceError
    let count = 4;
}
fun();

let 和 const 声明都将被提升(类似 var 和 function),但是由于TDZ暂时死区效应(进入作用域到变量被声明这段时间成为暂时死区或时间死区)。

var count = 1;
console.log(count); // SyntaxError: Identifier 'count' has already declared

上面的例子运行时依旧会抛出异常错误,这便是TDZ的缘故。

Constants

const 的运作原理类似于 let,不同之处在于你必须立即初始化使用 const 声明的变量,并且此后不得再次对该变量进行赋值,这便是常量。

const 声明方式为值创建了一个只读的引用。

const PI;
console.log(PI); // Missing initializar in const declaration

不能通过对变量重新赋值而改变变量的值,并且不能对该变量重新声明。

const PI = 3.14;
console.log(PI); // 3.14
PI = 3.14;
console.log(PI); // Identifier 'PI' has already been declared

在第一次使用 const 声明变量并赋之后,不能再次赋值

const PI;
PI = 3.14;
console.log(PI); // Uncaught TypeError Assignment to constant variable.(...)

const作用域

const PI = 3.14;
function fun(){
    const PI = 3.141;
    if(true){
        const PI = 3.14159;
        console.log(PI); // 3.14159
    }
    console.log(PI); // 3.141
}
console.log(PI); // 3.14
fun();

//output
// 3.14
// 3.14159
// 3.141

for…of

for-of 是 ES6 新增的意在在某些情景下替代 for-in 和 forEach() 的循环语句,并且支持新的迭代语法。

for(variable of iterable){
    statement
}

迭代一数组

let values = [10, 20, 30];
for(let value of values){
    console.log(value);
}

// 10
// 20
// 30

迭代一字符串

let color = "red";
for(let c of color){
    console.log(c);
}

// r
// e
// d

for…of / for…in

for…in 循环将迭代对象所有可枚举的属性。

for…of 语法只针对集合,而非全部对象。

var values = ["one", "two"];
for (var value in values){
    console.log("Index is: " + value);
}
// Index is: 0
// Index is 1

for (var value in values){
    console.log("Value is: " + values[value]);
}
// Value is: one
// Value is: two
var values = ["one", "two"];
for (let value of values){
    console.log("Value is: " + value);
}
// Value is: one
// Value is: two

箭头函数

使用箭头函数有两个好处。

  • 和 function 相比 => 有更短的书写语法。
  • 函数中 this 的指向由词法作用域(也称静态作用域)决定。

箭头函数总是匿名的。

var intro = () => "Welcome";

上方 ES6 语法示例代码等价于下方 ES5 语法写法。

var intro = function intro(){
    return "Welcome";
};

在 map 方法中使用箭头函数。

const arr = [1, 2, 3];
const squares = arr.map(x => x*x);

上方 ES6 语法示例代码等价于下方 ES5 语法写法。

var arr = [1, 2, 3];
var squares = arr.map(function(){
    return x * x;
});

携带一个参数

var multiplyBy2 = value1 => value1 * 2;
console.log(multiplyBy2(4)); // prints 8

上方 ES6 语法示例代码等价于下方 ES5 语法写法。

var multiplyBy2 = function multiplyBy2(value1){
    return value * 2;
}
console.log(multiplyBy2(4)); // prints 8

携带两个参数

var add = (value1, value2) => value1 + value2;
console.log(add(10, 20)); // prints 30

上方 ES6 示例代码等价于下方 ES5 代码写法。

var add = function add(value1, value2){
    return value1 + value2;
}
console.log(add(10, 20)); // prints 30

this 指向谁?

在箭头函数中,this的指向问题与普通函数中有些重要的不同之处

  • 传统函数中 this 的指向是动态的,由谁调用了该函数决定 this 的指向。
  • 箭头函数中拥有词法 this,也就是说 this 指向谁取决于其周围的代码。

下方变量在箭头函数中全部是基于词法分析的

  • arguments
  • super
  • this
  • new.target

箭头函数不会创建他自己的 this 上下文,因此箭头函数中的 this 指向它原本代表的意义。

在过去,我们经常这样使用 this。

function Person(){
    var _this = this;
    this.count = 0;
    setInterval(function(){
        console.log(_this.count++); // this 将指向 person 对象
    });
}

上方 ES5 语法示例代码等价于下方 ES6 语法写法。

function Person(){
    this.count = 0;
    setInterval(()=>{
        console.log(this.count++); // 此处 this 仍然指向 person 对象
    });
}

返回可迭代对象

当需要使用箭头函数返回可迭代对象是,花括号的写法会有歧义,因此需要注意一下情况。

const f = x=> ({bar: 123});

上方 ES6 语法示例代码等价于下方 ES5 语法代码

var f = function f(x){
    return {bar: 123};
}

rest 参数

形参前使用rest操作符,意味着该形参将是一个数组类型,并且其元素包含所有剩余实参。

在下方示例代码中,形参marks将收集所有无法在形参列表中匹配的实参。

function student(name, ...marks){
    console.log(name); // myname
    console.log(marks); // [10, 20, 30, 40, 50]
}
student('myname', 10, 20, 30, 40, 50);

rest / arguments

  • rest 参数仅仅收集那些无法在形参中一一对应的实参,而arguments则收集所有传入函数的实参
  • arguments 对象不是真正意义上的数组(累数组对象),而 rest 是真正意义上的 Array 类的一个实例,也就是说,可以直接使用 rest参数从 Array 类继承而来的方法,比如 sort、map、forEach 或 pop等。
  • arguments 对象自身还有其他功用性。

过去使用 arguments 对象的情景。

function student(name, marks){
    console.log(name); // myname
    console.log(arguments); // ['myname', 7, 4, 8, 2, 9]
    console.log(marks);
    console.log(arguments.sort()); // Uncaught TypeError: arguments.sort is not a function(...)
}
student('myname', 7, 4, 8, 2, 9);

上方 ES5 语法示例代码等价于下方 ES6 语法写法。

function student(name, ..marks){
    console.log(name); // myname
    console.log(marks); // [7, 4, 8, 2, 9]
    console.log(marks.sort()); // [2, 4, 7, 8, 9]
}
student('myname', 7, 4, 8, 2, 9);

其他用例

function student(name, ..marks){
    console.log(name); // myname
    console.log(marks);  // [10, 20, 30]
    var total = marks.reduce(function(a, b){
        rreturn a + b;
    });
    console.log(total); // 50
}
student('myname', 10, 20, 30, 40, 50);

默认参数

默认函参使得当在调用函数时没有为函数指定对应的实参时可以使用函数的默认函数。

在javascript中,没有传递实参的形参默认是 undefined,然而在一些情况下,开发者希望在没有实参的情况下能提供其他的默认值。这就是默认参数的意义。

function multiply(value1, value2 = 1){
    return value1 * value2;
}
console.log(multiply(4)); // 4

使用 ES5 语法写法如下

function multiply(value1, value2){
    if(value2 === void 0){
        value2 = 1;
    }
    return value1 * value2;
}
console.log(multiply(4)); // 4

将函数作为参数传递

默认参数总是先被执行,随后才会执行函数体内的函数声明(如果有的话)

在函数体内声明的函数无法在默认参数处引用,否则会抛出 ReferenceError。

function multiply(a, b = sqrt()){
    function sqrt(){
        return Math.SQRT2;
    }
    console.log(b);
    return a * b;
}
console.log(multiply(4));

下面示例代码则会正常运行

function sqrt(){
    return Math.SQRT2;
}
function multiply(a, b = sqrt()){
    console.log(b); // 1.4142135623730951
    return a * b;
}
console.log(multiply(4)); // 5.656854249492381

展开操作

当函数形参希望是一个一个的变量而非数组时,此时实参确实数组而又不想逐个访问数组元素并填入实参位置中时,可使用该语法。

通常情况下的使用示例

function myFunction(x, y, z){
    console.log(x);
    console.log(y);
    console.log(z);
}
var args = [0, 1, 2];
myFunction(args);

// [0, 1, 2]
// undefined
// undefined

上方示例中,函数希望传入三个实参,而实际上传入一个数组实参,如果能将数组元素展开分别传递给形参的x, y, z就完美了,ES2015中这一语法特性便可实现。

function myFunction(x, y, z){
    console.log(x);
    console.log(y);
    console.log(z);
}
myFunction(...args);
// 0
// 1
// 2

rest / spread

rest 操作符中的 … 与 spread操作符的 … 着实很相像,都是为了解构数组和对象。

一方面,rest 将未使用到的实参收集起来作为函数的形参,而 spread 将数组解构并将元素们作为函数的实参传递。

解构赋值

解构能很方便的从数组、对象或两者相互嵌套的数据中提取需要的值。

var a, b, rest;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2

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

对象解构

const obj = {total: 42, isValid: true}
const {total, isValis} = obj;
console.log(total); // 42
console.log(isValid); // true

从对象中提取所需的值,并将该值赋值给一个新的变量。

const obj = {total: 42, isValid:true};
const {total: tot, isValid: valid} = obj;
console.log(tot); // 42
console.log(valid); // true

对象与数组嵌套解构

var metadata = {
    title: 'JSC',
    translations: [
        {
            las_edit: '2018-03-21T19:20:11',
            url: '/dev/docs/Tools/',
            title: 'Javascript'
        }
    ]
};
var {title, translations: [{last_edit}]} = metadata;
console.log(title); // JSC
console.log(last_edit); // 2018-03-21T19:20:11

数组解构

var valueArray = ['one', 'two', 'three'];
var [one, two, three] = valueArray;
console.log(one); // one
console.log(two); // two
console.log(three); // three

数组中的元素可被赋予默认值。

var [one='one', two='two'] = ['1'];
console.log(one); // 1
console.log(two); // two
const [all, year, month, day] = /^(\d\d\d\d)-(\d\d)-(\d\d)$/.exec('1999-12-28');
console.log(all); // 1999-12-28
console.log(year); // 1999
console.log(12);
console.log(28);

用例 for-of 结合解构赋值

const user = [
    {name: 'leochen', age:25},
    {name: 'henry', age: 24},
];
for (const {name, age} of user){
    console.log(name, age);
}
// leochen 25
// henry 24

javascript已经是一个基于原型的语言,通过对象原型创建对象继承关系,并实现代码的复用。

ES6在传统的原型智商新增新的创建类的语法。

创建类的语法有两种,分别是类表达式和类声明。

类声明

其中一种定义类的手段就是使用类声明。为了声明类,需要使用 class 关键字和类名。

class Person {
    constructor(_dirstName, _lastName){
        this._firstName = _firstName;
        this._lastName = _lastName;
    }
}

类表达式

类表达式是另一种定义类的手段。类表达式中的类名是可选的,如果写类名,则之后类本身可以使用该类名访问该类。

var Person = class {
    constructor(_firstName, _lastName){
        this._firstName = _firstName;
        this._lastName = _lastName;
    }
}

上方示例中未命名类,下方为命名类。

var Person = class Person{
    constructor(_firstName, _lastName){
        this._firstName = _firstName;
        this._lastName = _lastName;
    }
}

构造函数

构造函数是一个特殊的方法,它用来创建并初始化一个类的一个实例。

类中只能存在一个 constructor 方法。

如果类中存在超过一个 constructor 方法将会抛出语法错误。

在构造函数中,使用 super 关键字调用父类的构造函数。

class Person {
    constructor(firstName, lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }
    fullName(){
        return this.firstName + ' ' + this.lastName;
    }
}
let p = new Person('leo', 'chen');
console.log(p.fullName()); // leo chen

提升

类声明与函数声明最大的不同是,函数声明会提升,类声明不会。开发者必须先声明类才能使用,否则下面的代码将会抛出引用异常错误。

var p = new Person(); // ReferenceError
class Person {}

静态方法

static 关键字为类定义静态方法。静态方法定义在类上,无需实例化类即可使用。

class Person {
    constructor(firstName, lastName){
        this.firstName = firstName;
        this.flastName = lastName;
    }
    fullName(){
        return this.firstName + ' ' + this.lastName;
    }
    static MarksFirstLast(...marks){
        let sorted = marks.sort(function(a, b){return b-a});
        return 'First ' + sorted[0] + ' ' + 'Last ' + sorted[marks.length - 1];
    }
}
console.log(Person.MarkFirstLast(7, 4, 8, 2, 9)); // First 9 Last 2

继承

扩展类作为其他类的子类,ES6介绍了两种关键字 extends 和 super 用于从父类继承属性和方法。

class Person {
    constructor(_firstName, _lastName){
        this._firstName = _firstName;
        this._lastName = _lastName;
    }
    fullName(){
        return (this._firstName + ' ' + this._lastName).toUpperCase();
    }
}
class Student extends Person {
    constructor(_firstName, _lastName, _grade){
        super(_firstName, _lastName);
        this._grade = _grade;
    }
    studentDetail(){
        return 'FirstName: ' + this._firstName + ' LastName: ' + this._lastName + ' Grade: ' + this._grade;
    }
}

var s = new Student('leo', 'chen', '3rd');
console.log(s.fullName()); // LEO CHEN
console.log(s.studentDetail()); // FirstName: leo LastName: chen Grade: 3rd

Promises

Promises 对象通常被用来进行异步操作。一个 Promise 代表一个此刻可用的值,或者在未来,或者从不会得到该值。

一个 Promise 拥有以下这些值

  • pending: 初始化状态,既不是 fulfilled,也不是 rejected。
  • fulfilled: 意味着操作成功。
  • rejected: 意味着操作失败。
var myFirstPromise = new Promise(function(resolve, reject){
    // 当处理成功只需调用 resolve(...),处理失败则调用 reject(...)
    // 本例中,使用定时器模拟异步脚本
    // 实际开发环境中,耗时的XHR或者HTML5 API非常需要这一特性。
    setTimeout(function(){
        resolve('success!');
    }, 250);
});
myFirstPromise.then(function(successMessage){
    // successMessage 便是开发者向 resolve 中传递的数据,不仅仅可以是字符串。
 .   console.log('Yay! ' + successMessage);
});

模块

javascript 模块提供了一种机制,将公共的、私有的方法(变量)放入一个实体中,模块内部定义的函数或变量在模块外部不可见,除非开发者有意要导出它们。

模块基础

ES6 模块作为一个文件存在,没有特别的模块关键字。

ES6 模块自动将代码转换为严格模式,即使开发者不指明 “use strict”。

开发者可以在模块中使用 import 和 export 关键字。

基础用法

基本的导出导入模块,以两个工具函数为例。

  • generateRandom(): 生成随机数
  • sum(): 对两个数值求和
// utils.js
function getRandomNumber() {
    return Math,random();
}
function getSum(a, b) {
    return a + b;
}
export {getRandomNumber, getSum}

开发者可以在导出该工具函数时,为其重命名。

// utils.js
export {getRandomNumber as random, getSum as doSum}

当需要使用上面定义的工具函数时,只需这样操作。

import {getRandomNumber, getSum} from './utils';
console.log(getRandomNumber()); // 输出随机数
console.log(getSum(1, 2)); // 3

多次导出

export const appValue = Math.PI + Math.SQRT2;
function getRandomNumber() {
    return Math,random();
}
function getSum(a, b) {
    return a + b;
}
export {getRandomNumber, getSum}

将模块中导出的函数或变量悉数导入

import * as appUtils from './utils';

为导入的函数或变量起一个别名

import {getSum as appSum} from './utils';
console.log(appSum(2, 3)); // 5

同时使用默认导出和命名导出

function getRandomNumber() {
    return Math.random();
}
function getSum(a, b) {
    return a + b;
}
function getGreet(name: string) {
    return `Welcome ${name}`;
}
export default getGreet;
export {getRandomNumber, getSum}
import getGreet, {getRandomNumber, getSum} from './utils';
import getGreet, * as appUtils from './utils';
console.log(getGreet('leochen));
console.log(appUtils.getRandomNumber());
console.log(appUtils.getSum(2, 3)); // 5

导出默认函数

export default function(a, b) {
    return a + b;
}

导入默认函数

import appSum from './utils';
console.log(appSum(4, 6)); // 10