ES6学习笔记

ES6学习笔记

暑假时间充足,于是就买了 Nicholas 大神的 《UNDERSTANDING ECMASSCRIPT6》在家啃(之前学习 ES5 的时候也是看这个大佬写的《JavaScript高级语言程序设计(第三版)》)。随着逐渐学的深入,不得不说这本书真的是太好了,全书语言通俗易懂,实例丰富,很多 ES6 新的概念看一遍就会了(比如强大的模板字面量语法、简洁的箭头函数语法和好用的不定参数等)。如果你正愁着不知道怎么入门 ES6 ,那我强烈向你建议购买这本!

下面是我的 ES6 学习笔记:

块级作用域绑定

最佳实践: 默认使用const,只在确实需要改变变量的值时使用let

注意:

  1. const 声明不允许修改绑定,但允许修改绑定的值
  2. TDZ(Temporal Dead Zone)体现了let和const的不提升特性

字符串和正则表达式

字符串

增强对Unicode的支持

  1. codePointAt() 检测一个字符占用的编码单元的数量

    1
    2
    3
    function is32Bit(c) {
    return c.codePointAt(0) > 0xFFFF
    }

  2. String.fromCodePoint() 根据指定的码位生成字符
  3. 在比较字符串之前一定要先使用 normalize() 方法将它们标准化为同一形式 ### 子串识别
  4. includes() 字符串中包含子串
  5. startsWith() 字符串开头包含子串
  6. endsWith() 字符串结尾包含子串 ### 字符串重复
  7. repeat() 将字符串重复几遍 注意: 这三个方法都支持指定第二个参数,用于指定开始搜索位置的起始值 ## 正则表达式 ### 增强对Unicode的支持
  8. 正则表达式u修饰符 增强对Unicode的支持 ### 增加粘滞修饰符
  9. 正则表达式y修饰符 粘滞修饰符(如果从lastIndex处开始匹配失败则返回null)

    1
    2
    let pattern = /hello\d?/y
    console.log(pattern.sticky) //true

更改正则表达式的复制行为

ES6在复制正则表达式的时候可以重新指定修饰符

1
2
let re1 = /ab/i,
re2 = new RegExp(re1, 'g') //在ES5中报错但在ES6中不会报错

新增flags属性

用于获取正则表达式的修饰符

1
2
let reg = /hello\d\s/gi
console.log(reg.source + '\n' + reg.flags)

模板字面量

模板字面量支持 - 多行字符串 一个正式的多行字符串的概念 - 基本的字符串格式化 将变量的值嵌入字符串的能力 - HTML转义 向HTML插入经过安全转换后的字符串的能力

1
console.log(`这是一个模板字面量"'单引号和双引号不需要转义\`反撇号需要转义`)

使用反撇号创建多行字符串的最佳实践

1
2
3
4
let html = `
<div>
<h1>这是一个标题</h1>
</div>`.trim()

字符串占位符

占位符中可以是任意Javascript表达式

1
2
let name = 'lolimay',
message = `Hello, ${name}`

标签模板

标签一般是一个函数,调用传入加工过的模板字面量各部分数据

1
2
3
4
5
6
7
8
9
10
11
12
13
function tag(literals, ...substitutions) {
let result = ''
for(let i=0; i<substitutions.length; i++) {
result += literals[i]
result += substitutions[i]
}
result += literals[substitutions.length] //合并最后一个literal
return result
}

let count = 5,
price = 0.25,
message = tag`${count} items cost $${count * price}.toFixed(1)}.`

在模板字面量中使用原始值

String.raw() 访问原生字符串信息

1
let rawstr = String.raw`\nitems cost\thaha.`

函数

默认参数

1
2
3
4
5
6
7
function makeRequest(url, timeout = 200, callback = function() {}) {
console.log(`${url}\n${timeout}\n${callback}`)
}

makeRequest('http://baidu.com', undefined,function() { //使用默认的timeout值
console.log('Hello Wolrd')
})

注意: 确定使用参数的默认值时,只需给参数传入undefined即可 ### 默认参数值对arguments对象的影响 默认参数值的存在使得arguments对象与命令参数分离,arguments对象的值不会随着函数参数值的更新而更新。

1
2
3
4
5
6
7
8
function mixArgs(first, second='b') {
console.log(arguments[0] === first) //true
console.log(arguments[1] === second) //false
first = 'c'
second = 'd'
console.log(arguments[0] === first) //false 存在默认参数值后
console.log(arguments[1] === second) //false arguments对象的值不会自动更新了
}

