后海有树的院子,夏代有工的;此时此刻的云,二十来岁的你。
——冯唐《可遇不可求的事》
一、概述
ECMAScript是JavaScript的语言本身
- 通过看做JavaScript的标准化规范, 实际上JavaScript 是ECMAScript的扩展语言, 因为ECMAScript只提供了最基本的语法
- ES2015开始按照年份命名,不再按照版本号命名(ES6 为 ES2015)
基于ES5.1(ES6)的变化:
- 解决原有语法上的一些问题或者不足
- 对原有语法进行增强,使之变得更加便捷,易用
- 全新的对象、全新的方法、全新的功能
- 全新的数据类型和数据结构
二、方法
1. let、const
作用域: 全局作用域、函数作用域、块级作用域(新增)
实践: 能用 const
就用 const
,需要被修改的值用 let
, 万不得已不要用 var
2. 解构
⑴. 数组的解构
// 数组结构
const arr = [1, 2, 3, 4]
// 根据数组中元素的位置实现赋值
const [a, b, c, d] = arr
console.log(b)
// => 2
// 参数可以为空
const [, w, ,] = arr
console.log([, w, ,])
// => [ <1 empty item>, 2, <1 empty item> ]
// ... => 提取当前元素往后的所有成员
const [foo, ...rest] = arr
console.log(rest)
// => [ 2, 3, 4 ]
// 只会记录数组对应的脚标赋值, 也可以增删数组的值
const [fo, ba = '123', va, pa] = arr
console.log([ba, va, pa, more = '123'])
// => [ 2, 3, 4, '123' ]
⑵. 对象的解构
// 对象的解构
const userInfo = {
name: '张三',
age: 34,
sex: 1
}
// // 根据对象的变量名,匹配对象成员,获取指定的值
// const { name } = userInfo
// console.log(name)
// // => 张三
// 解构的变量名必须是对象内成员的名称, 所以不可避免的会发生重名问题
const name = '李四'
console.log(name)
// SyntaxError: Identifier 'name' has already been declared
// 重命名: 解构的 成员名 后面取一个新的名称
const { name: userName } = userInfo
console.log(userName)
// => 李四
// 可以直观感受简洁方便
const {log} = console
log('12')
// => 12
3. 字符串
⑴. 字符串的拓展方法
- includes()
- startsWith()
- endsWith()
// 字符串的拓展方法
const errorMsg = 'Error: value is not undefined'
console.log(
// startsWith 判断字符串是否 Error 开头
errorMsg.startsWith('Error')
// => true
)
console.log(
// endsWith 判断字符串是否 undefined 结尾
errorMsg.endsWith('undefined')
// => true
)
console.log(
// includes 判断字符串是否 包含 not
errorMsg.includes('not')
// => true
)
⑵. 模板字符串
- 模板字符串(反引号)内字符串支持换行
- 支持插值方式的嵌入
- 插值内可以嵌入表达式
// 模板字符串
const str = 'hello world'
console.log(str)
// => hello world
const str2 = `hello world`
console.log(str2)
// => hello world
// 反引号(模板字符串的新特性)
// 1. 支持换行
const str3 = `hello
world`
console.log(str3)
// => hello
// => world
// 2. 通过插值的方式进行嵌入(较于传统的字符串拼接方式便捷很多)
const name = '张三'
const msg = `name: ${name}`
console.log(msg)
// => name: 张三
// 3. 插值中可以嵌入 表达式
const number = 1
const pow = `pow: ${number + 1}`
console.log(pow)
// => pow: 2
⑶. 标签模板字符串
功能: 可以在模板字符串前添加个 标签,这个标签其实就是一个函数
书写方式:
const logStr = console.log`hello world`
// => [ 'hello world' ]
// console.log 标签(函数)
// hello world 标签(函数)的参数
示例:
// 模板字符串可以对返回的数据进行加工
const userName = '张三'
const userSex = 0 ? '男' : '女'
function tagFunc(val, userName, userSex) {
// console.log(val, userName, userSex)
return userName + val[1] + userSex
}
const result = tagFunc`${userName}: ${userSex}`
// => [ '', ': ', '' ] 张三 0
console.log(result)
// => 张三: 女
4. 参数
⑴. 参数默认值
// 参数默认值
// 设置默认参数 value => true
function foo(value) {
// 传统方式 / 短路运算
// value = value || true
// 问题: 如果传入 false, 也会被设置默认值 true
value = value == undefined ? true : value
console.log(value)
}
foo()
// => true
foo(true)
// => true
foo(false)
// => false
⑵. 剩余参数
// 剩余参数
const arr = [1, 2, 3, 4]
function foo(val) {
// 传统 通过 arguments 这个伪数组来接收
console.log(arguments)
// => [Arguments] { '0': [ 1, 2, 3, 4 ] }
// ... 能接受从当前位置开始,之后所有的实参
console.log(...val)
// => 1 2 3 4
}
foo(arr)
⑶. 展开数组
// 展开数组
const arr = ['tom', 'bob', 'lucy']
// 硬方法
console.log(arr[0], arr[1], arr[2])
// => tom bob lucy
// 传统方法
// 函数对象的 apply 方法: 第一个参数是 this 的指向, 第二个参数是实参数组
console.log.apply(console, arr)
// => tom bob lucy
// ES6
// ... 展开运算符
console.log(...arr)
// => tom bob lucy
5. 箭头函数
⑴. 基础语法
// 箭头函数
// 传统函数
function add(val) {
return val + 1
}
console.log(add(1))
// => 2
// 箭头函数
// const addNumber = (val) => {
// return val + 1
// }
// 简化 =>
const addNumber = val => val + 1
console.log(addNumber(1))
// => 2
⑵. this 指向
// this 指向
const person = {
name: 'tom',
sayHi: function () {
console.log(`Hi, my name is ${this.name}`)
// => Hi, my name is tom
},
// 箭头函数不会改变 this 的指向(内部没有 this 的机制)
sayHello: () => {
console.log(`Hello, my name is ${this.name}`)
// => Hello, my name is undefined
},
sayAsyncHello: function () {
setTimeout(function () {
console.log(`Hello, my name is ${this.name}`)
// => Hello, my name is undefined
// 原因: setTimeout 内部的函数最终会放到 全局对象 上被调用, 所以这里的 this 访问的是全局作用域
}, 0)
},
// 解决办法1: 定义一个变量, 储存当前 this 对象
sayAsyncHello2: function () {
let _this = this
setTimeout(function () {
// 基于闭包的机制在内部使用
console.log(`Hello, my name is ${_this.name}`)
// => Hello, my name is tom
}, 0)
},
// 解决办法2: 使用箭头函数,箭头函数的 this 指向永远是 包裹箭头函数的第一个普通函数
sayAsyncHello3: function () {
setTimeout(() => {
console.log(`Hello, my name is ${this.name}`)
// => Hello, my name is tom
}, 0)
}
}
person.sayHi()
person.sayHello()
person.sayAsyncHello2()
person.sayAsyncHello3()
6. 对象
⑴. 对象字面量增强
简化写法:
// 对象字面量增强
const name = 'zoe'
const obj = {
// name: name,
// 如果变量和属性名是一致, 可以等价 name: name === name
name,
age: '18',
// 给属性添加方法
//传统
method1: function () {
console.log('method1')
},
// 简化
method2() {
console.log('method2')
// 内部还是使用了 function, this 的指向是当前对象
console.log(this)
}
}
console.log(obj)
// => { name: 'zoe', age: '18' }
obj.method1()
// => method1
obj.method2()
// => method2
// => {
// => name: 'zoe',
// => age: '18',
// => method1: [Function: method1],
// => method2: [Function: method2]
// => }
动态添加属性名:
// 对象动态添加属性名
const obj = {
name: 'zoe',
age: 18,
// 字面量的方式(计算属性名) '[]'内部可以是计算表达式
[Math.random()]: 456,
[1 + 1]: 789
}
// 添加动态属性名
// 传统方法
// 只能在对象声明过后, 通过索引器的方式动态添加属性值
obj[Math.random()] = 123
console.log(obj)
// => {
// => '2': 789,
// => name: 'zoe',
// => age: 18,
// => '0.9406667184590531': 456,
// => '0.04643497457906176': 123
// => }
⑵. Object.assign
将多个源对象中的属性,复制到一个对象当中,如果对象中有相同的属性,那么源对象中的属性就会覆盖掉当前对象的属性值
语法:
const source1 = {
name: 'zoe',
age: 18
}
const source2 = {
name: 'bom',
sex: 0
}
const target = Object.assign(source1, source2)
console.log(target)
// => { name: 'bom', age: 18, sex: 0 }
应用场景:
const data = {
name: 'zoe',
age: 18,
sex: 0
}
// 直接修改, 会同时修改数据源(对象复制的是指针)
const userInfo = data
userInfo.sex = 1
console.log(data)
// => { name: 'zoe', age: 18, sex: 1 }
console.log(userInfo)
// => { name: 'zoe', age: 18, sex: 1 }
// Object.assign
const userInfo2 = Object.assign({}, data)
userInfo2.sex = 0
console.log(data)
// => { name: 'zoe', age: 18, sex: 1 }
console.log(userInfo2)
// => { name: 'zoe', age: 18, sex: 0 }
⑵. Object.is
判断两个对象是否相等
console.log(
false == 0,
// => true
false === 0,
// => false
+0 === -0,
// => true
NaN === NaN,
// => false
// 在特殊情况下, 这种等式并不规范
Object.is(+0, -0),
// => false
Object.is(NaN, NaN)
// => true
)
7. 代理对象
⑴. Proxy
代理: 就像一个门卫,可以知道外部对内部的修改和读取
const person = {
name: 'zoe',
age: 18
}
// Proxy 第一个参数是需要代理的对象, 第二个参数是代理的处理对象
const personProxy = new Proxy(person, {
// 监听属性的访问
// 接收两个参数, 代理对象, 外部访问的属性名
get(target, property) {
return property in target ? target[property] : 'default'
},
// 监听属性的修改
// 接收三个参数, 代理的目标对象, 写入的目标名称, 写入的属性值
set(target, property, value) {
console.log(target, property, value)
target.property = value
}
})
console.log(personProxy.name)
// => zoe
console.log(personProxy.nothing)
// => default
personProxy.gender = true
// => { name: 'zoe', age: 18 } gender true
⑵. defineProperty
defineProperty
只能监听到对象的读取和写入Proxy
是以非入侵的方式监听了对象的读写, 而defineProperty
需要单独对需要监听的属性做额外操作Proxy
能够监听到更多对象的操作- delete 方法
- 对 对象中方法调用
- …
const person = {
name: 'zoe',
age: 18
}
const personProxy = {
// 接收两个参数, 代理对象, 外部访问(删除)的属性名
defineProperty(target, property) {
delete target[property]
}
}
delete person.age
console.log(person)
// => { name: 'zoe' }
⑶. 数组对象的监视
传统对数组对象监视的方法是 重写数组
, 通过自定义的方法覆盖掉,数组对象原型上的push
、 shift
… 方法,以此来劫持调用这些方法的过程
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 (监听到 push 的值所处的下标, 并返回)
// => set length 1
⑷. Reflect
- 是一个静态类,不能够通过
new Reflect
的方式构建实例对象,只能够调用静态类中的静态方法 - 提供了统一的对象操作 API
Proxy 的内部 get 方法:
const person = {
name: 'zoe',
age: 18
}
const personProxy = new Proxy(person, {
// 如果没有在 Proxy 内部定义 get 方法, 就等同于内部定义了 get 方法,并将参数交给 Reflect 的 get 方法
get(target, property) {
console.log('watch name')
return Reflect.get(target, property)
}
})
console.log(personProxy.name)
// => watch name
// => zoe
统一的对象操作 API:
const person = {
name: 'zoe',
age: 18
}
// 判断对象 person 内部是否有 name 属性名
console.log('name' in person)
// 删除对象内部 age 的属性名
console.log(delete person['age'])
// 获取对象中的属性名
console.log(Object.keys(person))
// 提供了统一的对象操作 API
console.log(Reflect.has(person, 'name'))
console.log(Reflect.deleteProperty(person, 'age'))
console.log(Reflect.ownKeys(person))
8. 类
⑴. Class
之前都是通过定义函数、函数的原型对象来实现的类型
// 定义一个名为 person 的函数,作为这个函数的构造函数
function Person (name) {
// this 访问当前的实例对象
this.name = name
}
// this 实例中需要共享成员: 通过函数对象的 prototype/原型 来实现
Person.prototype.say = function() {
console.log(`hi, my name is ${this.name}`)
}
const p = new Person('zoe')
p.say()
// => hi, my name is zoe
// 通过 class 实现
class Person2{
// 通过 constructor 创建一个构造方法作为函数
constructor(name) {
this.name = name
}
say() {
console.log(`hi, my name is ${this.name}`)
}
}
// 创建类型的构造实例
const p2 = new Person2('zoe')
p2.say()
// => hi, my name is zoe
⑵. static
类的方法: 实例方法、静态方法
- 以前实现静态方法,就是在构造函数上挂载方法来实现,因为在 JS 中函数也是对象,他也可以添加一些方法成员
- ES6 中新增了添加静态成员的 static 关键词
class Person{
constructor(name) {
this.name = name
}
// 创建一个名为 creat 的静态方法, 用于创建 Person 的实例
static creat(name) {
return new Person(name)
}
}
const zoe = Person.creat('zoe')
console.log(zoe)
// => Person { name: 'zoe' }
// tip: 静态方法是挂载到类型上的,所以静态方法内部的 this 指向的是当前的类型
⑶. extends 类的继承
- ES6 之前大多使用原型的方式实现继承
- ES6 中新增了extends 关键词
class Person{
constructor(name) {
this.name = name
}
say() {
console.log(`hi, my name is ${this.name}`)
}
}
// 继承的 Students 就会拥有 Person 里面所有的成员了
class Student extends Person {
// 定义一个构造函数, 接收两个参数
constructor(name, number) {
// super对象 永远指向父类, 调用它就调用了 父类的构造函数
super(name)
// 定义这个类型特有的成员
this.number = number
}
// 定义一个方法
hello() {
super.say()
console.log(`hello, my school number is ${this.number}`)
}
}
const h = new Student('bob', '123')
h.hello()
// => hi, my name is bob
// => hello, my school number is 123
9. 数据结构
⑴. Set
基础用法:
const s = new Set()
// add 方法可以添加数据
s.add(1)
// add 方法会返回集合对象本身, 所以可以链式调用
s.add(1)
.add(2)
.add(3)
.add(2)
console.log(s)
// => Set(3) { 1, 2, 3 }
// set 内部不允许出现重复的值, 所以重复添加的值会被忽略
// 遍历集合中的数据
// forEach
s.forEach(i => console.log(i))
// => 1 2 3
// for...of
for (let i of s) console.log(i)
// => 1 2 3
// 获取对象的长度
console.log(s.size)
// => 3
// Set 的其他方法
// has 是否包含
console.log(s.has(2))
// delete 删除
console.log(s.delete(1))
// => true
// clear 清除
s.clear()
console.log(s)
// => Set(0) {}
去重:
const arr = [1, 2, 3, 4, 2, 3, 5]
const result = new Set(arr)
console.log(result)
// => Set(5) { 1, 2, 3, 4, 5 }
// 对象转数组
// 方法 1: Array.from
const result2 = Array.from(new Set(arr))
console.log(result2)
// => [ 1, 2, 3, 4, 5 ]
// 方法 2: ... 展开运算符
const result3 =[...new Set(arr)]
console.log(result3)
// => [ 1, 2, 3, 4, 5 ]
⑵. Map
const obj= {}
obj[true] = 'value1'
obj[123] = 'value2'
obj[{name: 'zoe'}] = 'value3'
console.log(Object.keys(obj))
// => [ '123', 'true', '[object Object]' ]
// 添加的 键 都变成了字符串
// map 键值对集合,用来映射键值对之间的关系
// 和对象的区别,可以以任意类型的数据作为键
const m = new Map()
const zoe = {name: 'zoe'}
m.set(zoe, 90)
console.log(m)
// => Map(1) { { name: 'zoe' } => 90 }
console.log(m.get(zoe))
// => 90
m.forEach((value, keys) => {
console.log(value, keys)
})
// => 90 { name: 'zoe' }
// 同 set
m.has()
m.delete()
m.clear()
10. Symbol
- 一种全新的原始数据类型
- 独一无二
- 主要作用:为对象添加独一无二的属性值(属性标识符)
⑴. 出现的意义
// shared.js 全局共享
// 定义一个用于存放数据缓存的对象
const cache = {}
// a.js
// 在缓存中 放入一个键为 foo,值为随机数
cache['foo'] = Math.random()
// b.js
// 不知道其他文件夹已经存放了 foo 键的情况下, 再次存放名为 foo 的数据
cache['foo'] = '123'
console.log(cache)
// => { foo: '123' }
// 传统解决方案 - 约定键名
// c.js
cache['c_foo'] = '456'
console.log(cache)
// => { foo: '123', c_foo: '456' }
⑵. 功能
// 可以传入一个字符串作为这个类型描述
console.log(Symbol('foo'))
// => Symbol(foo)
console.log(Symbol('bar'))
// => Symbol(bar)
// 1. 解决 键名冲突问题
const obj = {}
// 再添加键时,就不需要考虑键名冲突的问题
obj[Symbol()] = '123'
obj[Symbol()] = '456'
console.log(obj)
// => { [Symbol()]: '123', [Symbol()]: '456' }
// 2. 创建私有成员
// a.js
const name = Symbol()
const userInfo = {
[name]: 'zoe',
say() {
console.log(`hi, my name is ${this[name]}`)
}
}
userInfo.say()
// => hi, my name is zoe
// b.js
// 因为 Symbol 为独一无二, 所以外部无法访问 a.js 内部的私有成员
// 只能调用普通成员
userInfo.say()
// => hi, my name is zoe
⑶. 补充
console.log( Symbol() === Symbol() )
// => false
console.log( Symbol('foo') === Symbol('foo') )
// => false
// Symbol for 方法可以实现相同的描述会有相同的 Symbol 类型(内部维护了一个全局的注册表,为描述和 Symbol 值提供了一一对应的关系)
// 传入的值都会转换为字符串
console.log(
Symbol.for(true) === Symbol.for('true')
)
// => true
// Symbol 存在内置的常量
const obj = {
[Symbol.toStringTag]: 'XYZ'
}
console.log(obj.toString())
// => [object XYZ]
// 自定义的 toString 'XYZ'
const obj2 = {
// 设置特定属性值
[Symbol()]: 'symbol value',
foo: 'normal value'
}
// 通过传统 for in 是无法获取到特定属性值的
for(var keys in obj2) {
console.log(keys)
}
// => foo
// Object.keys 方法同样获取不到
console.log(Object.keys(obj2))
// => [ 'foo' ]
// Object.getOwnPropertySymbols 和 Object.keys 方法类似, 只是专门用来获取 Symbol 类型的属性名
console.log(
Object.getOwnPropertySymbols(obj2)
)
// => [ Symbol() ]
11. 遍历
- for 遍历数组
- for…in 遍历 jianzhidui
- 对象的遍历方法:forEach…
- !!! for…of 将作为遍历所有数据结构的统一方式
⑴. 基础用法
const arr = [1, 2, 3]
// 1. for of 方法拿到的是数组中的每一个元素
for (let i of arr) {
console.log(i)
}
// => 1 2 3
// 2. for of 可以使用 break 随时终止循环
// 传统终止遍历的方法 forEach + some/every
for (let i of arr) {
if(i > 2) break
console.log(i)
}
// => 1 2
// 3. Set 对象遍历
const s = new Set(['foo', 'bar'])
for (let i of s) {
console.log(i)
}
// => foo bar
// 4. Map 对象遍历
const m = new Map()
m.set('name', 'zoe')
m.set('age', 18)
for (let i of m) {
console.log(i)
}
// => [ 'name', 'zoe' ] [ 'age', 18 ]
// Map 可以直接循环键值
for(let [key, value] of m) {
console.log(key, value)
}
// => name zoe age 18
// 5. 遍历普通对象
const obj = {
name: 'zoe',
age: 18
}
for (let i of obj) {
console.log(i)
}
// => TypeError: obj is not iterable
⑵. iterable - 可迭代接口
接口: 就是实现了相同的规格标准
打开 VScode 的开发者工具:
因为数组、Set、map 能够遍历,寻找三者相同的规范标准
创建一个数组,直接调用这个方法
得到的是一个数组的迭代器对象,并且继续调用 next 方法,会得到一个猜想:
next
方法内部应该维护了一个指针,每调用一次 next
,指针就会向后移动一位,而 done
的作用是记录内部的数据是否全部被遍历
手动调用来证实猜想:
const s = new Set(['foo', 'bar', 'bsz', 'foo'])
const iterable = s[Symbol.iterator]()
console.log(iterable.next())
// => { value: 'foo', done: false }
console.log(iterable.next())
// => { value: 'bar', done: false }
console.log(iterable.next())
// => value: 'bsz', done: false }
console.log(iterable.next())
// => { value: undefined, done: true }
console.log(iterable.next())
// => { value: undefined, done: true }
// 其实这也是 for...of 内部的工作原理
⑶. 实现可迭代接口
const obj = {
// 因为 Symbol.iterable 是一个 Symbol 常量, 所以需要通过计算属性名的方式提供到自定义属性当中
[Symbol.iterator]: function () {
return {
// 需要提供一个 next 方法, 实现向后迭代
next: function () {
// 需要返回一个结果对象, 对象内有两个参数,
return {
value: 'zoe',
done: false
}
}
}
}
}
// 调试
const obj2 = {
store: ['foo', 'bar', 'baz'],
[Symbol.iterator]: function () {
let index = 0
return {
next: () => {
const result = {
value: this.store[index],
done: index >= this.store.length
}
index++
return result
}
}
}
}
for (let i of obj2) {
console.log(i)
}
// => foo bar baz
⑷. 迭代器模式
对外部提供统一遍历的解构,外部不用关注内部的数据结构
// 协同实现一个任务清单
// A:
const todo = {
life: ['吃饭', '睡觉', '打游戏'],
work: ['前端', '后端', '数据库'],
sport: ['篮球']
}
// B:
for (let i of todo.life) {
console.log(i)
}
for (let i of todo.work) {
console.log(i)
}
for (let i of todo.sport) {
console.log(i)
}
// B 调用 A 的数据, 需要根据 A 的数据结构不断调整
// C:
const todo2 = {
work: ['前端', '后端', '数据库'],
sport: ['篮球', '跑步'],
family: ['旺财'],
// 定义一个名为 each 的方法, 接收外部的回调函数作为参数
each: (callback) => {
const all = [].concat(todo2.work, todo2.sport, todo2.family)
for (let i of all) {
// 提供了统一遍历的接口
callback(i)
}
},
// iterable 方法
[Symbol.iterator]: () => {
const all = [...todo2.work, ...todo2.sport, ...todo2.family]
let index = 0
return {
next: () => {
return {
value: all[index],
done: index++ >= all.length
}
}
}
}
}
console.log('----------------------')
// D:
// 只需要调用 C 提供的接口, 就可以获取到遍历的数据, 并且不需要关系其数据结构
todo2.each(item => {
console.log(item)
})
// => 前端 后端 数据库 篮球 跑步 旺财
console.log('----------------------')
for(let i of todo2) {
console.log(i)
}
// => 前端 后端 数据库 篮球 跑步 旺财
12. Promise
- 一种全新的异步编程解决方案
- 链式调用的方式解决了,传统异步编程过程中回调函数嵌套过深的问题
- 因为涉及的内容过多,这里不多概述,相信大家对于 Promise 已经具备了很深的李姐(理解)
13. Async await
- 解决异步函数回调嵌套过深的问题
- 本质上还是运用 Promise 的语法糖
14. Generator 生成器
⑴. 概念
作用: 避免异步编程中回调嵌套过深,提供更好的异步编程解决方案
基础语法:
// 定义一个生成器函数
function * foo(){
console.log('foo')
return 100
}
// 生成器函数是静态方法,不能被直接调用
const result = foo()
console.log(result)
// => Object [Generator] {}
console.log(result.next())
// => foo
// => { value: 100, done: true }
基本用法:
- 规律: 生成器函数会返回一个生成器对象,调用该对象的 next 方法,才会让函数的函数体开始执行,执行过程中一旦遇到 yield 关键词,函数的执行就会被暂停下来,并且 yield 的值将作为 next 的结果返回;继续调用对象 next 方法,函数就会继续向后执行
- 特点: 惰性执行
function * bar () {
console.log('111')
// yield 同 return 可以返回值, 但是不会立即结束
yield 100
console.log('222')
yield 200
}
const generator = bar()
console.log(generator.next())
// => 111
// => { value: 100, done: false }
console.log(generator.next())
// => 222
// => { value: 200, done: false }
console.log(generator.next())
// => 111
// => { value: undefined, done: true }
⑵. 生成器应用
// 发号器
// 创建一个生成器函数
function * creatIdMaker() {
let id = 0
// 定义一个死循环,不断 + 1
while(true) {
// yield 执行时, 就会暂停, 知道下一次调用 yield
yield id++
}
}
// 外部创建一个生成器对象, 实现 '拿号'
const idMaker = creatIdMaker()
// 每次调用 next 方法, 就能获取一个 自增的 id
console.log(idMaker.next().value)
// => 0
console.log(idMaker.next().value)
// => 1
console.log(idMaker.next().value)
// => 2
console.log(idMaker.next().value)
// => 3
15. ES Modules
语言层面的模块化标准
16. ES 2016
- 数组实例对象 Array.prototype.includes
- 指数运算符 **
includes 方法
// Array.prototype.includes
const arr = ['foo', 'bar', NaN, 'baz']
console.log(arr.indexOf('bar'))
// => 1 (返回元素对应的下标)
console.log(arr.indexOf('xxx'))
// => -1 (未找到)
console.log(arr.indexOf(NaN))
// => -1
// includes 方法可以直接判断
console.log(arr.includes(NaN))
// => true
指数运算符**
// 传统方法 Math 方法
console.log(Math.pow(4, 2))
// => 16
// ES6 新增
console.log(4 ** 2)
// => 16
17. ES 2017
- Object.value 值组成的数组
- Object.entries 以数组的形式返回所有的键值对
- Object.getOwnPropertyDescriptors 复制对象
- String.prototype.padStart / String.prototype.padEnd 字符串填充
- 允许在函数参数中添加尾逗号
const obj = {
name: 'zoe',
age: 18
}
// Object.value 值组成的数组
console.log(Object.values(obj))
// => [ 'zoe', 18 ] (keys 方法返回的是键组成的数组)
// Object.entries 以数组的形式返回所有的键值对
console.log(Object.entries(obj))
// => [ [ 'name', 'zoe' ], [ 'age', 18 ] ]
for (let [key, value] of Object.entries(obj)) {
console.log(key, value)
}
// => name zoe age 18
// Object.getOwnPropertyDescriptors 复制对象
const p1 = {
firstName: 'li',
lastName: 'hua',
get fullName() {
return this.firstName + '' + this.lastName
}
}
const p2 = Object.assign({}, p1)
p2.firstName = 'Wang'
console.log(p2.fullName)
// => li hua (Object.assign 在复制时 只是把 p1.fullName 当做普通的属性复制了)
const descriptors = Object.getOwnPropertyDescriptors(p1)
const p3 = Object.defineProperties({}, descriptors)
p3.firstName = 'Wang'
console.log(p3.fullName)
// => Wang hua
// String.prototype.padStart / String.prototype.padEnd 字符串填充
const objNumber = {
html: 18,
css: 9,
javaString: 666
}
for (let [key, value] of Object.entries(objNumber)) {
console.log(key, value)
}
// => html 18
// => css 9
// => javaString 666
for (let [key, value] of Object.entries(objNumber)) {
// padEnd / padStart 有两个参数, 给定的长度, 用来填充的符号
console.log(`${key.padEnd(16, '-')}`, `${value.toString().padStart(3, '0')}`)
}
// => html------------ 018
// => css------------- 009
// => javaString------ 666
// 在函数参数中添加尾逗号
const userInfo = {
name: 'zoe',
age: 18,
sex: 0, // 这个就是尾逗号
}
// 1. 在 alt + ↑ / ↓ 时, 不会出现格式问题
// 2. 在尾部添加数据时, 不需要再上一个属性尾部添加 ','