8月最后一篇文章,9月你好
set数据结构和map数据结构
set数据结构
-
特点
- 表示一组数据的集合,类似数组
- 成员值没有重复(用于去重)
- set本身是一个构造函数,可以接收一个参数(数组或者类数组)
-
set实例的属性
- Set.prototype.constructor:构造函数,默认就是Set函数
- Set.prototype.size:返回实例的成员总数
-
set实例的函数
- Set.prototype.add(value):添加一个值,返回Set结构本
- 添加值时使用(===)判断是否有该值,没有则添加
- NaN使用严格相等时不等的,但是在set函数中它们是相等的
- Set.prototype.delete(value):删除某个值,返回布尔值
- Set.prototype.has(value):返回布尔值,表示是否是成员
- Set.prototype.clear():清除所有成员,无返回值
- Set.prototype.add(value):添加一个值,返回Set结构本
let set = new Set();
set.add('1');
set.add(1);
set.add('1');
set.add(NaN);
set.add(NaN);
console.log(set);// {"1", 1, NaN}
-
set的常见用法
- 单一数组去重
-
- 在set函数中传入数组
- 多个数组合并去重
- 使用扩展运算符合并数组后传入set函数
- set与数组的转换
let arr1 = [1,2,3,3,2];
let arr2 = [4,5,6,6,4];
console.log(new Set(arr1)); // {1,2,3}
console.log(new Set([...arr1,...arr2])); // {1,2,3,4,5,6}
// 数组转换成set
let arr3 = [1,2,3];
let set = new Set(arr3);
// set转换成数组
let set = new Set();
set.add('1');
set.add(1);
let arr = Array.from(set);
// 或者
let arr = [...set]
// 所以数组去重的完整实现
let arr1 = [1,2,3,3,2];
arr1 = Array.from(new Set(arr1)); // [1,2,3]
-
set的遍历
- forEach
- 因为set中没有索引,所以索引返回的也是值
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach
通过keys()/values()/entries()函数获得的对象都是遍历器对象Iterator,然后通过for...of循环可以获取每一项的值;因为Set实例的键和值是相等的,所以keys()函数和values()函数实际返回的是相同的值
let set = new Set(['x','y','z']);
set.forEach((item,index) => {
console.log(item,index);
})
// x x
// y y
// z z
for(let item of set.keys()){
console.log(item);
}
// x
// y
// z
for(let item of set.values()){
console.log(item);
}
// x
// y
// z
for(let item of set.entries()){
console.log(item);
}
// ['x','x']
// ['y','y']
// ['z','z']
Map数据结构
与对象字面量类似,本质是一种键值对的组合
-
特点
- Map的键可以是任意类型的值
- 传统的对象字面量的键只能是字符串(非字符串类型的值会强制类型转换成字符串)
- Map本身是一个构造函数
- 可以接收一个数组作为参数
- Map数据结构中键都必须具有唯一性
- 对同一个键进行多次赋值,那么后面的值会覆盖前面的值
- 如果Map实例的键是原生数据类型,则采用严格相等判断是否为同一个键
- 对于Number类型数据,+0和-0严格相等
- NaN与NaN不严格相等,Map会将其视为一个相同的键
- 字符串'true'与Boolean类型true不严格相等,是两个不同的键
- undefined与null是两个不同的键
- Map实例的键是引用数据类型,则判断对象是否为同一个引用、是否占据同一个内存地址
- Map的键可以是任意类型的值
const element = document.getElementById('app');
// 传统对象
const obj={};
obj[element] = 'dom';
console.log(obj); // {[object HTMLDivElement]:'dom'};
// map对象
const mapObj = new Map();
mapObj.set(element,'dom');
console.log(mapObj); // {div#app => 'first'}
// 接收参数
const map = new Map([['name','yangf']]);
console.log(map); // Map {'name'=>'yangf'}
// 引用类型判断是否是同一个引用、同一个内存地址
const map = new Map();
map.set([0],'1');
map.set([0],'2');
console.log(map); // Map {[0]=>'1',[0]=>'2'}
let arr = [1];
map.set(arr,'1');
map.set(arr,'2');
console.log(map); // Map {[0]=>'1',[0]=>'2',[1]=>'2'}
-
Map实例的属性
- size属性:返回Map结构的成员总数
-
Map实例的函数
- set(key, value)
- 函数设置键名key对应的键值为value
- 函数返回的是当前Map对象,
- 函数可以采用链式调用的写法
- get(key)
- 函数读取key对应的键值
- 找不到key,返回“undefined”
- has(key)
- 函数返回一个布尔值,表示某个键是否在当前Map对象中
- delete(key):函数删除某个键,返回“true”;如果删除失败,返回“false”
- clear():函数清除所有成员,没有返回值
- set(key, value)
-
Map的遍历
- forEach
- 第一个参数表示的是值,第二个参数表示的是键
- keys():返回的键的集合
- values():返回的值的集合
- entries():返回键值对的集合
- 这些集合都是Iterator的实例,可以通过for...of进行遍历
- forEach
const map = new Map();
map.set('name','yangf');
map.set('age',18);
map.forEach((item,key) =>{
console.log(item,key);
})
// yangf name
// 18 age
for(let item of map.keys()){
console.log(item);
}
// name
// age
for(let item of map.values()){
console.log(item);
}
// yangf
// 18
for(let item of map.entries()){
console.log(item);
}
// ['name','yangf']
Proxy概述
proxy意为代理器,用于改变对象的默认行为,实际就是在访问对象之前加了一层拦截,在拦截中,我们可以增加自定义的行为
-
基本语法
-
- Proxy是一个构造函数,接收两个参数,target为目标对象,handler用来定义拦截的行为
const proxy = new Proxy(target,handler);
-
- 通过Proxy构造函数可以生成实例proxy,任何对proxy实例的属性的访问都会自动转发至target对象上,若针对访问的行为配置自定义的handler对象
- 外界通过proxy访问target对象的属性时,都会执行handler对象自定义的拦截操作
const person = {
name:'yangf',
age:'18'
};
// 定义一个包含get函数的配置对象,对目标对象的属性进行读取操作
const hanlder = {
get:function(target,propKey,receiver){
console.log(target,propKey,receiver)
return target[propKey];
},
set:function(target, propKey, value, receiver){
console.log(target, propKey, value, receiver)
return target[propKey] = value
},
has:function(target, propKey){
console.log('执行了has操作');
return true
},
deleteProperty:function(target, propKey){
console.log('执行了delete操作');
return true
}
}
const p = new Proxy(person,hanlder);
// 执行p.name,即调用Proxy实例的name属性时;会触发get()函数
console.log(p.name)
// {name: 'yangf', age: '18'} 'name' Proxy {name: 'yangf', age: '18'}
// yangf
// 如果直接对目标对象进行访问不会触发拦截
console.log(person.name)
// yangf
-
特点
-
- 必须通过代理实例访问才能使拦截行为生效
- 如果配置对象为空,则代表不对目标对象进行拦截,直接访问目标对象
- 配置对象不能为null,否则会抛出异常
Proxy实例函数
-
Proxy实例支持的总共13种函数
-
- get(target, propKey, receiver)
-
- 拦截对象属性的读取操作,target表示的是目标对象,propKey表示的是读取的属性值,receiver表示的是配置对象
- set(target, propKey, value, receiver)
-
- 拦截对象属性的写操作,即设置属性值,value表示将要设置的属性的
// 在上面例子的基础上设置年龄
p.age = '20';
// {name: 'yangf', age: '18'} 'age' 20 Proxy {name: 'yangf', age: '18'}Object {age: "18" name: "yangf"} 'age' 20 Proxy对象
console.log(person.age)
// 20
-
- has(target, propKey)
-
- 返回一个布尔值
console.log('name' in p);
// 执行了has操作
// true
-
- deleteProperty(target, propKey)
-
- 返回一个布尔值
console.log(delete p['name']);
-
- ownKeys(target)
-
- 拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环等操作,其中target表示的是获取对象自身所有的属性名
- getOwnPropertyDescriptor(target, propKey)
-
- 拦截Object.getOwnPropertyDescriptor(proxy, propKey)操作,返回属性的属性描述符构成的对象,其中target表示目标对象,propKey表示需要获取属性描述符集合的属性
- defineProperty(target, propKey, propDesc)
-
- 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy,propDescs)操作,返回一个布尔值,propDesc表示的是属性描述符对象
- preventExtensions(target)
-
- 拦截Object.preventExtensions(proxy)操作,返回一个布尔值,表示的是让一个对象变得不可扩展,不能再增加新的属性,其中target表示目标对象
- getPrototypeOf(target)
-
- 拦截Object.getPrototypeOf(proxy)操作,返回一个对象,表示的是拦截获取对象原型属性,其中target表示目标对象
- isExtensible(target)
-
- 拦截Object.isExtensible(proxy),返回一个布尔值,表示对象是否是可扩展的,其中target表示目标对象
- setPrototypeOf(target, proto)
-
- 拦截Object.setPrototypeOf(proxy, proto)操作,返回一个布尔值,表示的是拦截设置对象的原型属性的行为,其中target表示目标对象,proto表示新的原型对象
- apply(target, object, args)
-
- 拦截Proxy实例作为函数调用的操作,例如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...),其中target表示目标对象,object表示函数的调用方,args表示函数调用传递的参数
- construct(target, args)
-
- 拦截Proxy实例作为构造函数调用的操作,例如new proxy(...args),其中target表示目标对象,args表示函数调用传递的参数。
如果在target中使用了this关键字,再通过Proxy处理后,this关键字指向的是Proxy的实例,而不是目标对象target
-
Proxy实例的基本使用
-
- 读取不存在对属性
-
- 一般返回undefined,可以通过Proxy的get()函数设置读取不存在的属性时抛出异常
- 读取负索引的值
-
- 数组的索引值是从0开始依次递增的,过Proxy的get()函数可以设置负索引对值
- 禁止访问私有属性
-
- 通过设置Proxy的get()函数来实现不想用户访问的属性
- Proxy访问属性的限制
-
- 如果属性同时为不可配置和不可写,那么在通过Proxy代理读取属性时,会抛出异常
const person = Object.defineProperties({},{
name:{
value:'yangf',
configurable:false,
writable:false
}
})
const proxy = new Proxy(person,{
get:function(target,propKey){
return 'yang'
}
});
console.log(proxy.name);
// Uncaught TypeError: 'get' on proxy: property 'name' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'yangf' but got 'yang')
Reflect概述
-
ES6为了操作对象而提供的新API
-
- 更合理地规划与Object对象相关的API
- x现在Object对象与Refect对象中会存在相同的处理函数。在未来的设计中,语言内部的函数将只会添加到Refect对象中
- 用一个单一的全局对象去存储这些函数,能够保持其他的JavaScript代码的整洁、干净
- 将一些命令式的操作符如delete、in等使用函数来替代,可以让代码更好维护,更容易向下兼容,同时也避免出现更多的保留字
// 传统写法
'assign' in Object // true
// 新写法
Reflect.has(Object,'assign');// true
-
- 修改Object对象的某些函数的返回结果,可以让其变得更合理,使得代码更好维护
/** 如果一个对象obj是不能扩展的
*那么在调用Object.defineProperty(obj, name, desc)时,会抛出一个异常
*因此在传统的写法中,我们需要通过try...catch处理
*而使用Refect.defineProperty(obj, name, desc)时
*返回的是“false”,新的写法就可以通过if...else实现
**/
// 传统写法
try{
Object.defineProperty(obj, name, desc);
// sucess
}catch(e){
// error
}
// 新写法
if(Reflect.defineProperty(obj, name, desc)){
// sucess
}else {
// error
}
-
- Refect对象的函数与Proxy对象的函数一一对应,不管Proxy对象怎么修改默认行为,总可以在Refect对象上获取默认行为
-
Reflect特点
-
- Reflect不是一个构造函数,只提供静态函数
-
Reflect提供的13个静态函数
如果没有特殊说明:
target表示的是目标函数
thisArg表示的是执行target函数时的this对象
args表示的是参数列表
-
- Reflect.apply(target, thisArg, args),通过指定的参数列表执行target函数
-
- 等同于执行Function.prototype.apply.call(target, thisArg, args)
// 查找数组的最大元素
const arr = [1,2,3,5];
// ES6写法
let max = Reflect.apply(Math.max,null,arr);
console.log(max);//7
// ES5写法
max = Math.max.apply(null,arr);
console.log(max);//7
max = Function.prototype.apply.call(Math.max, null, arr);
console.log(max);//7
// 截取字符串
const str = 'hello world';
// ES6写法
let newStr = Reflect.apply(String.prototype.slice,str,[2,5]);
console.log(newStr);//'llo '
newStr = str.slice(2,5);
console.log(newStr);//'llo '
newStr = String.prototype.slice.apply.call(str,[2,5]);
console.log(newStr);//'llo '
-
- Reflect.construct(target, args [, newTarget]);执行构造函数
-
- 等同于执行new target(...args)
- target表示的是构造函数
- newTarget是选填的参数
-
- 如果增加了该参数,则表示将newTarget作为新的构造函数
- 如果没有增加该参数,则仍然使用第一个参数target作为构造函
- Reflect.defineProperty(target, propKey, attributes);为对象定义属性
-
- 等同于执行Object.defineProperty()
- target表示的是定义属性的目标对象
- propKey表示的是新增的属性名
- attributes表示的是属性描述符对象集
- Reflect.deleteProperty(target, propKey);删除对象属性
-
- 等同于deleteobj[propKey]
- target表示的是待删除属性的对象
- propKey表示的是待删除的属性
注意:
- 使用Reflect.deleteProperty()函数删除对象的属性时,只要对象是可扩展的,删除任何属性都会返回为“true”,即使该属性不存在
- 被冻结的对象(使用Object.freeze())不能被删除
- 使用delete删除不存在的属性也不会抛出异常,同样的delete不能删除被冻结的对象
-
- Reflect.get(target, propKey, receiver);获取对象属性
-
- 等同于执行target[propKey]
- target表示的是获取属性的对象
- propKey表示的是获取的属性
- receiver表示函数中this绑定的对象
- Reflect.getOwnPropertyDescriptor(target, propKey);得到指定属性的描述对象
-
- 等同于执行Object.getOwnPropertyDescriptor()
- target表示的是待操作的对象
- propKey表示的是指定的属性
- Reflect.getPrototypeOf(target);读取对象的_proto_属性
-
- 等同于执行Object.getPrototypeOf(obj)
- Reflect.has(target, propKey);判断属性是否在对象中
-
- 等同于执行propKey in target
- propKey表示的是判断的属性
- Reflect.isExtensible(target);判断对象是否可以扩展
-
- 等同于执行Object.isExtensible()函数
- Reflect.ownKeys(target);获取对象的所有属性,包括Symbol属性
-
- 等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和
- Reflect.preventExtensions(target);让对象变的不可扩展
-
- 等同于执行Object.preventExtensions()
- Reflect.set(target, propKey, value, receiver);设置某个属性
-
- 等同于执行target[propKey] =value
- propKey表示的是待设置的属性
- value表示的是设置属性的具体值
- receiver表示函数中this绑定的对象,this将指向新对象
const obj = {
_name:'',
set name(name){
console.log('this',this);
this._name = name;
},
get name(){
return this._name;
},
age:22
}
let obj1 = Reflect.set(obj,'age',24);
let obj2 = Reflect.set(obj,'name','yangf');//this {_name: '', age: 24}
console.log(obj);// {_name: 'yangf', age: 24}
console.log(obj1);//true
console.log(obj2);//true
let receiver = {test:'test'};
let obj3 = Reflect.set(obj,'name','xiaoxixi',receiver);// this {test: 'test'}
console.log(obj);//{_name: 'yangf', age: 24}
console.log(obj3);//true
console.log(receiver);//{test: 'test', _name: 'xiaoxixi'}
-
- Reflect.setPrototypeOf(target, newProto);设置对象的原型
-
- 等同于执行Object.setPrototypeOf(target, newProto)
- newProto表示的是新的原型对象。
Reflect与Proxy
- Reflect对象的函数和Proxy对象的函数一一对应
let obj = {
name:'yangf'
};
const proxy = new Proxy(obj,{
get(target,prop){
return Reflect.get(target,prop);
}
})
- 观察者模式
-
- 一个目标对象管理所有依赖于它的观察者对象
-
- 即当目标对象的状态有变更时,会主动向所有观察者发出通知
// 定义一个目标对象
let obj = {
name:'yangf'
};
// 定义观察者队列
const allObservers = new Set();
const observer1 = function (prop,value){
console.log(`${value}观察者1很开心`)
};
const observer2 = function (prop,value){
console.log(`${value}观察者2似乎不是很开心`)
};
// 为目标对象添加观察者
allObservers.add(observer1);
allObservers.add(observer2);
function set(target,prop,value){
// 修改函数的属性
const result = Reflect.set(target,prop,value);
// 执行通知函数,通知所有的观察者
result ? allObservers.forEach(fn =>fn(prop,value)):'';
return result;
}
// 通过Proxy生成目标对象的代理函数
const observable = (target) => new Proxy(target,{set});
const proxy = observable(obj);
proxy.name='xiaoxixi';