本文已参与「新人创作礼」活动,一起开启掘金创作之路。
大家好,为方便学习与交流使用,本人在这里对ES2015到ES2018新特性进行了简单的总结。
个人在开发中常用的ES6(es6泛指es2015及后续版本)新特性主要是这些,所以就先总结到这里了。
以下内容只是个人的简单总结,有些可能描述不太准确,大家具体可查询MDN。
一、ES2015
let和const
- leta或const声明的成员只会在所声明的作用域内生效。
- 与var不同,不具有变量提升特性。
- const定义是常量不可修改,恒量要在声明同时赋值。
{
// 必须先声明
console.log(str) // undefined
let str = 'hello'
str = 'hi'
console.log(str) // hi
// 基础数据类型不可修改
const PI = 3.14
}
// 作用域外无法访问
console.log(str, PI) // undefined undefined
数组解构
- 数组解构赋值可以更方便地将数组成员提取出来进行使用。
- 可提取所有、部份或某一个成员以及剩余成员。
// 将数组所有成员分别赋值到a b c变量中
const arr = [100, 200, 300]
const [a, b, c] = arr
console.log(a, b, c) // 100 200 300
// 将数组第三个成员赋值到c变量中
const [, , c] = arr
console.log(c) // 300
// 解构时获取数组剩余成员
const [a, ...rest] = arr
console.log(a, rest) // 100 [200, 300]
// 超出数组长度的成员为undefined
const [a, b, c, more] = arr
console.log(more) // undefined
// 超出数组长度的成员可以进行赋值,就相当于顺便在这里定义一个变量并赋值
const [a, b, c, default='default value'] = arr
console.log(c, default) // 300 default value
// 应用:获取根目录
const path = '/src/main.js'
const [, rootDir] = path.split('/') // ['', 'src', 'main.js']
console.log(rootDir) // src
对象解构
- 与数组解构基本类似。
- 不同在于对象是无序的,解构时是跟据键值进行解构的,键值与想要赋值的变量名不一致时可重命名。
// 将对象中所有成员分别赋值到name age变量中
const obj = {
name: 'ma',
age: 27
}
const { name, age } = obj
console.log(name, age) // ma 27
// 对象中不存在的键为undefined
const { other } = obj
console.log(other) // undefined
// 重命名变量赋值
const { name: objName } = obj
console.log(objName) // ma
// 对重命名变量账值无效
const { name: objName='jack' } = obj // 解构对象时对重命名变量进行赋值无效
console.log(objName) // ma
// 应用:在对象中解构常用方法
const { log } = console
log('txt')
模板字符串
- 模板字符串支持换行,可以嵌入表达式。
- 可以使模板字符串标签(一个特殊的函数)。
// 支持换行
const str = `Hello ES2015
this is \`string\``
console.log(str)
/*
Hello ES2015
this is `string`
*/
// 可通过${}插入表达式
const name = 'ma'
const str = `Hey, ${name}. ${1+2}, ${Math.random}`
console.log(str) // Hey, ma. 3, 0.0261460237503921
// 模板字符串标签
const name = 'ma'
const gender = true
// 模板字符串标签是一个特殊的函数
function myTagFunc (string, name, gender) {
// string是以表达式为依据进行拆分的字符串数组
console.log(string, name, gender) // ['I am ', ' is a ', '.'] ma true
const sex = gender ? 'male' : 'female'
return string[0] + name + stirng[1] + sex + string[2]
}
const res = myTagFunc`I am ${name} is a ${gender}.`
console.log(res) // I am ma is a male
字符串扩展方法
- startsWidth是否以某字符串开头。
- endsWith是否以某字符串结尾。
- includes是否包含某字符串。
const message = 'Error: foo is not defined.'
console.log(message.startsWith('Error')) // true
console.log(message.endsWith('.')) // true
console.log(message.includes('foo')) // true
函数参数默认值
- 函数参数可带默认值。
- 一般带默认参数放最后。
function foo (enable = true) {
console.log('foo invoked - enable:')
console.log(enable)
}
foo() // foo invoked - enable: true
剩余参数
- ...变量名 方式获取函数剩余参数。
- 剩余参数只能放到最后。
function foo (first, ...args) {
console.log(first, args)
}
foo(1, 2, 3, 4, 5) // 1 [2, 3, 4, 5]
展开数组参数
- ...变量名 方式展开数组所有成员
const arr = ['foo', 'bar', 'baz']
// console.log.apply(console, arr)
console.log(...arr) // foo bar baz
箭头函数
- 单个参数括号()可以省略。
- 单行语句大括号{}和return可以省略。
const inc = n => n + 1
console.log(inc(1)) // 2
// 函数传入多个参数需要括号,多行语句时需要大括号
const incre = (n, m) => {
console.log('incre invoked')
return n + m
}
console.log(incre(1, 2)) // 3
箭头函数与this
- 箭头函数不会改变this指向,从作用域上层继承this。
const person = {
name: 'ma',
// 在浏览器环境下顶层this是window对象,node环境下是空对象{}
sayHi: () => {
console.log(`my name is ${this.name}`)
}
// function定义的函数,谁调用this就指向谁
sayHi2: function() {
console.log(`my name is ${this.name}`)
}
sayHiAsync: function() {
setTimeout(() => {
console.log(`I am ${this.name}`)
}, 1000)
}
}
person.sayHi() // my name is undefined
// 箭头函数在执行中没有this,而是直接拿变量,所以bind、call、apply是无效的!
// person.sayHi.call(person) // my name is undefined
person.sayHi2() // my name is ma
person.sayHiAsync() // I am ma
对象字面量
- 属性与变量名相同,可以省略。
- 定义方法可以省略 :function。
- 通过[]可以让表达式结果作为属性名。
const key = '345'
const obj = {
foo: '123',
key, // 属性与变量名相同,可以省略
func () { // 方法可以省略 :function
console.log('func')
console.log(this) // 这种方法就是普通的函数,同样影响this指向
},
[key]: '666', // 通过[]可以让表达式结果作为属性名
}
obj[Math.random] = '999'
console.log(obj)
/*
{
foo: '123',
key: '345',
func: [Function func],
'345': '666',
'0.6701959833170565': '999'
}
*/
Object.assign方法
- 合并对象到第一个对象上,前一个对象属性会被后一个对象属性覆盖。
const source1 = {
a: '1',
b: '2'
}
const source2 = {
b: '22',
d: '44'
}
const target = {
a: '111',
c: '333'
}
// 合并对象到第一个对象上,前一个对象属性会被后一个对象属性覆盖
const res = Object.assign(target, source1, source2)
console.log(target) // { a: '1', b: '22', c: '333', d: '44' }
console.log(res === target) // true
// 应用:拷贝对象
function func (obj) {
const funcObj = Object.assign({}, obj) // 将ojb对象属性覆盖到一个空对象上
funcObj.name = 'Jack' // 修改属性不会影响原对象
console.log(funcObj)
}
const obj = { name: 'Rose' }
func(obj) // { name: 'Jack' }
console.log(obj) // { name: 'Rose' }
Object.is方法
- 可对正负零进行判断。
- 判断两个NaN是相等的。
console.log(0 == false) // true
console.log(0 === false) // false
console.log(+0 === -0) // true
console.log(NaN === NaN) // false
console.log(Object.is(+0, -0)) // false
console.log(Object.is(NaN, NaN)) // true
Proxy对象
- 可监视get和set以外的操作。
- 可方便监视数组操作。
- 不需要入侵对象。
// 基本用法
const person = {
name: 'ma',
age: 27
}
const personProxy = new Proxy(person, {
// 监视属性读取
get (target, property) {
console.log(target, property) // { name: 'ma', age: 27 } age
return property in target ? target[property] : 'default'
},
// 监视属性设置
set (target, property, value) {
if (property === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError(`${value} is not a int`)
}
}
target[property] = value
}
})
console.log(personProxy.age)
/*
{ name: 'ma', age: 27 } age
27
*/
personProxy.age = 18
personProxy.gender = true
console.log(personProxy) // { name: 'ma', age: 18, gender: true }
Proxy与Object.defineProperty的区别。
// 01 Proxy可以监听读写以外的操作
const person = {
name: 'ma',
age: 27
}
const personProxy = new Proxy(person, {
deleteProperty (target, property) {
// console.log('delete', property)
delete target[property]
}
})
delete personProxy.age
console.log(personProxy) // { name: 'ma' }
// 02 可以方便监视数组操作
const list = []
const listProxy = new Proxy(list, {
set (target, property, value) {
console.log('set', property, value)
target[property] = value
return true // 表示设置成功
}
})
listProxy.push(100)
/*
set 0 100
set length 1
*/
listProxy.push(200)
/*
set 1 200
set length 2
*/
console.log(list) // [100, 200]
// 03 Proxy不需要入侵对象
// 1、Object.defineProperty方式
const person = {}
Object.defineProperty(person, 'name', {
get () {
console.log('name', 被访问)
return person._name
},
set (value) {
console.log('name', 被设置)
person._name = value // 如果这里是name不是_name则会死循环
}
})
Object.defindProperty(person, 'age', {
get () {
console.log('age', 被访问)
return person._age
},
set (value) {
console.log('age', 被设置)
person._age = value
}
})
person.name = 'ma'
person.age = 27
console.log(person) // { _name: 'ma', _age: 27 }
// 2、Proxy方式
const person = {
name: 'ma',
age: 27
}
const personProxy = new Proxy(person, {
get (target, property) {
console.log('get', property)
return target[property]
},
set (target, property, value) {
console.log('set', property)
target[property] = value
}
})
personProxy.name = 'Jack'
console.log(personProxy) // { name: 'Jack', age: 27 }
Reflect对象
- Reflect对象提供用来拦截js操作的方法,它不是构造函数不可以new,只有静态方法。主要用来统一一下对象的操作。
const person = {
name: 'ma',
age: 27
}
const personProxy = new Proxy(person, {
get (target, property) {
console.log('watch logic')
return Reflect.get(target, property)
}
})
console.log(personProxy.name) // ma
const obj = {
name: 'Jack',
age: 18
}
// before
console.log('name' in obj) // true
console.log(delete obj['age']) // true
console.log(Object.keys(obj)) // [ 'name' ]
// Reflect方式
console.log(Reflect.has(obj, 'name')) // true
console.log(Reflect.deleteProperty(obj, 'age')) // true
console.log(Reflect.ownKeys(obj)) // [ 'name' ]
class关键字
- 类是用于创建对象的模板。
- 他们用代码封装数据以处理该数据。
- JS中的类建立在原型上,但也具有某些语法和语义未与 ES5 类相似语义共享。
// 基本用法
class Person {
constructor (name) {
this.name = name
}
say () {
console.log(`hello, my name is ${this.name}`)
}
}
const p = new Person('Jack')
p.say() // hello, my name is Jack
// static 静态方法关键字
class Person2 {
constructor (name) {
this.name = name
}
say () {
console.log(`I am ${this.name}`)
}
static create (name) {
return new Person2(name)
}
}
const p2 = Person2.create('Rose')
p2.say() // I am Rose
// extends 继承
class People {
constructor (name) {
this.name = name
}
say () {
console.log(`hi, my name is ${this.name}`)
}
}
class Student extends People {
constructor (name, number) {
super(name) // 父类构造函数
this.number = number
}
hello () {
super.say() // 调用父类成员
console.log(`my school number is ${this.number}`)
}
}
const stu = new Student('Tom', 007)
stu.hello()
/*
hi, my name is Tommy
school number is 007
*/
set数据结构
- 可以存储任意类型数据,但元素是唯一的。
const s = new Set()
s.add(1).add(2),add(3).add(4).add(2)
console.log(s) // Set(4) { 1, 2, 3, 4 }
s.forEach(i => console.log(i))
for (let i of s) {
console.log(i)
}
/*
1
2
3
4
*/
console.log(s.size) // 4
console.log(s.has(100)) // false
s.delete(3)
console.log(s) // Set(3) { 1, 2, 4 }
s.clear()
console.log(s) // Set(0) {}
// 应用场景:数组去重
const arr = [1, 2, 1, 3, 4, 1]
// const res = Array.from(new Set(arr))
const res = [...new Set(arr)]
console.log(res) // [1, 2, 3, 4]
/*
弱引用版本 WeakSet,不同的是 Set中会对所使用到的数据产生引用,即便这个数据在外面被消耗,
但是由于Set引用了这个数据,所以依然不会回收,而 WeakSet 的特点就是不会产生引用,一旦数据销毁,
就可以被回收,所以不会产生内存泄漏问题。
*/
map数据结构
- 能够记住键的原始插入顺序。
- 键值可以是引用数据类型。
const tom = {
name: 'tom'
}
const m = new Map()
m.set(tom, 90)
console.log(m) // { { name: 'tom' } => 90 }
console.log(m.get(tome)) // 90
// m.has()
// m.delete()
// m.clear()
m.forEach((val, key) => {
console.log(val, key)
})
90 { name: 'tom' }
Symbol数据类型
- 是一个基础数据类型。
- Symbol返回的值是唯一的。
- 一个symbol值能作为对象属性的标识符。这是该数据类型仅有的目的。
const s = Symbol()
console.log(s) // Symbol()
console.log(typeof s) // Symbol
// 两个Symbol永远不会相等
console.log(Symbol() === Symbol()) // false
// 添加描述文本
console.log(Symbol('foo')) // Symbol('foo')
console.log(Symbol('bar')) // Symbol('bar')
// 使用Symbol为对象添加不重复的键
const obj = {}
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj) // { [Symbol()]: '123', [Symbol()]: '456' }
// 在计算属性名中使用
const obj2 = {
[Symbol()]: 123
}
console.log(obj2) // { [Symbol()]: 123 }
// 模拟私有成员
/* a.js */
const name = Symbol()
const person = {
[name]: 'ma',
say () {
console.log(`it is ${this[name]}`)
}
}
// 只对外暴露person
/* b.js */
console.log(person[Symbol]) // undefined
person.say() // it is ma
// 描述一样不等于相同
consle.log(Symbol('foo') === Symbol('foo')) // false
// Symbol全局注册
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
// 全局注册可以使两个Symbol相等
console.log(s1 === s2) // true
console.log(
Symbol.for(true) === Symbol.for('true') // Symbol.for会自动将其他类型的值转化为字符串
) // true
// 内置Symbol常量
console.log(Symbol.iterator) // Symbol(Symbol.iterator)
console.log(Symbol.hasInstance) // Symbol(Symbol.hasInstance)
const obj = {
[Symbol.toStringTag]: 'Module'
}
console.log(obj.toString()) // [Object Module]
// Symbol属性名获取
const obj = {
[Symbol()]: 'symbol value',
foo: 'normal value'
}
console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol() ]
console.log(obj[Object.getOwnPropertySymbols(obj)[0]]) // symbol value
for...of循环
- 在可迭代对象上创建一个迭代循环。
const arr = [100, 200, 300]
for (const item of arr) {
consle.log(item)
}
/*
100
200
300
*/
// for of可替代forEach方法,因为forEach无法跳出循环,必须使用some、every方法
for (const item of arr) {
console.log(item)
if (item > 100) break
}
/*
100
*/
// for of遍历Set和遍历数组相同
const s = new Set(['foo', 'bar'])
for (const item of s) {
console.log(item)
}
/*
foo
bar
*/
// for of遍历Map可以使用数组解构语法,直接取键值
const m = new Map()
m.set('foo', 123)
m.set('bar', 456)
for (const [key, val] of m) {
console.log(key, val)
}
/*
foo 123
bar 456
*/
// 普通对象不能直接被for of遍历,因为普通对象__proto__上没有Symbol(Symbol.iterator)创建迭代器方法
const obj = {
foo: 123,
bar: 456
}
for (const item of obj) {
console.log(item)
}
// TypeError: obj is not iterable
迭代器
- 处理集合中的每个项是很常见的操作。
- 这是一种for of循环机制。
const set = new Set(['foo', 'bar', 'baz'])
// Symbol.iterator是Symbol的内置常量,用于存放创建迭代器方法,存放于此属性名就可以被for of识别
const iterator = set[Symbol.iterator]() // set对象原型上创建迭代器方法
console.log(iterator.next()) // { vaule: 'foo', done: false }
console.log(iterator.next()) // { vaule: 'bar', done: false }
console.log(iterator.next()) // { vaule: 'baz', done: false }
console.log(iterator.next()) // { vaule: undefined, done: true }
while (true) {
const current = iterator.next()
if (current.done) break
console.log(current.value)
}
/*
foo
bar
baz
*/
// 实现可迭代接口(Iterable)
const obj = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: funciton () {
const self = this
let index = 0
return {
next: function () {
const res = {
value: self.store[index],
done: index >= self.store.length
}
index++
return res
}
}
}
}
for (const item of obj) {
console.log(item)
}
/*
foo
bar
baz
*/
// 迭代器设计模式
const todos = {
life: ['eat', 'drink', 'sleep'],
study: ['chinese', 'math', 'english'],
work: ['coding'],
// 提供统一遍历访问接口
each: function (cb) {
const all = [].concat(life, study, work)
for (const item of all) {
cb(item)
}
},
// 提供迭代器(ES2016统一遍历访问接口)
[Symbol.iterator]: function () {
const all = [...life, ...study, ...work]
let index = 0
return {
next: function () {
const res = {
value: all[index],
done: index >= all.length
}
index++
return res
}
}
}
}
todos.each(item => {
console.log(item)
})
for (const item of todos) {
console.log(item)
}
/*
eat
drink
sleep
chinese
math
english
coding
*/
生成器
- 它不像自定议的迭代器显式地维护其内部状态,它定义一个包含自有迭代算法的函数,同时也可以自动维护自己的状态。
function * foo () {
console.log('one')
yield 1
console.log('two')
yield 2
}
const generator = foo()
console.log(generator.next())
/*
one
{ value: 'one', done: false }
*/
console.log(generator.next())
/*
two
{ value: 'two', done: false }
*/
console.log(generator.next()) // { value: undefined, done: true }
// 应用:发号器
function * createIdMaker () {
let id = 1
while (true) {
yield id++
}
}
const idMaker = createIdMaker()
console.log(idMaker.next().value) // 1
console.log(idMaker.next().value) // 2
// 应用:使用Generator实现Iterator方法
/*
可以这样理解:
Iterator方法返回一个对象,该对象有next方法,next方法返回{value: 'val', done: false}。
生成器也是返回一个对象并有next方法也一样返回{value: 'val', done: false},其状态还是自动维护。
*/
const todos = {
life: ['eat', 'drink', 'sleep'],
study: ['chinese', 'math', 'english'],
work: ['coding'],
[Symbol.iterator]: function * () {
const all = [...life, ...study, ...work]
for (const item of all) {
yield item
}
},
/*
[Symbol.iterator]: function () {
const all = [...life, ...study, ...work]
let index = 0
return {
next: function () {
const res = { value: all[index], done: index >= all.length }
index++
return res
}
}
}
*/
}
for (const item of todos) {
console.log(item)
}
/*
eat
drink
sleep
chinese
math
english
coding
*/
二、ES2016
Array.prototype.includes
- 直接返回是否存在指定元素。
const arr = ['foo', NaN, false]
console.log(arr.indexOf('foo')) // 0 (找不到返回-1)
console.log(arr.indexOf(NaN)) // -1 (无法找到数组中的NaN)
// 直接返回是否存在指定元素
console.log(arr.includes('foo')) // true
console.log(arr.includes(NaN)) // true (可以查找NaN)
指数运算符
// before
console.log(Math.pow(2, 10)) // 1024
// now
console.log(2 ** 10) // 1024
三、ES2017
Object.values
- 获取对象所有的值。
const obj = {
foo: 'value01',
bar: 'value02'
}
console.log(Object.values(obj)) // ['value01', 'value02']
Object.entries
- 获取对象所有的键、值。
const obj = {
foo: 'val1',
bar: 'val2'
}
console.log(Object.entries(obj)) // [['foo', 'val1'], ['bar', 'val2']]
for (const [key, val] of Object.entries(obj)) {
console.log(key, val)
}
/*
foo val1
bar val2
*/
Object.getOwnPropertyDescriptors
- 获取一个对象的所有自身属性的描述符。
const p1 = {
firstName: 'ma',
lastName: 'qf',
get fullName () {
return `${this.firstName} ${this.lastName}`
}
}
const descriptors = Object.getOwnPropertyDescriptors(p1)
console.log(descriptors)
/*
{
firstName: {
value: 'ma',
writable: true,
enumerable: true,
configurable: true
},
lastName: {
value: 'qf',
writable: true,
enumerable: true,
configurable: true
},
fullName: {
get: [Function: get fullName],
set: undefined,
enumerable: true,
configurable: true
}
}
*/
const p2 = Object.defineProperties({}, descriptors)
console.log(p2.firstName) // ma
console.log(p2.fullName) // ma qf
Object.prototype.padStart / Object.prototype.padEnd
- 向左或向右填充字符串。
const books = {
html: 3,
css: 5,
js: 128
}
for (const [key, val] of Object.entries(books)) {
console.log(`${key.padEnd(16, '-')} ${val.padStart(3, '0')}`)
}
/*
html------------|003
css-------------|005
js--------------|128
*/
在函数参数中带尾逗号
- 就是函数最后一个参数可以带逗号。
function foo (
foo,
bar, // <--
){
console.log(foo, bar)
}
四、ES2018
Rest(剩余)/Spread(展开) 属性
- ES6 在处理数组解构时,引入了 rest(剩余)元素的概念,ES2018 为对象引入了类似的功能。
1. const { one, two, ...others } = { one: 1, two: 2, three: 3, four: 4, five: 5 }
1. console.log(one) // 1
1. console.log(two) // 2
1. console.log(others) // { three: 3, four: 4, five: 5 }
// spread(展开) 属性可以通过组合展开运算符...之后传递的对象属性来创建新的对象。
1. const items = { one, two, ...others }
1. console.log(items) // { one: 1, two: 2, three: 3, four: 4, five: 5 }
异步迭代async/await
- ES2018引入异步迭代器(asynchronous iterators),这就像常规迭代器,除了
next()方法还返回一个Promise。
async function process () {
await new Promise(resolve => {
console.log('doing')
setTimeout(() => {
resolve()
}, 1000)
})
console.log('done')
}
// 新的for await of构造允许使用异步可迭代对象作为循环迭代
async function process (filePath) {
for await (const line of readLines(filePath)) {
console.log(line)
}
}
Promise.prototype.finally()
- 无论promise的执行成功或失败都会执行。
fetch('data.json')
.then(data => data.json())
.catch(error => console.error(error))
.finally(() => console.log('finished'))
-EOF-