ES6
var,let,const有哪些区别
在ES5中,顶层对象(在浏览器中是window
)的属性和全局变量是等价的,或者说全局变量会被挂载到window
对象中
变量提升
var
声明的变量存在变量提升,变量提升只提升变量声明,不提升变量赋值。而let
和const
不存在变量提升重复声明
var声明的变量可以被
重复声明
,后面声明的会覆盖前面声明的。而let
和const
声明的变量无法被重复声明
。作用域
var声明的变量只会产生
函数作用域
,不会产生块级作用域let用来声明一个
变量
,会产生一个块级作用域,const用来声明一个
常量
,也会产生一个块级作用域。
数组新增了哪些扩展
扩展运算符 …
扩展运算符的作用就是把数组变成一个序列
1 | console.log(...[1, 2, 3]) //等同于console.log(1,2,3) |
用来展开数组
1
Math.max(...arr)//求数组arr的最大值
用来合并,拷贝数组
拷贝数组进行的是
浅层次
的拷贝1
const arr = [...arr1,...arr2]
定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组
1
2
3
4
5
6
7
8
9
10let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]如果对没有 Iterator 接口的对象,使用扩展运算符,将会报错
1
2const obj = {a: 1, b: 2};
let arr = [...obj]; // TypeError: Cannot spread non-iterable object
构造函数Array的新增方法
关于构造函数,数组新增的方法有如下:
- Array.from()
- Array.of()
Array.from()
将两类对象转为真正的数组:类似数组的对象(伪数组
)和可迭代对象
(包括 ES6
新增的数据结构 Set
和 Map
)
1 | let arrayLike = { |
还可以接受第二个参数
,用来对每个元素进行处理,将处理后的值放入返回的数组
1 | Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9] |
Array.of()
用于将一组值,转换为数组
1 | Array.of(3, 11, 8) // [3,11,8] |
当参数只有一个的时候,实际上是指定数组的长度
参数个数不少于 2 个时,Array()
才会返回由参数组成的新数组
1 | Array.of() // [] |
新增方法
- find()、findIndex()
- fill()
- entries(),keys(),values()
- includes()
find,findIndex
find()
用于找出第一个符合条件的数组成员
参数是一个回调函数,接受三个参数依次为当前的值、当前的位置和原数组
1 | [1, 5, 10, 15].find(function(value, index, arr) { |
1 | findIndex`返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回`-1 |
fill
使用给定值,填充一个数组
1 | ['a', 'b', 'c'].fill(7) |
还可以接受第二个和第三个参数,用于指定填充的起始位置
和结束位置
,左闭右开
1 | ['a', 'b', 'c'].fill(7, 1, 2) |
注意,如果填充的类型为对象,则是浅拷贝,即个被填充的数据,使用的都是同一个对象
1 | ['a', 'b'].fill({name:'tom'}) |
includes
用于判断数组是否包含给定的值,相比indexOf
方法,优化了对NAN
的判断
1 | [1, 2, 3].includes(2) // true |
函数新增了哪些扩展
参数
ES6
允许为函数的参数设置默认值
1 | function log(x, y = 'World') { |
函数的形参是默认声明的,不能使用let
或const
再次声明
1 | function foo(x = 5) { |
解构赋值
过程中也可以给形参
添加默认值
1 | function foo({x, y = 5}) { |
属性
函数本质也是个对象,有许多属性
func.length
将返回没有指定
默认值
的参数个数,具体情况还得具体分析,感觉很鸡肋。func.name
如果把
匿名函数
赋值给一个变量,则name属性返回这个变量的名字1
2
3
4
5var f = function () {};
// ES5
f.name // ""
// ES6
f.name // "f"如果将一个
具名函数
赋值给一个变量,则name
属性都返回这个具名函数
原本的名字1
2const bar = function baz() {};
bar.name // "baz"bind
返回的函数,name
属性值会加上bound
前缀1
2function foo() {};
foo.bind({}).name // "bound foo"
作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域
等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的
下面例子中,y=x
会形成一个单独作用域,x
没有被定义,所以指向全局变量x
1 | let x = 1; |
严格模式
只要函数形参
使用了默认值
、解构赋值
、或者扩展运算符
,那么函数内部就不能显式设定为严格模式,否则会报错。所以说函数内部也不能随便开启严格模式。
1 | // 报错 |
箭头函数
形如:
1 | ()=>{} |
更适用于那些本来 需要
匿名函数
的地方,类似lambda
表达式,它和普通匿名函数
一样,它属于表达式函数
,不存在函数提升
只有一个
参数
的时候可以省略括号,只有一行代码且是return
语句,可以省略大括号
和return
关键字,如果返回的是一个对象,则需要加括号。1
2item => item.name //等同于(item)=>{ return item.name }
item => ({name:'tom'})没有自己的环境变量
this
,内部的this指向被定义的时候外层函数
的this,this指向和如何被调用无关,内部也没有arguments
对象没有自己的
原型对象
,所以不能当作构造函数
使用,不能用来创造实例。无法使用
apply
,call
,bind
等方法改变箭头函数内部的this
指向,但是可以调用
这些方法。不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数
对象新增了哪些扩展
属性的简写
ES6中,当对象键名与对应值名相等的时候,可以进行简写
1 | const baz = {foo:foo} |
方法也能够进行简写
1 | const o = { |
属性名表达式
eS6 允许字面量定义对象时,将表达式放在括号内,当作对象的属性。
1 | let lastWord = 'last word'; |
注意,属性名表达式
与属性名简写
,不能同时使用,会报错。
1 | // 报错 |
super关键字
this
关键字总是指向函数所在的当前对象,ES6 又新增了另一个类似的关键字super
,指向当前对象的原型对象
即super=this.__proto__
如何理解ES6新增Set、Map两种数据结构
Set
是一种叫做集合
的数据结构,Map
什么是集合?什么又是字典?
- 集合
是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合 - 字典
是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同
Set
Set
本身是一个构造函数,用来生成 Set 数据结构
1 | const s = new Set(); |
增删改查
Set
的实例关于增删改查的方法:
add()
向集合中添加元素,返回
Set
结构本身,所以可以链式调用
当添加实例中已经存在的元素,
set
不会进行处理添加,即会被去重
1
s.add(1).add(2).add(2); // 2只被添加了一次
delete()
删除某个值,返回一个
布尔值
,表示删除是否成功1
s.delete(1)
has()
返回一个
布尔值
,判断集合中是否存在某个元素1
s.has(2)
clear()
清除所有成员,没有返回值
1
s.clear()
遍历
关于遍历的方法,有如下:
- keys():返回
键名
的迭代器 - values():返回
键值
的迭代器 - entries():返回
键值对
的迭代器 - forEach():使用回调函数遍历每个成员
1 | let set = new Set(['red', 'green', 'blue']); |
forEach
1 | let set = new Set([1, 4, 9]); |
使用场景
扩展运算符和Set
结构相结合实现数组
或字符串
去重
1 | // 数组 |
Map
Map
类型是键值对的有序列表,而键和值都可以是任意类型
Map
本身是一个构造函数,用来生成 Map
数据结构
1 | const m = new Map() |
增删查改
Map
结构的实例针对增删改查有以下属性和操作方法:
size 属性
size
属性返回 键值对的个数1
2
3
4
5const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2set()
设置键名
key
对应的键值为value
,然后返回整个 Map 结构如果
key
已经有值,则键值会被更新
,否则就新生成该键同时返回的是当前
Map
对象,可采用链式写法
1
2
3
4
5
6const m = new Map();
m.set('edition', 6) // 键是字符串
m.set(262, 'standard') // 键是数值
m.set(undefined, 'nah') // 键是 undefined
m.set(1, 'a').set(2, 'b').set(3, 'c') // 链式操作get()
get
方法读取key
对应的键值,如果找不到key,返回undefined
1
2
3
4
5
6const m = new Map();
const hello = function() {console.log('hello');};
m.set(hello, 'Hello ES6!') // 键是函数
m.get(hello) // Hello ES6!has()
has
方法返回一个布尔值,表示某个键
是否在当前 Map 对象之中1
2
3
4
5
6
7
8
9
10const m = new Map();
m.set('edition', 6);
m.set(262, 'standard');
m.set(undefined, 'nah');
m.has('edition') // true
m.has('years') // false
m.has(262) // true
m.has(undefined) // truedelete()
1
2
3
4
5
6
7delete`方法删除某个键,返回`true`。如果删除失败,返回`false
const m = new Map();
m.set(undefined, 'nah');
m.has(undefined) // true
m.delete(undefined)
m.has(undefined) // false,删除后不再存在clear()
clear
方法清除所有成员,没有返回值1
2
3
4
5
6
7let map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2
map.clear()
map.size // 0
遍历
- keys():返回
键名
的迭代器 - values():返回
键值
的迭代器 - entries():返回
键值对
的迭代器 - forEach():遍历 Map 的所有成员
1 | //传入一个二维数组说是 |
你是怎么理解ES6中 Promise的
是什么
是异步编程
的一种解决方案
,比传统的解决方案—回调函数
,更加合理和更加强大
因为使用回调函数来解决异步编程问题,存在回调函数地狱问题
,即在回调函数中嵌套回调函数,这样就导致代码的可读性变得很差,代码也变得难以维护。
而使用promise
解决异步编程操作有如下优点:
链式操作
减低了编码难度- 代码可读性明显增强
下面我们来正式介绍promise:
状态
promise
对象仅有三种状态
pending
(进行中)fulfilled
(已成功)rejected
(已失败)
对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
一旦状态改变(从pending
变为fulfilled
和从pending
变为rejected
),就不会再变
实例方法
Promise
对象是一个构造函数,用来生成Promise
实例
1 | const promise = new Promise(function(resolve, reject) { |
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败“
Promise
构建出来的实例存在以下方法:
- then()
- catch()
- finally()
then
then
是实例状态发生改变时的回调函数,第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数
then
方法返回的是一个新的Promise
实例,也就是promise
能链式书写的原因。
catch
catch()
方法用于指定发生错误时的回调函数,本质就是在内部调用then(undefined,onRejected)
一般来说,使用catch
方法代替then()
第二个参数
finally
finally()
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作,内部其实本质就是在调用then(onFinally,onFinally)
1 | promise |
静态方法
Promise
构造函数存在以下方法:
- all()
- race()
- allSettled()
- resolve()
- reject()
手写一个Promise
为了帮助我们更深入的理解Promise,建议尝试自己手写一个Promise
参考资料:Day02-01.手写promise-核心功能-构造函数_哔哩哔哩_bilibili
任务目标
创建一个myPromise类,处理同步修改promise状态的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
state = PENDING//默认值
result
constructor(func) {
//func是创建promise实例的时候,传入的回调函数
//这个回调函数接受两个参数resolve,reject,我们在构造函数内部准备这两个方法
const resolve = (res) => {
//如果promise实例的状态还没有改变
if (this.state = PENDING) {
//那就改变它的状态为'fulfilled'
this.state = FULFILLED
//res是传入的值
this.result = res
}
}
const reject = (err) => {
//如果promise实例的状态还没有改变
if (this.state = PENDING) {
//那就改变它的状态为'rejected'
this.state = REJECTED
this.result = err
}
}
//因为用户传入的回调函数func本身也可能报错,所以我们用try-catch捕获一下
try {
//同步调用传入的回调函数,传入2个准备好的能改变promise实例状态的函数
//由用户决定如何改变promise实例的状态
func(resolve, reject)
} catch (err) {
reject(err)
}
}
//暂时只考虑同步修改promise状态
then(onFulFilled, onRejected) {
if (this.state == FULFILLED) {
onFulFilled(this.result)
} else if (this.state == REJECTED) {
onRejected(this.result)
}
}
}举例测试
1
2
3
4
5
6const p = new myPromise((resolve,reject)=>{
//同步修改promise状态
resolve(1)
})
//因为p的状态在刚被创建的时候就改变了,then方法传入的回调函数也会马上执行,拿到p实例的值
p.then(res=>{console.log(res)},err=>{console.log(err)})//立即输出1处理异步修改promise状态的情况
如果我们promise的状态是异步改变的,比如
1
2
3
4const p = new myPromise((resolve,reject)=>{
//1s后,再调用resolve方法,把promise实例的状态修改为'fulfilled'
setTimeout(()=>{resolve(1)},1000)
})创建完实例p后我们同步调用
then
方法(调用then方法本身是同步的,创建promise对象,执行构造函数本身也是同步的)1
p.then(res=>{console.log(res)},err=>{console.log(err)})
因为
p
的状态还没有改变,所以我们还不能执行传入then
方法的回调函数,那我们还怎么确保1s
后p
状态改变,这个回调函数会被调用呢?我们先把回调函数存储到一个数组
#handler
中,当p
状态改变的时候再查找这个数组,执行对应的回调函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
state = PENDING//默认值
result
#handler = []
constructor(func) {
//func是创建promise实例的时候,传入的回调函数
//这个回调函数接受两个参数resolve,reject,我们在构造函数内部准备这两个方法
const resolve = (res) => {
//如果promise实例的状态还没有改变
if (this.state = PENDING) {
//那就改变它的状态为'fulfilled'
this.state = FULFILLED
//res是传入的值
this.result = res
//resolve函数执行之时,就是promise状态改变之时,就是回调函数该被调用的时候
this.#handler.forEach(item => { item.onFulFilled(this.result) })
}
}
const reject = (err) => {
//如果promise实例的状态还没有改变
if (this.state = PENDING) {
//那就改变它的状态为'rejected'
this.state = REJECTED
this.result = err
//reject函数执行之时,就是promise状态改变之时,就是回调函数该被调用的时候
this.#handler.forEach(item => { item.onRejected(this.result) })
}
}
//同步调用传入的回调函数,传入2个准备好的能改变promise实例状态的函数
//因为用户传入的回调函数func本身也可能报错,所以我们用try-catch捕获一下
try {
func(resolve, reject)
} catch (err) {
reject(err)
}
}
//暂时只考虑同步修改promise状态
then(onFulFilled, onRejected) {
if (this.state == FULFILLED) {
//执行传入的回调函数,并把值暴露出去
onFulFilled(this.result)
} else if (this.state == REJECTED) {
//执行传入的回调函数,并把值暴露出去
onRejected(this.result)
} else {
//如果promise实例的状态还未改变,就先把传入的回调函数存储起来,也可以理解为放入任务队列中
this.#handler.push({
onFulFilled
, onRejected
})
}
}
}
const p = new myPromise((resolve, reject) => {
setTimeout(() => { resolve(123) }, 1000)
})
p.then(res => { console.log(res) }, err => { console.log(err) })//1s后输出123处理then的返回值
因为then方法是支持
链式调用
的,意味着then方法的返回值也是一个promise对象,如何确定返回的promise实例的状态和值呢?then方法把传入的
回调函数
的返回值包装成promise
对象后返回我们先修改一下then方法,确保能返回一个
promise
对象,这样书写并不会改变代码原有的功能,因为构造函数中的代码是立即执行的,原来的代码也是同步执行的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17then(onFulFilled, onRejected) {
return new myPromise((resolve, reject) => {
//原来的代码顶部------------
if (this.state == FULFILLED) {
onFulFilled(this.result)
} else if (this.state == REJECTED) {
onRejected(this.result)
} else {
//如果promise实例的状态还未改变
this.#handler.push({
onFulFilled
, onRejected
})
}
//原来的代码底部---------------
})
}然后我们再思考返回的
promise
对象的状态与值如何确定,代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42//传入的resolve,reject,是被用来修改then方法返回的myPromise实例的状态的
//这个wrap方法到底做了什么?
//1.调用传入的回调函数,把promise对象的值(this.result)传入
//2.分析回调函数的返回值,调用传入的,能够改变then方法返回的promise对象状态的函数
//修改then方法返回对象的状态
wrap(func, resolve, reject) {
//wrap中this的指向等于then的this指向,指向同一个promise对象
//因为调用传入的onFulFilled/onRejected函数可能会报错,所以使用try-catch捕获
try {
//需要拿到返回值,来确定then函数返回的promise实例的状态
const x = func(this.result)
//如果返回的是一个myPromise对象,它的状态可能是同步改变或者异步改变
//因为我们已经解决了异步回调的情况的,所以也能通过then方法拿到它的result,
if (x instanceof myPromise) {
//wow,相当于由一个promise对象的状态来确定另一个promise的状态
x.then(res => { resolve(res) }, err => { reject(err) })
} else {
//如果返回值不是myPromise对象,那就简单了,直接resolve
resolve(x)
}
}
catch (err) {
//如果报错直接返回状态为rejeted的promise实例
reject(err)
}
}
then(onFulFilled, onRejected) {
return new myPromise((resolve, reject) => {
if (this.state == FULFILLED) {
this.wrap(onFulFilled, resolve, reject)
} else if (this.state == REJECTED) {
this.wrap(onRejected, resolve, reject)
} else {
//如果promise实例的状态还未改变
this.#handler.push({
onFulFilled:()=>{this.wrap(onFulFilled, resolve, reject)}
, onRejected:()=>{this.wrap(onRejected, resolve, reject)}
})
}
})
}举例测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//创建一个1s后状态改变的promise对象
const p = new myPromise((resolve, reject) => {
setTimeout(() => { resolve(1) }, 1000)
})
//p调用then方法,因为p状态未确定,回调函数被wrap包装,然后push到#handler
//虽然传入的回调函数没有立马被调用,但是then方法已经返回了一个状态未改变的promise对象
//第一个then方法返回的对象状态未改变,第2次then的调用执行了,但是没动静
//1s后,因为在构造函数内调用resolve(1)方法,p的状态改变,遍历执行#handler中的待执行的回调函数,执行被包装的回调函数
//执行被包装的函数,也就是第一个then传入的第一个回调函数,输出1,等待2s后,调用resolve(2)方法
//此时第一个then方法返回的对象状态被改变,于是第二个then传入的回调函数也被触发,输出2
p.then(res => {
console.log(res)
return new myPromise((resolve) => {
setTimeout(() => {
resolve(2)
}, 1000);
})
}, err => { console.log(err) })
//第一次调用then方法立马返回一个promise对象,但是由于对象的状态还未确定,1s后才会打印出2
.then(res => { console.log(res) }, err => { console.log(err) })结果就是1s后输出1,3秒后输出2。
实现
catch
方法1
2
3catch(onRejected) {
this.then(undefined, onRejected)
}实现
finally
1
2
3finally(onFinally) {
this.then(onFinally, onFinally)
}实现
resolve
静态方法1
2
3
4
5
6
7
8
9static resolve(res) {
//如果本来就是myPromise实例,则直接返回
if (res instanceof myPromise) {
return res
}
return new myPromise((resolve, reject) => {
resolve(res)
})
}实现
reject
静态方法1
2
3
4
5
6
7
8static reject(err) {
if (res instanceof myPromise) {
return res
}
return new myPromise((resolve, reject) => {
reject(err)
})
}实现
race
静态方法传入一个数组,返回最先兑现的
promise
,无论是resolve
还是reject
,只取一个值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15static race(arr) {
//传入的必须是一个数组
return new myPromise((resolve, reject) => {
if (!(arr instanceof Array)) {
reject(new TypeError('arguments is not iterable'))
}
//如果数组长度为0
if (arr.length == 0) {
resolve([])
}
//对每个元素都调用then方法(同时),不是myPromise对象则先包裹
//再等待它们状态的改变,来改变最终返回的promise的状态
arr.forEach(i => { myPromise.resolve(i).then(res => { resolve(res) }, err => { reject(err) }) })
})
}实现
all
静态方法要求传入的数组中的所有 myPromise对象的状态都
resolve
后,再resolve(//包含所有对象值的数组)如果任意一个对象reject了,则reject这个对象的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22static all(arr) {
return new myPromise((resolve, reject) => {
//如果传入的不是数组,报错
if (!(arr instanceof Array)) {
reject(new TypeError('arguments is not iterable'))
}
if (arr.length == 0) {
resolve([])
}
const result = []
let count = 0
//要求返回的数组中元素的排列顺序就是传入的顺序
arr.forEach((i, index) => {
myPromise.resolve(i).then(res => {
result[index] = res
if (++count == arr.length) {
resolve(result)
}
}, err => { reject(err) })
})
})
}实现
any
静态方法就是和all相反。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22static any(arr) {
return new myPromise((resolve, reject) => {
if (!(arr instanceof Array)) {
reject(new TypeError('arguments is not iterable'))
}
if (arr.length == 0) {
reject(new AggregateError([], 'All promises were rejected'))
}
const errs = []
let count = 0
arr.forEach((i, index) => {
myPromise.resolve(i).then(res => {
resolve(res)
}, err => {
errs[index] = err
if (++count == arr.length) {
reject(new AggregateError(errs, 'All promises were rejected'))
}
})
})
})
}实现
allSettled
方法传入Promise都变成已敲定,即可获取兑现的结果
结果数组[{status: ‘fulfilled’, value: 1}, {status: ‘rejected’, value: 3)]
结果数组的顺序,和传入的Promise数组的顺序一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25static allSettled(arr) {
return new myPromise((resolve, reject) => {
if (!(arr instanceof Array)) {
reject(new TypeError('arguments is not iterable'))
}
if (arr.length == 0) {
resolve([])
}
const result = []//一个对象数组
let count = 0
arr.forEach((i, index) => {
myPromise.resolve(i).then(res => {
result[index] = { state: FULFILLED, value: res }
if (++count == arr.length) {
resolve(result)
}
}, err => {
result[index] = { state: REJECTED, reason: err }
if (++count == arr.length) {
resolve(result)
}
})
})
})
}
使用场景
使用Promise.all()
合并多个请求,只需设置一个loading
即可
1 | function initLoad(){ |
通过race
可以设置图片请求超时时间
1 | //请求某个图片资源 |
for in 和 for of的区别
for in
for in
语句以任意顺序迭代一个对象
的除Symbol
以外的可枚举属性
,包括继承的
可枚举属性
一个直接创建的对象是非常常见的,它的原型是Object.prototype
,上面的属性都是不可枚举的,所以我们使用for in
遍历这种对象时,只会得到它的自己的所有可枚举属性。
for of
for of语句可以遍历可迭代对象
,包括Array,Map,Set,string,TypedArray,arguments对象等等,遍历的是值
,继承
而来的无法遍历;保证迭代顺序,而一个直接创建的对象是不可迭代
的,无法直接使用for of
来遍历。
可迭代对象
即满足了迭代协议
的对象,需要存在一个名为[Symbol.iterator]
的方法,这个方法返回
一个迭代器对象
直接创建的对象不是可迭代对象,因为没有这个方法,但是我们也可以手动添加这个方法,让它变成可迭代对象
1 | { name:'1',age:'21',[Symbol.iterator]:function(){return 迭代器对象}} |
迭代器对象
顾名思义,迭代器对象的一个普通对象,必须实现一个名为 next()
的方法,该方法返回一个包含两个属性
的对象
:value 和 done。value
是迭代的当前值,done
是一个布尔值,表示是否已经到达了迭代的末尾。
那么改如何实现一个迭代器对象
呢?
手动创建
根据迭代器对象的定义,我们可以尝试手动创建一个
迭代器对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const obj = {
[Symbol.iterator]() {
const arr = [1, 2, 3]
let index = 0
//返回一个迭代器对象
return {
next() {
if (index < arr.length) {
return { done: false, value: arr[index++] }
}
return { done: true, value: 'end' }
}
}
}
}借助生成器函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function* generator() {
yield '1'
yield '2'
//如果生成器函数中有return,return后迭代也就终止了,无论后面还有没有yield
return
yield '3'
}
const obj = {
[Symbol.iterator]() {
//直接返回一个迭代器对象
return generator()
}
}
for(item of obj){
console.log(item)//依次输出1,2
}
当使用 for...of
遍历一个可迭代对象时,它实际上调用了该对象的 [Symbol.iterator]
方法,并根据迭代器提供的值进行迭代。
区别
遍历的东西不同
for in 遍历的对象的
属性
,for of遍历的对象的值
遍历的范围不同
for in遍历的是
对象自己的可枚举属性
以及它继承的所有可枚举属性
for of遍历的是
对象自己的属性的值
使用的范围不同
for in
可以用于遍历任何对象
,而for of
只能用来遍历可迭代对象
你是怎么理解ES6中 Generator的?使用场景?
是什么
Generator 函数是 ES6 提供的一种异步编程
解决方案,语法行为与传统函数完全不同
回顾下上文提到的解决异步的手段:
- 回调函数
- promise
执行 Generator
函数会返回一个迭代器对象
,可以依次遍历 Generator
函数内部的每一个状态
形式上,Generator
函数是一个普通函数,但是有两个特征:
function
关键字与函数名之间有一个星号函数体内部使用
yield(屈服,'叶儿得')
表达式,定义不同的内部状态1
2
3
4
5function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
使用
Generator
函数会返回一个迭代器对象
,即具有Symbol.iterator
属性,并且返回给自己
1 | function* gen(){ |
通过yield
关键字可以暂停generator
函数返回的迭代器对象
的状态
1 | function* helloWorldGenerator() { |
1 | console.log(hw.next())//输出1 输出{ value: 'hello', done: false } |
done
用来判断是否存在下个状态,value
对应状态值
再举个例子
1 | function* helloWorldGenerator2() { |
1 | console.log(hw.next())//输出1 输出{ value: 'hello', done: false } |
yield
表达式本身没有返回值,或者说总是返回undefined
1 | function* foo(x) { |
通过调用next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值
1 | var b = foo(5); |
正因为Generator
函数返回Iterator(迭代器)
对象,因此我们还可以通过for...of
进行遍历
1 | function* foo() { |
原生对象
没有遍历接口
,通过Generator
函数为它加上这个接口,就能使用for...of
进行遍历了,for...of
本质遍历的就是迭代器对象
。
1 | function* objectEntries(obj) { |
异步解决方案
回顾之前展开异步解决的方案:
- 回调函数
- Promise 对象
- generator 函数
- async/await
回调函数
1 | setTimeout(()=>{console.log(123)},1000) |
Promise
Promise
就是为了解决回调地狱
而产生的,将回调函数的嵌套
,改成链式调用
1 | readFile('/etc/fstab').then(data =>{ |
这种链式操作形式,使异步任务的两段执行更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强,Generator
就是用来解决这个问题的。
Generator
1 | function fetchUser(id) { |
虽然生成器
提供了处理异步代码的一种方式,但它的使用相对复杂
,还是不够简洁,于是就有了async
和await
async/await
async
函数本质上是构建在生成器
之上的语法糖
,它们内部实际上使用了 Promise,并且允许你以同步的方式编写异步代码,而不需要显式地处理迭代器
或手动调用 next()
。
1 | async function asyncFunc() { |
你是怎么理解ES6中Proxy的?使用场景?
是什么
Proxy
是一个构造函数,用于创建一个对象的代理
,从而拦截对该对象的基本操作。
1 | var proxy = new Proxy(target, handler) |
target
表示所要拦截的目标对象
(任何类型的对象,包括原生数组,函数,甚至另一个代理)
handler
是一个属性值
一般都是函数
的对象,各属性中的函数分别定义了在执行各种操作时代理目标对象的行为
难点就在于分析这个handler
,它可以包括多种拦截属性
,下面我们只介绍常见的几种:
- get(target,propKey,receiver):拦截对象属性的读取
- set(target,propKey,value,receiver):拦截对象属性的设置
- deleteProperty(target,propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值
handler
get()
get
接受三个参数,依次为目标对象、属性名和 proxy
实例本身,最后一个参数可选
用来监听对某个属性
查找
1 | var person = { |
注意:如果一个属性不可写(writable:false
),则 Proxy 不能修改该属性,否则会报错
1 | const target = Object.defineProperties({}, { |
set()
set
方法用来拦截对某个属性的赋值操作
,可以接受四个参数,依次为目标对象
、属性名
、属性值
和 Proxy
实例本身。
如果目标对象自身的某个属性,不可写(writable:false
),那么set
方法将不起作用
1 | const obj = {}; |
注意,严格模式
下,set
代理如果没有返回true
,就会报错
deleteProperty
deleteProperty
方法用于拦截delete
操作,如果这个方法抛出错误
或者返回false
,当前属性就无法被delete
命令删除
1 | var handler = { |
注意,目标对象
自身的不可配置(configurable:false
)属性,不能被deleteProperty
方法删除。
取消代理
1 | Proxy.revocable(target, handler); |
使用场景
Proxy
其功能非常类似于设计模式中的代理模式
,常用功能如下:。
拦截和监视外部对对象的操作
降低函数或类的复杂度
在复杂操作前对操作进行校验或对所需资源进行管理
使用
Proxy
实现观察者模式1
2
3
4
5
6
7
8
9
10
11
12
13const queuedObservers = new Set();
//添加一个订阅
const observe = fn => queuedObservers.add(fn);
//返回一个代理对象
const observable = obj => new Proxy(obj, {set});
//当数据改变,就调用所有的订阅方法
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
你是怎么理解ES6中Module的?使用场景?
为什么需要模块化
如果没有模块化,我们代码会怎样?
- 变量和方法不容易维护,容易污染
全局作用域
- 通过手动规定
script
标签的书写顺序来控制资源的加载顺序
- 依赖的环境主观逻辑偏重,代码较多就会比较复杂。
- 资源的
依赖关系
模糊,代码难以维护。
而模块化具有如下特点,能解决原生开发过程中的诸多问题
- 代码抽象
- 代码封装
- 代码复用
- 依赖管理
AMD
Asynchronous ModuleDefinition
(AMD),异步模块定义,采用异步方式
加载模块。所有依赖模块的语句,都定义在一个回调函数中,等到模块加载完成之后,这个回调函数才会运行
1 | /** main.js 入口文件/主模块 **/ |
CommonJs
CommonJS
是一套 nodejs
默认支持的模块规范,用于服务端。
其有如下特点:
- 所有代码都运行在
模块作用域
,不会污染全局作用域 - 模块是
同步加载
的,即只有加载完成,才能执行后面的操作 - 模块在首次执行后就会
缓存
,再次加载只返回缓存结果,如果想要再次执行,可清除缓存 require
返回的值是被输出的值的拷贝
,模块内部的变化也不会影响这个值
既然存在了AMD
以及CommonJs
机制,ES6
的Module
又有什么不一样?
ES6
在语言标准的层面上,实现了Module
,即模块功能,完全可以取代 CommonJS
和 AMD
规范,成为浏览器
和服务器
通用的模块解决方案。
CommonJS
和AMD
模块语法,导入导出的都是整个对象
,代码运行时
才能确定具体的依赖关系,比如具体使用了模块中的哪些变量。
ES6
设计思想是尽量的静态化
,使得编译时
就能确定模块的依赖关系,以及输入和输出的变量。
使用
具体使用方式可以参考本博客中nodejs
一文。