1. let/const 特性

在 ES6 标准发布之前,JS 一般都是通过关键字var声明变量,与此同时,并不存在明显的代码块声明,想要形成代码块,一般都是采用闭包的方式,比如十分常见的例子:

var arr = []
for(var i = 0; i < 5; i++) {
    arr.push(function() {
        console.log(i)
    })
}
arr.forEach(function(item)  {
    item()
})
// 输出5次数字5

而在引入let和const之后,这两个关键字会自动生成代码块,并且不存在变量提升,因此只需要把var关键字换成let就可以输出数字0到4了:

var arr = []

for(let i = 0; i < 5; i++) {
    arr.push(function() {
        console.log(i)
    })

}
arr.forEach(function(item)  {

    item()

})

// 输出 0 1 2 3 4

关键字let和const的区别在于,使用const声明的值类型变量不能被重新赋值,而引用类型变量是可以的。变量提升是因为浏览器在编译执行JS代码的时候,会先对变量和函数进行声明,var关键字声明的变量也会默认为undefined,在声明语句执行时再对该变量进行赋值当前使用块级绑定的最佳实践是:默认使用const,只在确实需要改变变量的值时使用let。这样就可以在某种程度上实现代码的不可变,从而防止某些错误的产生。

2. 箭头函数

在 ES6 中箭头函数的新增特性,它是一种使用箭头=>定义函数的新语法,和传统函数的不同主要集中在:

  • 没有this、super、arguments和new.target绑定
  • 不能通过new关键字调用
  • 没有原型
  • 不可以改变this的绑定
  • 不支持arguments对象
  • 不支持重复的命名参数

this的绑定是JS程序中一个常见的错误来源,尤其是在函数内就很容易对this的值是去控制,经常会导致意想不到的行为。在出现箭头函数之前,在声明构造函数并且修改原型的时候,经常会需要对this的值进行很多处理:

function Phone() {

    this.type = type

}

Phone.prototype.fixTips = function(tips) {

    // var that = this

    return tips.map(function(tip) {

        // return that.type + tip

        return this.type + tip

    // })

    },this)

}

要输出正确的fixTips,必须要对this的指向存在变量中或者给它找个上下文绑定,而如果使用箭头函数的话,则很容易实现:

function Phone() {
    this.type = type
}

Phone.prototype.fixTips = function(tips) {
    return tips.map(tip => this.type + tip)
}

当需要维护一个this上下文的时候,就可以使用箭头函数。

 

3. 字符串

.includes()

之前在需要判断字符串中是否包含某些字符串的时候,基本都是通过indexOf()的返回值来判断的:

var str = 'superman'

var subStr = 'super'

console.log(str.indexOf(subStr) > -1)

// true

而现在可以简单地使用includes()来进行判断,会返回一个布尔值:

const str = 'superman'

const subStr = 'super'

console.log(str.includes(subStr))

// true

当然除此之外还有两个特殊的方法,它们的用法和includes()一样:

  • startWith():如果在字符串的起始部分检测到指定文本则返回true
  • endsWith():如果在字符串的结束部分检测到指定文本则返回true

.repeat()

在此之前,需要重复字符串,我们需要自己封装一个函数:

function repeat(str, count) {

    var strs = []

    while(str.length < count) {

        strs.push(str)

    }

    return strs.join('')

}

现在则只需要调用repeat()就可以了:

'superman'.repeat(2)

// supermansuperman

 

模板字符串

它极大地简化了我们对于字符串的处理。
首先它让我们不用进行转义处理了:

var text = 'my name is \'Branson\'.'

const newText = `my name is 'Branson'.`

然后它还支持插入、换行和表达式:

const name = 'Branson'

console.log(`my name is ${name}.`)

// my name is Branson.

const text = (`

what's

wrong

?

`)

console.log(text)

// what's

// wrong

// ?

const today = new Date()

const anotherText = `The time and date is ${today.toLocaleString()}.`

console.log(anotherText)

使用模板字符串有一个好处就是,我不管你m和d是不是类型转换了,我最后都输出一个字符串,算是容错率更高了吧。

 

4. 解构

解构可以让我们用一个更简便的语法从一个数组或者对象(即使是深层的)中分离出来值,并存储他们。

// 数组解构

// ES5

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

var a = arr[0]

var b = arr[1]

var c = arr[2]

var d = arr[3]

// ES6

let [a, b, c, d] = [1, 2, 3, 4]

 

// 对象解构

// ES5

var luke = {occupation: 'jedi', father: 'anakin'}

var occupation = luke.occupation

// 'jedi'

var father = luke.father

// 'anakin'

// ES6

let luke = {occupation: 'jedi', father: 'anakin'}

let {occupation, father} = luke

console.log(occupation)

// 'jedi'

console.log(father)

// 'anakin'

 

5. 模块

在 ES6 之前,我们使用Browserify这样的库来创建客户端的模块化,在node.js中使用require。在 ES6 中,我们可以直接使用所有类型的模块化(AMD 和 CommonJS)。

CommonJS 模块的出口定义:

module.exports = 1

module.exports = { foo: 'bar' }

module.exports = ['foo', 'bar']

module.exports = function bar () {}

ES6 模块的出口定义:

// 暴露单个对象

export let type = 'ios'


// 暴露多个对象

function deDuplication(arr) {

    return [...(new Set(arr))]

}

function fix(item) {

    return `${item} ok!`

}

export {deDuplication, fix} 

// 暴露函数

export function sumThree(a, b, c) {

    return a + b + c

}
 

// 绑定默认输出

let api = {

    deDuplication,

    fix

}

export default api

// export { api as default }

 