默认参数表达式

通过函数执行来得到默认参数的值

1
2
3
4
5
6
7
8
9
10
11
12
let value = 5

function getValue() {
return value++
}

function add(first, second = getValue()) {
return first + second
}

console.log(add(1))
console.log(add(2))

默认参数的临时死区(TDZ)

1
2
3
4
5
function add(first=second, second) {
return first + second
}

console.log(add(undefined, 1)) //ReferneceError

调用细节

1
2
let first = second //second未定义(这里存在临时死区现象)
let second = 1

不定参数

在函数的命名参数前添加三个点(...)表示这是一个不定参数,该参数为一个数组

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 返回一个给定对象的副本
*/
function pick(object, ...keys) { //这里用到了函数的不定参数
let result = Object.create(null)

for(let i=0; i<keys.length; i++) {
result[keys[i]] = object[keys[i]]
}

return result
}

关于不定参数的注意事项: 1. 每个函数最多只能声明一个不定参数,而且必须要放在所有参数的末尾 2. 不定参数不能用于对象字面量的setter之中(setter的参数有且只能有一个) 3. 对arguments对象的影响: 无论是否使用不定参数,arguments对象总是包括所有传入函数的参数 ## 增强的Function构造函数 ES6支持用Function构造函数创建函数时定义默认参数和不定参数

1
2
const add = new Function('first', 'second=first', '...others', 'return second+others[0]')
console.log(add(1, undefined, 99, 4)) //100

展开运算符(...)

用于将数组展开成参数的形式

1
2
3
4
5
6
/**
* 返回数组中的最大值
*/
function maxOfArray(array) {
return Math.max(...array) //将数组展开成参数的形式
}

name属性

ES6增加了name属性来确保所有函数都有合适的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const doSomething = function doSomethingElse() {
//空函数
}

const person = {
get firstName() {
return 'LoliMay'
},
sayName: function() {
console.log(this.name)
}
}

console.log(doSomething.name) //doSomethingElse
console.log(person.sayName.name) //sayName
console.log(person.firstName.name) //undefined

注意:

  1. 函数声明的名称的权重要大于匿名函数的变量的名称
  2. 三种带有前缀的情况
  1. 对于对象中的setter函数和getter函数,其name属性不是在该方法上,而是在该方法的属性描述对象的set和get属性上

    1
    2
    3
    const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo')
    console.log(descriptor.set.name) //set foo
    console.log(descriptor.get.name) //get foo

  2. 通过bind()函数创建的函数其名称带有bound前缀

  3. 通过构造函数创建的函数其名称带有anonymous前缀

  1. name属性只是协助调试用的额外信息,不能使用name属性的值来获取对于函数的引用 ## 明确函数的多种用途
    1
    2
    3
    4
    5
    6
    function Person(name) {
    this.name = name
    }

    const person = new Person('LoliMay') //[object Object]
    const notAPerson = Person('LoliMay') //undefined

ES6中的函数有两个不同的内部方法: 1. 当通过new关键字调用函数时,执行的是[[Construct]]函数,他负责实例化一个对象,然后执行函数体将this绑定到这个对象上(构造函数) 2. 如果不通过new关键字调用函数,则执行[[Call]]函数,从而执行代码中的函数体

注意: 并不是所有函数都有[[Construct]]方法,因此并不是所有函数都可以通过new来调用,例如箭头函数就不支持通过new来调用。

元属性(Metaproperty) new.target

用于判断函数是否通过new关键字调用

1
2
3
4
5
6
7
8
9
10
function Person(name) {
if(typeof new.target !== undefined) {
this.name = name
} else {
throw new Error('必须通过new关键字来调用Person')
}
}

const person = new Person('Nicholas')
const notAPerson = Person.call(person, 'LoliMay')//Error:必须通过new关键字来调用Person

注意: 在函数外使用 new.target 是一个语法错误 ## 块级函数 在ES6的非严格模式下,也可以声明块级函数,并且这些函数会提升至外围函数或全局作用域的顶部

1
2
3
4
5
6
7
8
9
if(true) {
console.log(typeof doSomething) //'function'

function doSomething() {
console.log('Hello, I am doSomething.')
}
}

doSomething() //在外部作用域仍然可以访问块级函数

箭头函数

箭头函数的6大特性:

1. 没有this、super、arguments和new.target绑定 箭头函数中的这些值由外围最近一层非箭头函数决定

2. 不能通过new关键字调用 因为箭头函数没有[[Construct]]方法

