js重难点精讲(ES三)

407 阅读12分钟

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():清除所有成员,无返回值
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():返回键值对的遍历器

通过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实例的键是引用数据类型,则判断对象是否为同一个引用、是否占据同一个内存地址
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():函数清除所有成员,没有返回值
  • Map的遍历

    • forEach
      • 第一个参数表示的是值,第二个参数表示的是键
    • keys():返回的键的集合
    • values():返回的值的集合
    • entries():返回键值对的集合
    • 这些集合都是Iterator的实例,可以通过for...of进行遍历
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表示的是待删除的属性

注意:

  1. 使用Reflect.deleteProperty()函数删除对象的属性时,只要对象是可扩展的,删除任何属性都会返回为“true”,即使该属性不存在
  1. 被冻结的对象(使用Object.freeze())不能被删除
  1. 使用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';