模块出口最佳实践:总是在模块的最后面使用export default方法,这可以让暴露的东西更加清晰并且可以节省时间去找出暴露出来值的名字。尤其如此,在 CommonJS 中通常的实践就是暴露一个简单的值或者对象。坚持这种模式,可以让我们的代码更加可读,并且在 ES6 和 CommonJS 模块之间更好地兼容。

ES6 模块导入:

// 导入整个文件

import 'test'

 

// 整体加载

import * as test from 'test'

 

// 按需导入

import { deDuplication, fix } from 'test'

 

// 遇到出口为 export { foo as default, foo1, foo2 }

import foo, { foo1, foo2 } from 'foos'

 

 

6. 参数

参数这一块儿在这之前,无论是默认参数不定参数还是重命名参数都需要我们做很多处理,有了ES6之后相对来说就简洁多了:

默认参数

// ES5

function add(x, y) {

    x = x || 0

    y = y || 0

    return x + y

}

 

// ES6

function add(x=0, y=0) {

    return x + y

}

 

add(3, 6) // 9

add(3) // 3

add() // 0

 

不定参数

// ES5

function logArgs() {

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

        console.log(arguments[i])

    }

}

 

// ES6

function logArgs(...args) {

    for(let arg of args) {

        console.log(arg)

    }

}

 

 

命名参数

// ES5

function Phone(options) {

    var type = options.type || 'ios'

    var height = options.height || 667

    var width = options.width || 375

}

 

// ES6

function Phone(

    {type='ios', height=667, width=375}) {

    console.log(height)

}

 

 

展开操作

求一个数组的最大值:

// ES5

Math.max.apply(null, [-1, 100, 9001, -32])

 

// ES6

Math.max(...[-1, 100, 9001, -32])

这个特性还可以用来进行数组的合并:

const player = ['Bryant', 'Durant']

const team = ['Wade', ...player, 'Paul']

console.log(team)

// ['Wade', 'Bryant', 'Durant', 'Paul']

 

7. 类 class

关于面向对象这个词,大家都不陌生,在这之前,JS要实现面向对象编程都是基于原型链,ES6提供了很多类的语法糖,我们可以通过这些语法糖,在代码上简化很多对prototype的操作:

// ES5

// 创造一个类

function Animal(name, age) {

    this.name = name

    this.age = age

}

Animal.prototype.incrementAge = function() {

    this.age += 1

}

 

// 类继承

function Human(name, age, hobby, occupation) {

    Animal.call(this, name, age)

    this. hobby = hobby

    this.occupation = occupation

}

Human.prototype = Object.create(Animal.prototype)

Human.prototype.constructor = Human

Human.prototype.incrementAge = function() {

    Animal.prototype.incrementAge.call(this)

    console.log(this.age)

}

 

在ES6中使用语法糖简化:

// ES6

// 创建一个类

class Animal {

    constructor(name, age) {

        this.name = name

        this.age = age

    }

    incrementAge() {

        this.age += 1

    }

} 

// 类继承

class Human extends Animal {

    constructor(name, age, hobby, occupation) {

        super(name, age)

        this.hobby = hobby

        this.occupation = occupation

    }

    incrementAge() {

        super.incrementAge()

        console.log(this.age)

    }

}

 

注意:尽管类与自定义类型之间有诸多相似之处,我们仍然需要牢记它们之间的这些差异:

  • 函数声明可以被提升,而类声明与let声明类似,不能被提升;真正执行声明语句之前,它们会一直存在于临时死区中。
  • 类声明中的所有代码将自动运行在严格模式下,而且无法强行让代码脱离严格模式进行。
  • 在自定义类型中,需要通过Object.defineProperty()方法手动指定某个方法不可枚举;而在类中,所有方法都是不可枚举的。
  • 每个类都有一个constructor方法,通过关键字new调用那些不包含constructor的方法会导致程序抛出错误。
  • 使用除关键字new以外的方式调用类的构造函数会导致程序抛出错误。
  • 在类中修改类名会导致程序报错。

8. Symbols

Symbols在 ES6 之前就已经存在,现在可以直接使用一个开发的接口了。
Symbols是不可改变并且是独一无二的,可以在任意哈希中作一个key。

Symbol()

调用Symbol()或者Symbol(description)可以创造一个独一无二的符号,但是在全局是看不到的。Symbol()的一个使用情况是给一个类或者命名空间打上补丁,但是可以确定的是你不会去更新它。比如,你想给React.Component类添加一个refreshComponent方法,但是可以确定的是你不会在之后更新这个方法:

const refreshComponent = Symbol()

React.Component.prototype[refreshComponent] = () => {

    // do something

}

 

Symbol.for(key)
Symbol.for(key)同样会创造一个独一无二并且不可改变的 Symbol,但是它可以全局看到,两个相同的调用Symbol.for(key)会返回同一个Symbol类:

Symbol('foo') === Symbol('foo')

// false

Symbol.for('foo') === Symbol('foo')

// false

Symbol.for('foo') === Symbol.for('foo')

// true

对于 Symbols 的普遍用法(尤其是Symbol.for(key))是为了协同性。它可以通过在一个第三方插件中已知的接口中对象中的参数中寻找用 Symbol 成员来实现,

function reader(obj) {

    const specialRead = Symbol.for('specialRead')

    if (obj[specialRead]) {

        const reader = obj[specialRead]()

        // do something with reader

    } else {

        throw new TypeError('object cannot be read')

    }

}

在另一个库中:

const specialRead = Symbol.for('specialRead')

class SomeReadableType {

    [specialRead]() {

        const reader = createSomeReaderFrom(this)

        return reader

    }

}