3. 没有原型 箭头函数不存在prototype这个属性

4. 不可以改变this的绑定 函数内部的this值不可改变,在函数的生命周期内始终保持一致

5. 不支持arguments对象 必须通过命名参数和不定参数这两种方式访问函数的参数

6. 不支持重复的命名参数

1
2
3
console.log((app, app) => { //Duplicate parameter name not allowed in this context
return app+app
})

箭头函数语法

没有参数

1
2
3
4
5
6
7
8
9
let getName = () => 'Nicholas'

console.log(getName) //Function: getName

//实际上相当于

let getName = function() {
return 'Nicholas'
}

单一参数

1
2
3
4
5
6
7
8
9
const reflect = value => value

console.log(reflect) //[Function: reflect]

//实际上相当于

const reflect = function(value) {
return value
}

两个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
const sum = (num1, num) => num + num2

//也可以写的复杂点(写成更加传统的函数体并显示地指定一个返回值)

const sum = (num1, num2) => {
return num1 + num2
}

//实际上相当于

const sum = function(num1, num2) {
return num1 + num2
}

使用箭头函数语法创建一个空的函数

1
2
3
4
5
const doNothing = () => {} //创建一个空的函数

//实际上等价于

const doNothing = function() {}

使用箭头函数返回一个对象字面量

1
2
3
4
5
6
7
8
9
10
11
12
13
const getTemp = (id) => ({
id: id,
name: 'Lolimay'
})

//实际上等价于

const getTemp = function(id) {
return {
id: id,
name: 'LoliMay'
}
}

注意: 将对象包含在一个小括号中,是为了与函数体区分开 ### 创建立即执行函数表达式(IIFE) IIFE可以创建一个与其它程序隔离的作用域,这种模式在JavaScript的开发中非常常见,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const person = function(name) {
return {
getName: function() {
return name
}
}
}('LoliMay') //IIFE

//箭头函数写法

const person = (name => ({
getName() {
return name
}
}))('LoliMay')

console.log(person.getName())

箭头函数没有this绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 用箭头函数解决this绑定的问题
* 由于箭头函数中没有this绑定,其this绑定通过查找作用域链找到
* 一般为最近一层非箭头函数的this,直到全局作用域对象
*/
const PageHandler = {
id: '123456',
init() {
//使用箭头函数解决this绑定非预期又简洁又好理解
document.addEventListener('click', event => this.doSomething(event.type), false)
},
doSomething(type) {
console.log(`Handing ${type} for ${this.id}`)
}
}

PageHandler.init() //初始化页面事件处理程序

注意: 由于箭头函数中没有this绑定,故不能通过call()、apply()或bind()方法来改变this值 ### 箭头函数和数组 箭头函数的语法简洁,非常适用于数组处理

1
const result = values.sort((a, b) => a - b) //以递增序列排序

诸如sort()、map()和reduce()这些可以接收回调函数的数组方法都可以通过箭头函数简化代码量 ### 箭头函数没有arguments绑定 和箭头函数没有this绑定一样,虽然箭头函数没有arguments绑定,但是可以访问外部函数的arguments对象 如

1
2
3
4
const outerFunc = function() {
return (() => arguments[0])() //这里箭头函数引用的是outerFunc的arguments对象
}
console.log(outerFunc(5))

箭头函数的辨识方法

使用typeof和instanceof操作符调用箭头函数与调用其他函数并无二致

1
2
3
const comparator = (a, b) => a-b
console.log(typeof comparator) //'function'
console.log(comparator instanceof Function) //true

尾调用优化

尾调用指的是函数作为另一个函数的最后一条语句被调用

1
2
3
function doSomething() {
return doSomethingElse()
}

ES6中的尾调用优化的4个条件: 1. 使用严格模式 2. 尾调用不访问当前栈帧的变量(函数不是一个闭包) 3. 在函数内部,尾调用是最后一条语句 4. 尾调用的结果作为函数值返回

优化细节: 尾调用不再创建新的栈帧,而是清除并重用当前栈帧,以提高性能。

例:

1
2
3
4
5
'use strict'

function doSomething() {
return doSomethingElse() //符合尾调用优化的4个条件
}

如何利用尾调用优化

递归函数是最主要的尾调用优化场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function factorial(n) {
if(n<=1) {
return 1
} else {
return n * factorial(n-1) //无法优化必须在返回后执行乘法操作
}
}

//优化后

function factorial(n, p=1) { //p用来保存最终的递归结果
if(n<=1) {
return 1 * p
} else {
let result = n * p
return factorial(n-1, result)
}
}

