Symbol类型
之前学习的时候只知道Symbol
是声明独一无二的数据,即使作为相同的属性也不会冲突。今天来加深了解一下Symbol
Symbol
常应用在定义不需要对外操作和访问的属性,也不能与其他值进行运算
声明方式
let v = Symbol(v0)
v0传与不传的区别就是在控制台中两个Symbol()
不利于区分。如果声明多个Symbol
v0的值一样这些Symbol
不会相等。let v = Symbol.for(v0)
如果已经存在v0就等于v0,不存在就创建。如果声明多个Symbol
v0的值一样则这些Symbol
会相等。
成为独一无二的属性key,以及在对象中使用Symbol时必须放在[]
内:
1 | //1 |
调用Symbol
类型为key
的属性不能用.
只能用[]
:
1 | obj[a] |
Symbol.isConcatSpreadable
用于设置concat
方法是否打平展开参数对象,用法: arr/obj[Symbol.isConcatSpreadable]=false/true
,默认为undefined
。下面target
表示修改此属性后的数组和对象
对于数组,为
false
或非真值后,arr.concat(target)
将target
整个数组作为元素连接到arr
末尾对于伪数组(有
length
,其他键为非负整数的对象),为true
或真值后arr.concat(target)
将target
内索引键对应的值都作为元素连接到arr
末尾其他类型对象如
Set
、普通对象
、函数,为false
或非真值后arr.concat(target)
,target
会被忽略
相关特性
遍历
常规的for-in
、for-of
、Object.keys(obj)
遍历取不到Symbol
创建的属性:
Object.getOwnPropertySymbols(obj)
返回一个数组,拿到指定对象中所有Symbol
属性的key
Reflect.ownKeys(obj)
返回一个数组,拿到指定对象中包括Symbol
类型的所有属性的key
使用JSON.stringify()
将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外
iterator
迭代器for...of
循环内部就是使用iterator
接口。数组本身内置此接口:arr[Symbol.iterator]方法
,返回的对象中next()
依次查看数据,next
返回的对象中done
为true表示查找到了所有数据,对象内部没有iterator接口,需要自己定义,才能使用
for...of
遍历:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21let obj = {
name: 'qxj',
age: 18,
[Symbol.iterator]() {
let _this = this
let index = 0
let keys = Object.keys(_this)
let len = keys.length
return {
next() {
return {
value: _this[keys[index++]],
done: index > len
}
}
}
}
}
for(let v of obj) {
console.log(v); //'qxj' 18
}
Set类型
- 创建用
new Set()
,还可以在创建时传入数组—new Set(arr)
,会将数组中的元素作为set集合的数据 也可以接受具有iterable接口的其他数据结构作为参数(Array
、Map
和Set
,有interable接口的数据都可用for...of
、forEach
遍历) - 添加数据用
add()
- 查看数据长度用
size
属性 - set类型的数据会过滤掉重复的内容,可用于数组去重。不同数据类型不会过滤(内部通过
Object.is()
判断元素是否相等,判断+0和-0的时候是用===
) has()
查看数据是否存在,delete()
删除指定数据,clear()
清空所有数据keys/values()
获取set数据内容的key/value, 直接遍历set数据,默认就是得到value- 可以用
for of
或者forEach
遍历 - set类型内数据是对象时,可以配合
forEach
遍历和修改删除,单独用add和delete不好匹配(引用类型)
- 可以用
可利用set的特性实现数组去重(数组
和set
可以互相转换):
1 | new Set(arr) //Array=>set |
Map类型
创建
new Map()
new Map([[key,value],...])
set()
方法添加数据map.set(key,value)
map.
get(key)
获取对应value
map中size,delete(),clear(),遍历都与set相同
1 | 数据存储优先考虑用map,数据唯一性用set |
Proxy对象代理
var monitor = new Proxy(obj,{代理操作})
修改monitor会反应给原obj, 用户看不见obj只会对monitor操作 ,对monitor
操作即可触发代理。
代理操作
target
为原obj,key
为读取/用户设置的属性,value
为设置时用户赋的值
拦截对象属性的读取:
1
2
3get(target,key){return target[key].replace(str1,str2)}
//可将对象属性值中的str1替换为str2
//get不会影响原obj拦截对象属性的设置:
1
2
3
4set(target,key,value){
if (key === str) target[key] = value
}
//当设置时修改的属性为str,就允许此次设置拦截
key in obj
操作1
2
3
4has(target,key){
if(key===str) return target[key]
}
//只允许用户用`in`成功判断str这个属性拦截属性删除
1
2
3
4deleteProperty(target,key){
if(key.indexOf('_')>-1) delete target[key]
}
//只允许删除带_的属性拦截
Object.getOwnPropertySymbols
,Object.getOwnPropertyNames(返回自身对象的所有属性的数组集合)
,Object.keys(返回自身对象的可枚举属性的数组集合)
这些方法1
2
3
4ownKeys(target){
return Object.keys(target).filter(item=>item!=str)
}
//以上方法不能获取到属性为str的数据拦截属性定义操作,优先级在
set
、get
之后1
defineProperty(target,key,attribute) {}
Reflect
优化提取了Object对象的方法,将用 老Object方法 报错的情况,改为返回false。确保对象的原生行为能够正常进行
1 | //老写法 |
代理操作的方法在Reflect中名称跟作用相同,在Proxy上有的方法,在Reflect就一定有
直接
Reflect.方法(原obj,key,value)
,将处理后的结果返回。常用
Proxy
代理拦截,用Reflect
在内部完成赋值。将条件判断与业务代码进一步分离以下若
receiver
参数存在,代理方法中的this
会指向它。不传则指向原对象Reflect.has(obj, key)
判断对象是否有指定key
Reflect.get(obj, prop, reciever)
返回对象指定的属性值
1
2
3
4
5
6
7
8
9let obj = {
a: 1, b: 2, c: 3,
get add() {
return this.a + this.b //这里的this为reciever参数对象
}
}
console.log(Reflect.get(obj, 'add', {
a: 3, b: 3
})) //6Reflect.set(obj, prop, value, receiver)
设置对象的指定属性为value
1
2
3
4
5
6
7
8
9
10
11
12let obj = {
a: 1, b: 2, c: 3,
set add(val) {
return this.c = this.a + this.b + val
}
}
let obj2 = {
a: 3, b: 3, c: 0
}
//obj.add = 2 //未传reciever或直接修改则obj.c为5,代理对象指向原对象
console.log(Reflect.set(obj, 'add', 2, obj2)) //true
console.log(obj2.c, obj.c) //8 3Reflect.set
与Proxy.set
联合使用,并且传入receiver
,则会触发定义属性操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//因为Proxy.set()中的receiver是Proxy的实例,而Reflect.set一旦传入receiver,
//就会将属性赋值到receiver上面,所以就会触发defineProperty
let obj={
name:"chen"
}
let handler={
set(target,key,value,receiver){
console.log("Proxy拦截赋值操作")
//Reflect完成赋值操作
Reflect.set(target,key,value,receiver)
},
defineProperty(target,key,attribute){
console.log("Proxy拦截定义属性操作")
//Reflect完成定义属性操作
Reflect.defineProperty(target,key,attribute)
}
}
let proxy=new Proxy(obj,handler)
proxy.name = "ya"
//print: Proxy拦截赋值操作
//print: Proxy拦截定义属性操作Reflect.deleteProperty(obj, key)
删除指定对象的属性Reflect.construct(target, args)
根据构造函数target和参数创建对象,args
没有则传[]
1
2
3
4//老方法
let p = new Person('qxj')
//新方法
let p = Reflect.construct(Person, ['qxj'])Reflect.getPrototypeOf(obj)
获取对象的原型Reflect.setPrototypeOf(obj, newProto)
设置对象的原型Reflect.ownKeys(obj))
拿到对象的所有属性Reflect.apply(func, target(this), args)
继承目标对象的指定方法并传参
1
2
3
4
5
6
7let arr = [1,2,3,5,31,16,2]
//老写法
let big = Math.max.apply(Math, arr)
let bigType = Object.prototype.toString.call(big)
//新写法
let big = Reflect.apply(Math.max, Math, arr)
let bigType = Reflect.apply(Object.prototype.toString, big, [])
Generator
也是解决异步编程的方式,很适合进行状态的循环展示,请求长轮询
基本使用
1 | //长轮询 |
next()
每次调用next()得到下一个yield定义的返回值(iterator对象),直到获取结束(或者return)yield
定义阶段返回值函数的
function
后加*
async语法糖: 与上面的写法效果相同,需要安装插件
不加
*
,在function
之前加async
使用
await
代替yield
yield
后面的异步任务执行完后,需要手动执行 next 方法,才能接着执行 yield 下面的回调操作,await
后面的异步任务执行完后,可以自动执行 await 下面的回调操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14function reqFun() {
return new Promise(resolve=>{
//模拟请求操作,resolve传递异步请求结果res
setTimeOut((res)=>resolve(res), 2000)
})
}
async function asyncReq() {
for(let i = 0; i < len; i++) {
//循环每次等reqFunc中异步的resolve执行完毕
//并每次将reqFun中请求得到并传给resolve的res传递到response才会继续向下执行
//将异步操作以同步写法实现
let response = await reqFun()
}
}
Generator定义iterator接口
1 | const obj = { |
Promise
Promise.resolve(arg)
, arg可以是同步值,一个promise对象,或一个thenable类型的对象,可以将他们都解析成为Promise对象。在then
中return
值相当于Promise.resolve(值)
- 若是同步值或不传参,会直接返回Resolved状态的Promise对象,有参数会传递到
then
的resolve
中 - 若是promise对象,直接返回这个Promise对象
- 若是thenable对象, 会将其转为Promise对象并立即调用其
then
方法。Promise对象的状态会由thenable对象的then方法决定
- 若是同步值或不传参,会直接返回Resolved状态的Promise对象,有参数会传递到
Promise.reject(arg)
返回Reject状态的Promise对象 在then
中throw
值相当于Promise.resolve(值)
。参数类型与Promise.resoleve
相似
async await
- await后面不论是普通函数还是async函数(即await后跟普通值或promise)都会被放入微任务队列,猜测await后跟的如果不是promise, 则会被await内部包装成promise实例 ,执行顺序不变
小知识
之前学习ES6未注意到的点
解构赋值时遇到变量与属性名不同时,可以通过
:xx
1
2const obj = {a: 1, b: 2}
const { a:aa } = obj //aa = 1, a not defined
- 本文作者: MR-QXJ
- 本文链接: https://mr-qxj.github.io/2020/11/30/语言/ES6/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!