目录
ES6 入门教程https://es6.ruanyifeng.com/
let和const
块级作用域
let声明的变量,只在let命令所在的代码块内有效
{
var a = 10
let b = 20
}
console.log(a) // 10
console.log(b) // ReferenceError: b is not defined b未定义
console.log(c) // ReferenceError: c is not defined c未定义
不存在变量提升
console.log(a) // undefined
var a = 10
//var声明的变量会发生变量提升的现象,即变量可以在声明之前使用,值为undefined
/*--------------------------------------------------------------------------------------------------------------*/
console.log(b) // ReferenceError: Cannot access 'b' before initialization
let b = 20
//let只允许变量先声明再使用,否则就会报错 在初始化之前无法访问'b'
/*-------------------------------------------------------------------------------------------------------------*/
console.log(c) // ReferenceError: Cannot access 'c' before initialization
const c = 30
//const也要求先声明再使用
不允许被重复声明
var a = 10
var a = 20
console.log(a) // 20 ,var后声明的a覆盖掉了前面的a
/*--------------------------------------------------------------------------------------------------------------*/
let b = 30
let b = 40
console.log(b) // SyntaxError: Identifier 'b' has already been declared
// 语法错误,标识符'b'已经被声明了,let不允许重复声明
/*--------------------------------------------------------------------------------------------------------------*/
const c = 50
const c = 60
console.log(c) // SyntaxError: Identifier 'c' has already been declared
// 语法错误,标识符'c'已经被声明了,const不允许重复声明
/*--------------------------------------------------------------------------------------------------------------*/
function fn(age) {
let age
} // SyntaxError: Identifier 'age' has already been declared
//let不允许在相同的作用域内重复声明一个变量
/*--------------------------------------------------------------------------------------------------------------*/
function fn(max) {
const max
} // SyntaxError: Identifier 'max' has already been declared
//const不允许在相同的作用域下重复声明一个变量
/*--------------------------------------------------------------------------------------------------------------*/
const d = 10
d = 20
console.log(d) // TypeError: Assignment to constant variable
// d 是一个赋值的常数变量,不允许被修改,const声明的变量不允许被修改
/*--------------------------------------------------------------------------------------------------------------*/
const obj = {
uname: 'andy'
}
obj.uname = 'tom'
console.log(obj) // {uname: 'tom'}
// 可以修改内部对象
obj = {
age: 18
}
console.log(obj) // TypeError: Assignment to constant variable.
//不允许直接修改对象
const arr = []
for (var i = 0; i < 10; i++) {
arr[i] = function () {
return i
}
}
console.log(arr); //arr中存了十个函数
console.log(arr[5]()) // 10
// var 声明的变量有变量提升,执行完循环返回的i变成了10,在arr[5]()中的5改成任意一个数输出返回的都是10
for (let i = 0; i < 10; i++) {
arr[i] = function () {
return i
}
}
console.log(arr);
console.log(arr[5]()) //让索引号为5的函数执行,return的i是5
let RegExp = 10
console.log(RegExp) // 10
console.log(window.RegExp) // ƒ RegExp() { [native code] }
//let声明的变量不会造成变量污染影响到全局变量
模板字符串
以前的写法
<div class="box"></div>
<script>
let div = document.querySelector('.box')
let id = 1, uname = '小马哥'
div.innerHTML = "<ul><li id=" + id + ">" + uname + "</li></ul>"
模板字符串写法
<div class="box"></div>
<script>
let div = document.querySelector('.box')
let id = 1, uname = '小马哥'
let htmlStr = `
<ul>
<li id="${id}">${uname}</li>
</ul>
`
div.innerHTML = htmlStr
函数默认值,剩余参数,扩展运算符
函数默认值
//一个求和函数,赋予参数默认值
function add(a, b) {
a = a || 10
b = b || 20
console.log(a + b)
}
add() // 30
//当调用函数没有传递参数时,使用默认值计算,若传递有参数,则使用传参计算
在ES6中可以这样写
function add(a = 10, b = 40) {
console.log(a + b)
}
add() //50
但是在附默认值,传参时这种写法是错误的
function add(a = 10, b) {
console.log(a + b)
}
add(20) // NaN
//此时20会被赋给a,b未赋值是undefined,两者相加不是一个数字,输出NaN
这个默认值也可以是一个函数
function add(a = 10, b = getVal(5)) {
console.log(a + b)
}
function getVal(val) {
return val + 5
}
add() // 20
剩余参数
在ES5中我们在不确定用户会传递几个参数时,我们会使用arguments来接收成一个伪数组
let obj = {
uname: '刘德华',
age: 18,
sex: '男'
}
let obj2 = {
uname: 'andy',
age: 20,
sex: '女'
}
function pick(data) {
console.log(arguments);
//arguments是pick里面的所有参数,伪数组的形式[{uname: '刘德华', age: 18, sex: '男'},'uname', 'sex']
let result = {}
for (let i = 1; i < arguments.length; i++) {
console.log(arguments[i])
//arguments[i]是属性名,arguments[1]是传递参数里面的uname,arguments[2]是传递参数里面的sex
result[arguments[i]] = data[arguments[i]] //data[arguments[i]]的值分别是刘德华,男
//也就是说在这个循环里面给result加上属性uname,sex并分别赋上相应的值
console.log(result[arguments[i]]) //s被附了data[arguments[i]]的值
}
return result
}
let Data = pick(obj, 'uname', 'sex') //前面这个参数是选择获取那个对象的值进行传参
console.log(Data) // {uname: '刘德华', sex: '男'}
在ES6中我们可以获取一个真正的数组。使用(…+名字)这种语法来做形参,返回的是一个数组,但是这个一定要写在形参的最后一个
function pick2(data, ...keys) {
console.log(keys) // ['uname', 'sex']
let result = {}
for (let i = 0; i < keys.length; i++) {
result[keys[i]] = data[keys[i]]
}
return result
}
let Data2 = pick2(obj, 'uname', 'sex')
console.log(Data2) // {uname: '刘德华', sex: '男'}
对比一下两者
function checkArgs1(data) {
console.log(arguments)
}
function checkArgs2(...args) {
console.log(args);
}
//由此可见使用ES6提供新方法获得的数据不再是伪数组,可以使用相应的数组方法操作数据
控制台打印输出
扩展运算符
扩展运算符:将一个数组(或对象)分割,并将数组的各个项作为分离的参数传给函数。其实就是把数组或对象拆开
//当要获取数组的最大值,以前我们会使用apply
const arr = [12, 95, 46, 77, 182, 65, 42]
console.log(Math.max.apply(window, arr)); // 182
在ES6中扩展运算符可以将其改写为
console.log(Math.max(...arr)); //182
也可以将对象中的各个项作为分离的参数来传递
let obj1 = { x: 100, y: 200 }
let obj2 = {
a: 250,
...obj1
}
console.log(obj2) // {a: 250, x: 100, y: 200}
箭头函数
箭头函数的语法
在ES5中我们这样定义一个函数
function add(a, b) {
return a + b
}
console.log(add(10, 20)) // 30
在ES6中我们可以使用箭头函数简化写法
let add = (a, b) => {
return a + b
}
console.log(add(5, 3)) // 8
像这种只有一个返回值的我们甚至可以有更简洁的写法
let add = (a, b) => a + b
console.log(add(5, 3)) // 8
当我们的返回值是一个对象时
let getObj = id => {
return {
id: id,
name: '刘德华'
}
}
console.log(getObj(2)) // {id: 2, name: '刘德华'}
它也是只有一个返回值,但我们不能直接将对象写在箭头后面,需要用一个小括号将其包裹起来
let getObj = id => ({
id: id,
name: '刘德华'
})
console.log(getObj(2)) // {id: 2, name: '刘德华'}
对象中的函数和箭头函数
在一个page对象中,有参数和方法
let page = {
id: 123,
init: function () {
document.addEventListener('click', function (event) {
console.log(this) //这里的this是document
this.text(event.type) //document中没有text方法,就会报错
})
},
text: function (type) {
console.log(`事件类型:${type},当前id:${this.id}`)
}
}
page.init() //报错
函数内部的this只能通过作用域链来查找确定,一旦使用箭头函数,当前就不存在作用域链了,就会向上一层寻找作用域链
let page = {
id: 123,
init: function () {
document.addEventListener('click', (event) => {
console.log(this) //这里的this指向对象page
this.text(event.type) //page中可以查找到text方法
})
},
text: function (type) {
console.log(`事件类型:${type},当前id:${this.id}`)
// 打印输出:事件类型:click,当前id:123
}
}
page.init() //事件类型:click,当前id:123
若我们将init方法也改写为箭头函数时
let page = {
id: 123,
init: () => {
document.addEventListener('click', (event) => {
console.log(this) // this会在向上一层寻找,找到window
this.text(event.type) //但window并没有text方法
})
},
text: function (type) {
console.log(`事件类型:${type},当前id:${this.id}`)
}
}
page.init() //报错:this.text is not a function (window中的text方法未定义)
解构赋值
完全解构
在ES5中我们获取一个对象中的值
let obj = {
uname: '张三',
age: 18
}
let uname = obj.uname
let age = obj.age
console.log(uname, age) // 张三 18
在ES6中我们使用解构赋值
let { uname, age } = obj
console.log(uname, age) // 张三 18
不完全解构
let obj = {
a: {
name: 'andy',
age: 20
},
b: [2, 4, 6, 7, 9],
c: 'hello,world'
}
let { a } = obj
console.log(a) // {name: 'andy', age: 20}
//只获取a这一个属性值
我们还可以使用剩余运算符
let { b, ...rest } = obj
console.log(rest) // {a: {…}, c: 'hello,world'}
//这样a和c的值就保存在rest中了
重命名
let { a: t } = obj
console.log(t) // {name: 'andy', age: 20}
// 此时若再打印输出a则显示a is not defined
对数组解构
let arr = [1, 6, 9]
let [a, b, c] = arr
console.log(a, b, c) // 1, 6, 9
对象的扩展
在ES5中我们这样给一个对象写入值
let uname = 'andy', age = 18
let person = {
uname: uname,
age: age,
getName: function () {
console.log(this.uname);
}
}
console.log(person) // {uname: 'andy', age: 18, getName: ƒ}
person.getName() // andy
ES6中简化写法
let person = {
uname,
age, // 直接将外部的属性及值拿到对象中来
getName() {
console.log(this.uname)
} // 不需要再写function
}
assign()方法
let target = {
a: 1
}
let obj1 = {
b: 2,
c: 3
}
let obj2 = {
d: 5
}
Object.assign(target, obj1, obj2) //把第二个参数及后面的对象都合并给第一个
console.log(target) //{a: 1, b: 2, c: 3, d: 5}
//浅拷贝语法糖
Object.assign(other, obj) //把obj拷贝给other 代替for in
Symbol
ES6 引入了一种新的原始数据类型
Symbol
,表示独一无二的值。它属于 JavaScript 语言的原生数据类型之一,其他数据类型是:undefined
、null
、布尔值(Boolean)、字符串(String)、数值(Number)、大整数(BigInt)、对象(Object)。
Symbol 值通过Symbol()
函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
let uname1 = name
let uname2 = name
console.log(typeof (uname1)) // string
console.log(uname1 === uname2) // true
let uname3 = Symbol(name)
let uname4 = Symbol(name)
console.log(typeof (uname3)) // symbol 原始数据类型
console.log(uname3 === uname4) // true
通过Symbol声明的属性名
let name = Symbol('name')
console.log(name) // Symbol(name)
let obj = {
[name]: 'andy'
}
console.log(obj) //{Symbol(name): 'andy'}
//在这个对象中的Symbol(name)这个属性名是独一无二的
当我们想获取对象中的Symbol属性值时
console.log(obj[name]) // andy
console.log(obj.name) // undefined ----> 错误方法
属性名的遍历
let name = Symbol('name')
let obj = {
[name]: 'andy'
}
let obj1 = {
name: 'tom'
}
for (let k in obj) {
console.log(k)
} //在这个obj中的[name]属性属于symbol类型,无法使用for...in遍历得到属性名
for (let k in obj1) {
console.log(k) // name
} //但这个obj1中的name属性是string型,for...in遍历可以得到属性名name
Symbol 值作为属性名,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
//在ES6中我们提供了Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
let s = Object.getOwnPropertySymbols(obj)
console.log(s) // [Symbol(name)]
//另外,Reflect.ownKeys()方法也可以返回一个数组,包括所有类型的键名,包括常规键名和 Symbol 键名。
let m = Reflect.ownKeys(obj)
console.log(m) //[Symbol(name)] 与Object.getOwnPropertySymbols()方法作用一样
Set和Map数据结构
Set
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。set本身是一个构造函数,用来生成 Set 数据结构。
// 表示一个无重复值的集合:
let data = new Set()
console.log(data)
// 添加元素
data.add(22) //添加一个数字
data.add('31') //添加一个字符串
data.add('31') //添加一个重复的字符串---重复的不会再添加显示到集合中
data.add([1, 3, 6]) //添加一个数组
console.log(data) // Set(3) {22, '31', Array(3)}
//删除元素
data.delete(22)
console.log(data) // Set(2) {'31', Array(3)}
console.log(data.size) // 2 ,集合长度/元素个数
console.log(data.has('31')) //true,校验某个值是否在集合中
在set集合中,键值对是相等的,键就是值,值也是键
console.log(data) // Set(2) {'31', Array(3)}
data.forEach(
(val, key) => {
console.log(val)
console.log(key)
}
)
//打印输出:
31
31
[1, 3, 6]
[1, 3, 6]
set转换为数组,数组去重
let set = new Set([1, 2, 3, 3, 3, 4])
console.log(set) // Set(4) {1, 2, 3, 4} , 去除重复的
let arr = [...set] // 利用扩展运算符
console.log(arr) // (4) [1, 2, 3, 4]
// 以上方法就实现的数组去重功能
set实现字符串去重
let set1 = new Set('aaabcdde')
console.log(set1) //Set(5) {'a', 'b', 'c', 'd', 'e'}
str = [...set1] // 利用扩展运算符转换为数组
console.log(str) //(5) ['a', 'b', 'c', 'd', 'e']
str = [...set1].join('') // 利用join方法拼接成字符串
console.log(str) //abcde
//此时我们就实现了字符串的去重
Map
Map类型是键值对的集合,键和值可以是任意类型
//添加元素 .set(key, value)方法,在Map中没有add方法
let map = new Map()
map.set('name', '张三')
map.set(undefined, 12)
map.set(666, 'm')
map.set('name', 'tom')
console.log(map) // Map(3) {'name' => 'tom', undefined => 12, 666 => 'm'}
//set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。键可以是任意类型
//获取元素 get方法
console.log(map.get('name')) // tom
//校验是否含有某个属性 has()方法
console.log(map.has('name')) //true
//删除元素 delete方法
map.delete(666)
console.log(map) // Map(2) {'name' => 'tom', undefined => 12}
//得到集合中元素的个数 size方法
console.log(map.size) // 2
//清空 clear
map.clear()
console.log(map) // Map(0) {size: 0}
数组的扩展方法
from()将伪数组转换为真数组
function add() {
console.log(arguments) //Arguments(4) [1, 3, 5, 9, callee: ƒ, Symbol(Symbol.iterator): ƒ]
//接收到的参数是一个伪数组
// ES5方法
let arr1 = [].slice.call(arguments)
console.log(arr1) //(4) [1, 3, 5, 9]
// ES6方法
let arr2 = Array.from(arguments)
console.log(arr2) //(4) [1, 3, 5, 9]
}
add(1, 3, 5, 9)
当我们获取一个ul中的li集合时,我们希望将它转换为一个数组
<ul>
<li>1</li>
<li>3</li>
<li>6</li>
<li>9</li>
</ul>
<script>
let lis = document.querySelectorAll('li')
console.log(lis) // NodeList(4) [li, li, li, li]
//这里得到的是一个NodeList集合
</script>
我们可以使用from方法将他转换为数组
console.log(Array.from(lis)) // (4) [li, li, li, li]
当然,我们还可以使用前面学习到的扩展运算符来转换
console.log([...lis]) // (4) [li, li, li, li]
当我们想获取li中的元素时,from可以有第二个参数
console.log(Array.from(lis, lis => lis.innerHTML)) // (4) ['1', '3', '6', '9']
of方法
//of方法可以将一组任意类型的数据转换为一个数组
console.log(Array.of(2, 6, 'andy', [11, 22], { id: 18 })) // (5) [2, 6, 'andy', Array(2), {…}]
copyWithin()方法
数组实例的
copyWithin()
方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
/*
它接受三个参数。
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
*/
console.log([1, 2, 3, 4, 5].copyWithin(0, 3)) // (5) [4, 5, 3, 4, 5]
//表示从索引号为0(1)的位置开始替换为索引号为3及其以后的数据(4,5)
console.log([1, 2, 3, 4, 5].copyWithin(1, 3)) // (5) [1, 4, 5, 4, 5]
//表示从索引号为1(2)的位置开始替换为索引号为3及其以后的数据(4,5)
console.log([1, 2, 3, 4, 5].copyWithin(0, 3, 4)) // (5) [4, 2, 3, 4, 5]
//表示从索引号为0(1)的位置开始替换为索引号为3及其以后的数据但是只读取到索引号为4(5)的位置
find()和findIndex()
console.log([2, 5, -10, 6, -7, -5, 3].find(n => n < 0)) // 10
// find 是找出数组中第一个符合条件的成员
console.log([2, 5, -10, 6, -7, -5, 3].findIndex(n => n < 0)) // 2
// findIndex 是找出数组中第一个符合条件的成员的索引号
keys()和values()和entries()
ES6 提供三个新的方法——
entries()
,keys()
和values()
——用于遍历数组。它们都返回一个遍历器对象,可以用for...of
循环进行遍历,唯一的区别是keys()
是对键名的遍历、values()
是对键值的遍历,entries()
是对键值对的遍历。
let arr = ['a', 'b', 7, 3]
console.log(arr)
/*(4) ['a', 'b', 7, 3] //这是一个数组
0: "a"
1: "b"
2: 7
3: 3 //键值对形式,前面的一列数字就是键名,后面一列是键值
length: 4
[[Prototype]]: Array(0)
*/
for (let index of arr.keys()) {
console.log(index)
} // keys() 对键名的遍历
/*
1
2
3
4
*/
for (let ele of arr.values()) {
console.log(ele)
} // values() 对键值的遍历
/*
a
b
7
3
*/
for (let [index, ele] of arr.entries()) {
console.log(index, ele)
} // entries() 对键值对的遍历
/*
0 'a'
1 'b'
2 7
3 3
*/
include()
include()方法返回一个布尔值,表示某个数组是否包含给定的值,判断某个元素是否在数组中
//ES5中我们使用indexOf()方法返回首个被找到的元素在数组中的索引位置; 若没有找到则返回 -1
console.log([1, 2, 3,].indexOf(3)) // 2
console.log([1, 2, 3,].indexOf(7)) // -1
//则有
console.log([1, 2, 3,].indexOf(3) === 2) // true , 以此来表示在元素中有该值存在
ES6给我们提供了include()方法
console.log([1, 2, 3].includes(3)) // true
console.log([1, 2, 3].includes('3')) // false
迭代器Iterator
迭代器是一个接口,能快捷的访问数据,通过Symbol.iterator来创建迭代器,通过迭代器的next()方法获得迭代之后的结果。其实遍历器就是一个用于遍历数据结构的指针
let arr = ['one', 'two', 'three']
//创建迭代器
let iterator = arr[Symbol.iterator]()
console.log(iterator) // Array Iterator {}
console.log(iterator.next()) // {value: 'one', done: false}
console.log(iterator.next()) // {value: 'two', done: false}
console.log(iterator.next()) // {value: 'three', done: false}
console.log(iterator.next()) // {value: undefined, done: true}
/*
Iterator 的遍历过程是这样的:
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束(false:未结束,true:结束)。
*/
生成器Generator
Generator函数可以通过yield关键字将函数挂起(让函数的执行暂停在某一阶段),为改变执行流提供了可能,也为异步编程提供了方案
Generator函数的function后面紧跟一个*,在函数内部使用yield表达式将函数挂起
function* func() {
console.log(111)
yield 'hello'
yield 'world'
console.log(222)
}
func()
//Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,即遍历器对象,让我们在下面打印看看
console.log(func()) // func {<suspended>} ---> 遍历器对象
let fn = func() //这里我们将遍历器对象赋给fn
//下一步,我们必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行
fn.next() // 111
//当我们第一次调用next方法时,Generator 函数开始执行,遇到yield表达式就暂停后面的操作(停在yield 'hello'),并把紧跟yield后面的值作为返回对象value的值
fn.next()
//这是我们第二次调用next方法,Generator 函数接着上次停止的地方执行(从yield 'hello'往后执行),在这之间没有任何操作,所有我们会执行到下一个yield表达式(yield 'world'),停在这个地方不再继续执行,并返回对象value的值
fn.next() // 222
//第三次调用next方法,Generator 函数继续执行(执行yield 'world'后面的语句),因此可以打印输出222,此时函数执行完毕,后面也没有yield表达式,所以返回的value值是undefined,done的属性变为true,函数执行完毕
//让我们我们打印验证一下
console.log(fn.next()) // {value: 'hello', done: false}
console.log(fn.next()) // {value: 'world', done: false}
console.log(fn.next()) // {value: undefined, done: true}
Generator 函数从会上次yield
表达式停下的地方,一直执行到return
语句(如果没有return
语句,就执行到函数结束)。next
方法返回的对象的value
属性,就是紧跟在return
语句后面的表达式的值(如果没有return
语句,则value
属性的值为undefined
),done
属性的值true
,表示遍历已经结束。
Generator 与 Iterator 接口的关系
任意一个对象的Symbol.iterator
方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。
//我们定义这样一个对象,我们可以看到在对象在是没有Symbol.iterator方法的,并且用扩展运算符遍历对象会报错
let myIterable = {
name: 'andy',
age: 18
}
console.log(myIterable)
/*
{name: 'andy', age: 18}
age: 18
name: "andy"
[[Prototype]]: Object
*/
console.log([...myIterable]) // TypeError: myIterable is not iterable
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator
属性,从而使得该对象具有 Iterator 接口
//我们给对象添加一个Symbol.iterator属性,并且把Generator 函数赋值给Symbol.iterator属性,此时对象就具有 Iterator 接口
let myIterable = {
name: 'andy',
age: 18
}
console.log(myIterable)
/*
{name: 'andy', age: 18}
age: 18
name: "andy"
Symbol(Symbol.iterator): ƒ* ()
[[Prototype]]: Object
*/
myIterable[Symbol.iterator] = function* () {
yield 'andy';
yield 18;
};
console.log([...myIterable]) // ['andy', 18] 这样我们就可以用扩展运算符遍历这个对象
next方法的参数
function* add() {
console.log('start') // start
let x = yield '2'
console.log('one:' + x) // one:20
let y = yield x
console.log('total:' + y) // total:30
return x + y
}
let fn = add()
//在未使用next方法之前函数不会执行
console.log(fn.next()) // {value: '2', done: false}
//第一次调用next方法,函数开始执行,打印输出start,然后遇到yield,函数暂停执行,并返回yield后面的表达式作为value的值
console.log(fn.next(20)) // {value: 20, done: false}
//第二次调用next方法函数从上次暂停的地方开始执行,因为next携带了参数(20),它就会将上一次也就是让函数暂停的yield表达式的值设为这个参数(20),我们用x接收了这个表达式的值,所有x就是20,我们接着来执行函数打印 'one:' + x 会得到 one:20 ,然后我们的函数就执行到 let y = yield x ,在这里我们就又遇到yield了,函数暂停执行,返回yield后表达式的值给value,这里yield后面跟的是 x 因此它返回的值就是 20 ,所以我们 console.log(fn.next(20)) 会得到 {value: 20, done: false}
console.log(fn.next(30)) // {value: 50, done: true}
//第三次调用接着执行,并再次把携带的参数(30)赋给上次的yield表达式,因此 y = 30,函数继续执行,一直执行到return语句,next返回的对象的value的属性值就是紧跟在return后面表达式的值,done的值变为true,遍历结束
for...of 循环
for...of
循环可以自动遍历 Generator 函数运行时生成的Iterator
对象,且此时不再需要调用next
方法。
function* func() {
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for (let v of func()) {
console.log(v)
} // 1 2 3 4
//上面代码使用for...of循环,依次显示 4个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的5,不包括在for...of循环之中。
原生的 JavaScript 对象没有遍历接口,无法使用for...of
循环,通过 Generator 函数为它加上这个接口,就可以用了。
let obj = {
name: '小马哥',
age: 18
}
//给对象添加一个Symbol.iterator属性,并且把Generator 函数赋值给Symbol.iterator属性,使对象具有 Iterator 接口
obj[Symbol.iterator] = objectEntries
function* objectEntries(obj) {
//获取对象所有的key以数组形式保存起来,['name', 'age']
let propKeys = Object.keys(obj) // propKeys中是['name', 'age']
for (let propkey of propKeys) { // 利用for...of把键(name和age)从propKeys中遍历出来,propKey分别是name和age
yield [propkey, obj[propkey]] // 把obj中的值对应给到相应的键,obj[name]就是obj中name属性的值,把它组成一个数组[name,obj[name]],即[name,小马哥],遍历完成之后就是[name,小马哥] [age,18]
}
}
//在上面的操作中我们一个给这个原生对象通过 Generator 函数objectEntries为它加上遍历器接口,就可以用for...of遍历了。除了for...of循环以外,扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口
for (let [key, value] of objectEntries(obj)) {
// console.log([key, value])
/*
['name', '小马哥']
['age', 18]
*/
// console.log(key)
/*
name
age
*/
// console.log(value)
/*
小马哥
18
*/
console.log(`${key}: ${value}`)
/*
name: 小马哥
age: 18
*/
//在for...of中我们可以以任何想要的形式遍历出对象中的元素
}
//扩展运算符
console.log([...objectEntries(obj)])
/*
(2) [Array(2), Array(2)]
0: (2) ['name', '小马哥']
1: (2) ['age', 18]
length: 2
[[Prototype]]: Array(0)
*/
//Array.from 方法
console.log(Array.from(objectEntries(obj)))
/*
(2) [Array(2), Array(2)]
0: (2) ['name', '小马哥']
1: (2) ['age', 18]
length: 2
[[Prototype]]: Array(0)
*/
// 解构赋值
let [x, y] = objectEntries(obj)
console.log(x) // ['name', '小马哥']
console.log(y) // ['age', 18]
应用-异步操作同步化
当我们在初次进入某个页面时,会首先显示等待页面,然后数据加载完成,再隐藏这个等待页面
function loadUI() {
console.log('加载loading……页面')
}
function getData() {
setTimeout(() => {
console.log('数据加载完成')
}, 1000);
}
// 在这个获取数据函数中,我们模拟获取数据需要一定的时间,模拟一个异步操作
function hideUI() {
console.log('隐藏loading……页面')
}
loadUI()
getData()
hideUI()
//我们想要的效果是在数据加载完成后隐藏等待页面,但由于loadUI和hideUI是同步任务,getData是异步任务,因此如果我们直接执行的话,就会导致同步任务执行完再执行异步任务,也就是说当我们看到loading页面但是数据还没加载完毕,但是这个loading页面以及被隐藏了
/*执行结果就是:
加载loading……页面
隐藏loading……页面
数据加载完成 ----> 在前面两个同步任务完成后才会执行这个
*/
因此我们就需要创建一个生成器,使用Generator函数让异步操作同步化
function loadUI() {
console.log('加载loading……页面')
}
function getData() {
setTimeout(() => {
console.log('数据加载完成')
Iterator.next()
}, 1000);
}
function hideUI() {
console.log('隐藏loading……页面')
}
function* load() {
loadUI()
yield getData()
hideUI()
}
//当我们在一个生成器函数中调用这些函数时,他们并不会直接执行
let Iterator = load()
//调用生成器函数,函数不会执行,会返回一个遍历器对象
Iterator.next()
//当我们第一次调用next时,生成器函数开始从头执行,loadUI()函数执行,然后遇到yield挂载函数,函数会停到getData(),函数不再继续执行,但是getData()会正常执行只是不会再往下一步执行,巧妙地是我们在getData()函数中的最后一步再次调用next,此时hideUI()函数开始调用执行,至此就实现了我们想要的效果
补充一点,当我们函数执行时遇到关键字yield时他会挂载到当前yield表达式,如果这个表达式是一个值,他会返回到对象中作为value的值,如果这个表达式是一个操作他返回的对象的value值是undefined,但这个表达式会执行,等待下一次next调用再往后执行
function* func() {
console.log(111)
yield console.log('hello')
yield 6
console.log(222)
}
let fn = func()
fn.next() // 111 hello
//第一次调用函数从头执行,输出两条语句,意味着函数挂载在yield console.log('hello')这条语句时,yield后面的操作是被执行了的
fn.next()
//第二次调用,函数继续执行,然后挂载到yield 6,因此没有任何输出
fn.next() // 222
//第三次调用,函数执行yield 6后面的语句,即console.log(222),因此打印出222
让我们打印看看每次调用next他会给对象中的value返回什么值
console.log(fn.next()) //{value: undefined, done: false}
//第一次调用他会挂载在yield console.log('hello')并返回yield后的值,此处yield后面是一个操作表达式,因此返回的是undefined
console.log(fn.next()) //{value: 6, done: false}
//第二次调用挂载在yield 6,返回对象中value的值为6
console.log(fn.next()) //{value: undefined, done: true}
//第三次调用函数直接执行完,没有返回的value值,done的属性变为true表示函数执行完毕
Promise对象
Promise
对象有以下两个特点。
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
Promise对象的基本使用
ES6 规定,
Promise
对象是一个构造函数,用来生成Promise
实例。Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
let pro = new Promise(function (resolve, reject) {
//模拟从服务器获取数据,执行异步操作
/* //获取成功返回的数据:
let res = {
code: 200,
data: {
name: '小马哥',
age: 18
}
} */
//获取失败返回的数据:
let res = {
code: 201,
error: '获取数据失败'
}
setTimeout(() => {
if (res.code === 200) {
resolve(res.data)
} else {
reject(res.error)
}
}, 1000);
})
pro.then((value) => {
console.log(value); //这里的value就是获取数据成功resolve返回的res.data
}, (error) => {
console.log(error) //这里的error就是获取数据失败reject返回的res.error
})
//Promise封装一个定时器函数
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise success')
}, ms);
})
}
console.log(timeout()) //Promise {<pending>} 调用timeout(ms)其实就是把new的promise对象返回出来
timeout(2000).then((val) => {
console.log(val)
})
then()方法
采用链式的then
,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise
对象(即有异步操作),这时后一个回调函数,就会等待该Promise
对象的状态发生变化,才会被调用
//当我们打印上述pro.then()
console.log(pro.then((value) => {
console.log(value);
}, (error) => {
console.log(error)
})) // Promise {<pending>}
//我们发现打印输出的仍然是一个promise对象,也就意味着它可以继续调用then方法,这就是链式编程
catch()方法
catch()
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数
pro.then((value) => {
console.log(value);
}, (error) => {
console.log(error)
})
//上面的pro.then()也可以改写为这样:
pro.then((value) => {
console.log(value);
}).then(null, (error) => {
console.log(error)
})
//它也等价于用catch()方法捕获运行中抛出的错误
pro.then((value) => {
console.log(value);
}).catch((error) => {
console.log(error)
})
//这种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数
all()方法
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
let Promise1 = new Promise((resolve, reject) => { })
let Promise2 = new Promise((resolve, reject) => { })
let Promise3 = new Promise((resolve, reject) => { })
let Promise4 = Promise.all([Promise1, Promise2, Promise3])
Promise4.then((data) => {
console.log(data) // 三个都成功promise4才算成功,才能走到这里
}).catch((error) => {
console.log(error) // 否则有一个失败就整个失败了,往这走
})
race()方法
Promise.race()
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
let p = Promise.race([p1, p2, p3]);
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
function requestImg(imgSrc) {
return new Promise((resolve, reject) => {
let img = new Image()
img.onload = function () {
resolve(img)
}
img.src = imgSrc
})
}
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('图片请求超时'))
}, 3000);
})
}
let url = 'https://img2.baidu.com/it/u=1395980100,2999837177&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=675'
Promise.race([requestImg(url), timeout()]).then((data) => {
document.body.appendChild(data)
}).catch((err) => {
console.log(err)
})
//在上述函数中requestImg和timeout两个函数分别返回两个Promise实例,在Promise.race([requestImg(url), timeout()])中这两个函数谁先执行完,也就是说谁的状态先改变就把谁的返回值传递给Promise.race([requestImg(url), timeout()])的回调函数,若requestImg先完成则返回的是resolve那么Promise.race([requestImg(url), timeout()])就会执行.then((data) => {document.body.appendChild(data)}),若一直请求不到图片,三秒钟之后timeout就会执行,返回reject,那么函数就会通过catch捕捉到他返回的错误(简单来说就是竞争执行,谁先执行完相应调用那个回调函数,成功是resolve就会调用.then。失败则是reject调用.catch)
async函数
看文档去吧,,,没听懂