扩展对象的功能性

对象类别

ES6规范清晰地定义了每一个类别的对象 - 普通(Ordinary)对象 具有JavaScript对象所有的默认内部行为 - 特异(Exotic)对象 具有某些与默认行为不符的内部行为 - 标准(Standard)对象 ES6规范中定义的对象,如Array、Date等 - 内建对象 脚本开始执行时,存在于JavaScript执行环境中的对象,所有标准对象都是内建对象 ## 对象字面量语法扩展 ### 属性初始值的简写 当一个对象的属性与本地变量同名时,可以只写属性

1
2
3
4
5
6
function createPerson(name, age) {
return {
name,
age
}
}

对象方法的简写语法

1
2
3
4
5
6
const person = {
name: 'Nicholas',
sayName() { //对象方法的简写语法
console.log(this.name)
}
}

注意: 采用对象方法的简写语法可以使用super关键字 ### 可计算属性名(Computed Property Name) 表达式或者变量作为属性的可计算名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let lastName = 'last name'
const person = {
'first name': 'Nicholas;,
[lastName]: 'Zakas' //在对象字面量中使用方括号表示的该属性名称是可计算的
}
console.log(person['first name'])
console.log(person[lastName])

//因而同样可以使用表达式作为属性的可计算名称
let suffix = 'name'
const person = {
[`first ${suffix}`]: 'Nicholas',
[`last ${suffix}`]: 'Zakas'
}

新增方法

Object.is()

大部分情况与===运算符的作用相同,唯一的区别在于+0和-0被识别成不相等并且NaN与NaN等价

1
2
3
4
console.log(+0 === -0) //true
console.log(Object.is(+0, -0)) //false
console.log(NaN === NaN) //false
console.log(Object.is(NaN, NaN)) //true

Object.assign()

混合(Mixin)是JavaScript中实现对象组合最流行的一种模式,任何使用mixin()方法的地方都可以直接使用Object.assign()方法来替换

1
2
3
4
5
6
7
8
9
10
11
12
13
const receiver = {}

Object.assign(receiver,
{
type: 'js',
name: 'file.js'
},
{
type: 'css'
}
)

console.log(receiver.type) //'css'

注意 1. 如果多个源对象具有同名属性,则排位靠后的会覆盖排位靠前的 2. Object.assign()方法不能将提供者的访问器属性复制到接收者对象中

提供者的访问器属性最终会变为接收对象的一个数据属性

1
2
3
4
5
6
7
8
9
10
11
const receiver = {},
supplier = {
get name() {
return 'file.js'
}
}
Object.assign(receiver, supplier)

let descriptor = Object.getOwnPropertyDescriptor(receiver, 'name')

console.log(descriptor.get) //undefined

重复的对象字面量属性

ES6不再检查重复属性,对于每一组重复属性都会选取最后一个取值

1
2
3
4
5
6
'use strict'

const person = {
name: 'Nicholas',
name: 'LoliMay'
}

自有属性枚举顺序

ES6严格规定了对象的自有属性被枚举时的返回顺序,这会影响到Object.getOwnPropertyNames()Reflect.ownKeys返回属性的方式 ### 基本规则 1. 所有数字键按升序排序 2. 所有字符串键和symbol键按它们被加入对象的顺序排序

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {
a: 1,
0: 1,
c: 1,
2: 1,
b: 1,
1: 1
}

obj.d = 1 //添加一个属性

console.log(Object.keys(obj).join('')) //012acbd

注意: 对于for-in循环、Object.keys()和JSON.stringify()方法来说目前仍未指定一个明确的枚举顺序(但是个人在nodejs 8.10.0 环境下实测枚举顺序是符合ES6规范的) ## 增强的对象原型 原型是JavaScript继承的基础,ES6增强了开发者对JavaScript原型的控制力 ### 改变对象的原型 ES6添加了Object.setPrototypeOf() 方法来改变任意对象的原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const person = {
getGreeting() {
return 'Hello'
}
}

const dog = {
getGreeting() {
return 'Woof'
}
}

//以person对象为原型
const friend = Object.create(person)
console.log(friend.getGreeting()) //'Hello'
console.log(Object.getPrototypeOf(friend) === person) //true

//将原型设置为dog
Object.setPrototypeOf(friend, dog) //ES6添加的setPrototypeOf()方法用于改变对象的原型
console.log(friend.getGreeting()) //'Woof'
console.log(Object.getPrototypeOf(friend) === dog) //true

简化原型访问的super引用