一、ECMASCript简介
1、Javascript语言本身指的就是ECMASCript,只不过运行在不同的平台Javascript进行了扩展。
2、Javascript在Web下使用包括了:ECMASCript + Web APIs (BOM、DOM)。
3、Javascript在Node.js下使用包括了:ECMASCript + Node APIs (fs、net、etc.)。
4、2016年开始ES保持每年一个版本迭代,行业内我们所说的ES6可以泛指ECMASCript的所有标准。
5、ES6对原有基础的做了很大改进,主要体现在四个方面:
- 对原有语法的增强,如:
Object.values()、Object.assign()... - 解决原有语法上的一些问题或者缺陷,如:
let、const、块作用域... - 全新的对象、全新的方法、全新的功能,如:
Proxy、Reflect、class关键词、extends类的继承... - 全新的数据类型和数据结构,如:
Symbol、Set、Map...
二、数组解构、对象解构
1、以往获取数组元素的方式是通过数组下标,ES6获取数组元素可以按照位置相同进行赋值,也可以添加默认值。另外通过剩余运算符可以获取集合。
const arr = [100, 200, 300]
const [foo, bar, baz, car = 400] = arr
const [bas, ...rest] = arr
log(foo, bar, baz, car) // 100 200 300 400
log(bas, rest) // 100 [ 200, 300 ]
2、以往获取对象属性的方式是通过.运算符,ES6可以通过变量名与属性名相同获取属性值,也可以重命名属性名,给属性值添加默认值。
const obj = { name: 'Tom', age: 20 }
const name = 'Jack'
const { name: objName = 'default' } = obj
log(objName) // Tom
三、模板字符串及扩展方法
1、模板字符串普通用法支持支持变量,反引号转义、换行、插值运算
const name = 'peek'
log(`hello es2015
my name is ${name + 1} \`\string\``)
2、带标签的模板字符串可以对字符串进行加工
const name = 'peek'
const gender = true
function myTagFunc (string, name, gender) {
return string[0] + name + string[1] + gender + string[2]
}
const result = myTagFunc`hey, ${name} is a ${gender}`
console.log(result) // hey, peek is a true
3、ES6提供的字符串的扩展方法
const message = 'Error: foo is not defined'
log(message.includes('foo')) // true
log(message.startsWith('Error')) // true
log(message.endsWith('defined')) // true
四、...运算符
1、函数参数传值采用剩余运算符,最好放在形参的最后
const hello = (sum, ...rest) => {
log(sum) // 100
log(rest) // [ 1, 2, 3 ]
}
hello(100, 1, 2, 3)
2、数组中使用扩展运算符
const arr = ['1', '2', '3']
console.log(...arr) // 1 2 3
五、Object对象字面量增强
const x = '11'
const family = {
x,
y () { // 推荐写法,简洁
console.log(this) // this指向family
},
z: () => { // 推荐写法,方便理解
console.log(this.y) // this指向window
console.log(family.y) // 调用family对象的属性
return null
},
// 计算属性名作为属性名
[Math.random()]: '33'
}
console.log(family)
六、Object对象新方法
log(Object.assign({ a: 1, b: 2 }, { a: 3, c: 4 })) // 对象合并
log(Object.assign({}, { a: 3, c: 4 })) // 浅拷贝
log(-0 === +0) // true
log(null === null) // true
log(Object.is(-0, +0)) // false 判断对象是否相等
log(Object.is(null, null)) // true
七、Proxy对象(俗称:门卫)
1、Proxy对象采用的是非侵入的方式劫持整个对象, 对整个对象进行监管
const person = {
name: 'Tom',
age: 24
}
const personProxy = new Proxy(person, {
get (target, prop) {
// 1、对对象属性的取值操作,添加默认值,最后返回属性值
return target[prop] ? target[prop] : 'default'
},
set (target, prop, value) { // 小知识:vscode提示的代码不是我们所需要的,按下Esc键即可
// 2、对对象属性的赋值操作,添加校验以及异常处理
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new Error('this value must be a integer')
}
}
// 3、给对象属性赋值
target[prop] = value
}
})
personProxy.age = '20'
log(personProxy.name)
log(personProxy.xxx)
2、Proxy 与 Object.defineProperty对比
Object.defineProperty仅支持对对象的读写进行劫持,并且是侵入式写法,需要改造对象。 Proxy是对整个对象进行监管, Proxy采用非侵入的方式劫持整个对象。
const person = {
name: 'Tom',
age: 24
}
const personProxy = new Proxy(person, {
set (target, prop, value) {
target[prop] = value
},
deleteProperty (target, prop) {
console.log('delete', prop)
delete target[prop]
}
})
delete personProxy.age
personProxy.gender = 'man'
log(person) // { name: 'Tom', gender: 'man' }
Proxy对数组操作更友好的监视,以往都是采用重写数组的方法实现数组监听(vue2+)
const list = []
const listProxy = new Proxy(list, {
set (target, prop, value) {
target[prop] = value
return true // 标记数组元素添加成功
}
})
listProxy.push('11')
log(list) // [ '11' ]
3、除了上面常用的属性操作,还有其他的操作,如下:
八、Reflect静态类
诞生的意义: Reflect静态类提供了统一的操作对象的方法。以前我们操作对象的方法很杂,比如 delete、in、Object.keys(), 未来要废弃的。
const person = {
name: 'Tom',
age: 24
}
log('name' in person)
// 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
log(Reflect.has(person, 'name'))
log(delete person.name)
// 作为函数的delete操作符,相当于执行 delete target[name]
log(Reflect.deleteProperty(person, 'name'))
log(Object.keys(person))
// 返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable影响).
log(Reflect.ownKeys(person))
可以改造之前的Proxy获取对象属性的方法
const personProxy = new Proxy(person, {
get (target, prop, value) {
log('watch ...')
return Reflect.get(target, prop)
}
})
console.log(personProxy.age)
除了上面常用的属性操作,还有其他的操作,如下:
九、Promise对象
略,参考手写promise
十、class关键词
诞生的意义:让Javascript类的定义, 简洁易懂。
1、以往我们定义一个类是采用构造函数的方式,如下:
function Person (value) {
this.name = value
}
Person.create = function (value) { // ES5定义类的静态方法
return new Person(value)
}
Person.prototype.say = function () {
log(`hi, my name is ${this.name}`)
}
// const person = new Person()
const person = Person.create('Tom')
person.say()
2、现在我们采用class关键词定义类, 简洁易懂
class Person {
constructor(value) {
this.name = value
}
say () {
log(`hi, my name is ${this.name}`)
}
static create (value) { // ES6定义类的静态方法
return new Person(value)
}
}
// const person = new Person('Tom')
const person = Person.create('Tom')
person.say()
十一、extends类的继承
诞生的意义:让类的继承更加简洁易懂。
class Person {
constructor(value) {
this.name = value
}
say () {
log(`hi, my name is ${this.name}`)
}
}
class Student extends Person {
constructor(name, number) {
super(name) // super对象始终指向父类,调用它即调用父类构造函数
this.number = number
}
hello () {
super.say()
log(`my school number is ${this.number}`)
}
}
// extends实现类的继承,相比于ES5中原型继承,更简洁易懂
const student = new Student('jack', 101)
console.log(student.hello())
十二、Set 数据结构
诞生的意义:Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
const list = new Set() // 数据集合中的元素不存在重复
list.add(1).add(2).add(2).add(4) // add方法会返回集合本身,因此可以链式调用
for (i of list) {
log(i)
}
log(list.delete(4))
log(list.size)
log(list.has(4))
// 常用的场景是数组去重
const arr = [1, 1, 4, 4, 5, 6]
log(Array.from(new Set(arr)))
log([...new Set(arr)])
十三、Map 数据结构
诞生的意义:严格意义上的键值对的集合,可以以任意数据类型作为键。
1、以往以多种数据类型作为键值的对象
const obj1 = {
[123]: '123',
[true]: 'true',
[{ a: 1 }]: 'a',
[{ b: 1 }]: 'b',
}
log(Object.keys(obj1)) // [ '123', 'true', '[object Object]' ]
log(obj1['[object Object]']) // b
问题:键值默认被Object.prototype.toString.call(key) 转为字符串
2、ES6键值对的集合定义
const obj2 = new Map()
const tom = { name: 'tom' }
obj2.set(tom, 'c')
log(obj2) // Map(1) {{…} => 'c'}
obj2.forEach((value, key) => log(value, key)) // {name: 'tom'}
log(obj2.get(tom)) // c
log(obj2.has(tom)) // true
log(obj2.delete(tom)) // true
log(obj2.clear()) // undefined
十四、Symbol基本数据类型
1、以往当不同文件重复定义全局对象的同一个属性出现覆盖问题,以下模拟不同文件。
const cache = {}
/* -----------------a.js--------------------- */
cache['foo'] = '111'
/* ----------------b.js--------------------- */
cache['foo'] = '222'
log(cache) // { foo: '222' }
2、Symbol类型可以定义常量,并添加对Symbol数据的描述
const s = Symbol('foo')
log(typeof s) // Symbol
log(Symbol('foo')) // Symbol(foo) 添加对Symbol数据的描述
log(Symbol('bar'))
log(Symbol('fpp'))
3、定义对象的私有属性,以前都是约定的如:_name : 'Tom',不可靠
const name = Symbol('name')
const person = {
[name]: 'Tom', // 3、定义对象的私有属性,以前都是约定的如:_name : 'Tom'
say () {
log(`${this[name]}`)
}
}
log(person.name) // undefined
log(person.say()) // Tom
Symbol类型的数据最主要的作用就是为对象添加一个独一无二的属性名
4、Symbol类型的比较
log(Symbol() === Symbol(), Symbol('foo') === Symbol('foo')) // false false
// Symbol内部维护了一个描述符到Symbol的映射表
log(Symbol.for('foo') === Symbol.for('foo')) // true
// Symbol.for()内部调用了toString()方法
log(Symbol.for(true) === Symbol.for('true')) // true
5、Symbol()属性更适合作为对象的私有属性
const obj1 = {
[Symbol()]: 'Symbol value',
[Symbol()]: 'Symbol value',
foo: 'value'
}
for (key in obj1) { // foo
log(key)
}
log(Object.keys(obj1)) // [ 'foo' ]
log(JSON.stringify(obj1)) // {"foo":"value"}
// 获取对象所有Symbol类型的键值
log(Object.getOwnPropertySymbols(obj1)) // [ Symbol(), Symbol() ]
for...in 、Object.keys()、JSON.stringify() 等都获取不了Symbol类型的属性,也反证Symbol类型更适合作为对象的私有属性。
十五、ES6 可迭代接口
诞生意义:理论上for...of可以遍历任何一种数据结构(数组、伪数组、Set、Map、等)
1、JavaScript中的循环遍历
const list1 = [1, 2, 3, 4, 5]
// const list1 = new Set([1, 2, 3, 4, 5])
for (const value of list1) {
log(value)
if (value > 2) {
break
}
}
// list1.some()
// list1.every()
const list2 = new Map()
list2.set({ a: 1 }, 1)
list2.set({ b: 2 }, 2)
for (const [key, value] of list2) {
log(key, value)
}
2、对比forEach和for...of
list2.forEach(item => log(item)) // 不能跳出循环
3、数据接口含有可迭代接口--Symbol(Symbol.iterator)
// const arr = [1, 2, 3, 4]
const arr = new Set([1, 2, 3, 4])
log(arr[Symbol.iterator]) // [Function: values] 数组原型对象上定义了可迭代函数,返回对象
log(arr[Symbol.iterator]().next()) // { value: 1, done: false } 调用对象的next() 方法
4、如何实现对象的可迭代接口,采用迭代器模式(设计模式)
const obj2 = {
study: ['吃饭', '睡觉', '大豆豆'],
life: ['出行', '旅游', '美食'],
work: ['摸鱼'],
[Symbol.iterator] () {
const list = Object.entries(obj2)
let index = 0
return {
next () {
return { value: list[index], done: index++ >= list.length }
}
}
}
}
// 通过自定义对象的[Symbol.iterator],实现了对象的可迭代接口,就可使用for...of
for (const [key, value] of obj2) {
log(key, value)
}
十六、ES6 生成器
1、一般使用
function* fn () {
log('111...')
yield 100
log('222...')
yield 200
}
const result = fn()
log(result.next()) // 111... 返回 { value: 100, done: false }
log(result.next()) // 222... 返回 { value: 200, done: false }
log(result.next()) // 返回 { value: undefined, done: true }
2、场景需求:实现一个发号器
function* createIdMaker () {
let id = 1
while (true) {
yield id++
}
}
const maker = createIdMaker()
log(maker.next().value) // 依次递增
log(maker.next().value)
log(maker.next().value)
3、如何实现对象的可迭代接口,采用生成器
const obj2 = {
study: ['吃饭', '睡觉', '大豆豆'],
life: ['出行', '旅游', '美食'],
work: ['摸鱼'],
[Symbol.iterator]: function* () {
const list = Object.entries(obj2)
for (const item of list) {
yield item
}
}
}
for (const item of obj2) {
log(item)
}
十七、ES7、ES8、等新增特性
1、Object.values()、Object.entries()、Object.getOwnPropertyDescriptors
const p1 = {
firsetName: 'Yon',
lastName: 'zhang',
get fullName () {
return this.firsetName + ' ' + this.lastName
}
}
log(Object.values(p1)) // [ 'Yon', 'zhang', 'Yon zhang' ] 获取对象属性值的集合
log(Object.entries(p1)) // 获取对象属性与值的集合
const descriptors = Object.getOwnPropertyDescriptors(p1)
log(descriptors)
const p2 = Object.defineProperties({}, descriptors)
p2.firsetName = 'ton'
log(p2.fullName)
2、字符串的扩展方法padEnd()和padStart()
const books = {
html: 3,
css: 10,
javaScript: 103,
}
for (const [key, value] of Object.entries(books)) {
log(`${key.padEnd(16, '-')} | ${value.toString().padStart(3, '0')}`)
}
// html------------ | 003
// css------------- | 010
// javaScript------ | 103