js css html

ES6的Proxy和Reflect

2022-05-12  本文已影响0人  咸鱼不咸_123

1、监听对象的操作

我们先来看一个需求:有一个对象,我们希望监听这个对象的属性被设置或获取的过程

但是这样做有什么缺点呢?

const obj={
  name:"wjy",
  age:20
}
// * 只监听了obj的name属性
// Object.defineProperty(obj,"name",{
//   get(){
//     console.log("name被访问了");
//   },
//   set(value){
//     console.log("name的值被修改了");
//   }
// })


// * 如果想监听obj的所有属性被监听

let objKeys=Object.keys(obj);
objKeys.forEach((key)=>{
  let value=obj[key];
  Object.defineProperty(obj,key,{
    get(){
      console.log(`obj的${key}被获取了`);
      return value;
    },
    set(v){
      value=v;
      console.log(`obj的${key}被修改了`);
    }
  })
})

obj.name="hyz";
console.log(obj.name);



console.log(obj.age);
obj.age=19;

2.Proxy基本使用

ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的。

2.1 Proxy的set和get捕获器

如果我们想要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap)

set和get分别对应的是函数类型:

const obj={
  name:"wjy",
  age:18
}

const objProxy=new Proxy(obj,{
  // * 有13种捕获器
  get:function(target,key){
    // target:侦听的对象
    console.log(`obj的${key}被获取了`,target);
    return target[key];
  },
  set:function(target,key,newValue){
    target[key]= newValue;
    console.log(`obj的${key}被修改了`,target);
  }

});//第一个参数是指要监听的哪个对象 第二个参数是捕获器
console.log(objProxy.name);
console.log(objProxy.age);

objProxy.name="hyz";
objProxy.age=20;

console.log(obj.name);
console.log(obj.age);

2.2 Proxy的所有捕获器

  1. handler.getPrototypeOf()

    • Object.getPrototypeOf()方法的捕捉器

      obj.__proto__//这个存在浏览器兼容性
      
  2. handler.setPrototypeOf()

    • Object.setPrototype()方法的捕捉器
  3. handler.isExtensible()【是否能扩展】

    • Object.isExtensible()方法的捕捉器

      //阻止一个对象扩展是,阻止一个对象添加新属性 密封、冻结后 都是不能进行扩展的。
      Object.preventExtensions(obj)
      
  4. handler.preventExtensions()

    • Object.preventExtensions()的捕捉器
  5. handler.getOwnPropertyDescriptor()

    • Object.getOwnPropertyDescriptor()的捕捉器
  6. handler.defineProperty()

    • Object.defineProperty()的捕捉器
  7. handler.ownKeys()

    • Object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕捉器。
  8. handler.has()

    • in操作符的捕捉器
  9. handler.get()

    • 属性读取操作的捕捉器
  10. hanlder.set()

    • 属性设置操作的捕捉
  11. handler.deleteProperty()

  1. handler.apply() 【用于函数对象】
  1. handler.constructor() 【用于函数对象】

2.3 Proxy的apply和construct捕捉器

function foo(){

}
// * 调用方式一
foo();
// * 调用方式二
foo.apply({},[])
// * 调用方式三
new foo()

const fooProxy=new Proxy(foo,{
  apply:function(target,thisArg,argArray){
    console.log("执行了apply操作");
    return target.apply(thisArg,argArray);
  },
  construct:function(target,argArray){
    console.log("执行了new调用");
    return new target(...argArray);
  }
})

fooProxy.apply({},["abc","def"])
new fooProxy("123")

3. Reflect

Reflect是ES6新增的一个API,它是一个对象,字面的意思是 反射

那么这个Reflect有什么用呢?

如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?

那么Object和Reflect对象之间的API关系,可以参考 MDN文档:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods

3.1 Reflect的常用的方法

Reflect常用的方法是和Proxy的捕获器一一对应的

  1. Reflect.getPrototypeOf(target)

    • 类似于Object.getPrototypeOf()
  2. Reflect.setPrototypeOf(target,prototype)

    • 类似于Object.setPrototypeOf(),设置对象原型的函数,返回一个Boolean,如果更新成功,则返回true。
  3. Reflect.isExtensible()

    • 类似于Object.isExtensible()
  4. Reflect.preventExtensions()

    • 类似于Object.preventExtensions()
  5. Reflect.getOwnPropertyDescriptor(target,propertyKey)

    • 类似于Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,否则返回undefined.
  6. Reflect.defineProperty(target,propertyKey,attributes)

    • 和Object.defineProperty()类似,如果设置成功就会返回true。
  7. Reflect.ownKeys(target)

    • 返回一个包含所有自身属性(不包含继承属性)的属性。(类似于Object.keys(),但不会受enumerable影响)。
  8. Reflect.has(target,propertyKey)

    • 判断对象上是否存在某个属性,和in 运算符的功能完全相同。
  9. Reflect.get(target,propertyKey,receiver)

    • 获取对象上某个属性的值,类似于target[name]
  10. Reflect.set(target,propertyKey,newValue,receiver)

    • 将值分配给属性的函数,返回一个Boolean,如果更新成功,则返回true.
  11. Reflect.deleteProperty(target,propertyKey)

    • 作为函数的delete操作符,相当于执行 delete target[name]
  12. Reflect.apply(target,thisArg,argArray)

    • 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply()功能类似
  13. Reflect.construct(target,argArray)

    • 相当于对构造函数执行new操作,相当于执行new target(...args)

[图片上传失败...(image-f59118-1652335687662)]

const obj={
  name:"wjy",
  age:18
}
// * 创建代理对象的目的,不再对原来的对象进行直接的操作。
const objProxy=new Proxy(obj,{
  get:function(target,key,receiver){
    return Reflect.get(target,key);
  },
  set:function(target,key,newValue,receiver){
    const result=Reflect.set(target,key,newValue);
    if(result){

    }else{
      
    }
  }
})

objProxy.name="kobe";
console.log(objProxy.name);

3.2 Receiver的作用

我们发现在使用getter、setter的时候有一个receiver的参数,它的作用是什么呢?

const obj={
  _name:"wjy",
  get name(){
    return this._name;
  },
  set name(value){
    this._name=value
  }
}

const objProxy=new Proxy(obj,{
  get:function(target,key,receiver){
    // * receiver其实是创建出来的代理对象,它就是上面objProxy
    console.log("get方法被访问-----------",key,receiver);
    // console.log(receiver==objProxy);//true
    return Reflect.get(target,key,receiver);
  },
  set:function(target,key,newValue,receiver){
    console.log("set方法被访问---------",key,receiver);
    Reflect.set(target,key,newValue,receiver)
  }
})
console.log(objProxy.name);//wjy
objProxy.name="hyz";
// console.log(objProxy.name);

3.3 Reflect的constructor()方法

Reflect.constructor(super,arguments,newTarget)

function Student(name,age){
  this.name=name;
  this.age=age;
}

function Teacher(){

}
const stu=new Student();
console.log(stu);
console.log(stu.__proto__==Student.prototype);//true


// * 要求: new 出来的Student,调用的依然是Student的构造函数,但是希望最后创建的类型是 Teacher类型,并且Student的this也是指向Teacher
// * 执行Student函数中的内容,但是创建出来对象是Teacher对象
const teacher=Reflect.construct(Student,["wjy",18],Teacher);
console.log(teacher);//Teacher { name: 'wjy', age: 18 }
console.log(teacher.__proto__==Teacher.prototype);//true

4. 响应式

我们先看一下响应式意味着什么?我们先来看一段代码

let m=100;


// * 当m发生变化时,这段代码会自动重新执行
// 一段代码
console.log(m);
console.log(m*2);
console.log(m**2);

m=200;

4.1 响应式函数的封装

// * 封装一个响应式的函数
let reactiveFns=[];
function watchFn(Fn){
  reactiveFns.push(Fn)
}

const obj={
  name:"wjy",
  age:18
}

watchFn(function (){
  const newName=obj.name;
  console.log("你好~何沅洲");
  console.log("Hello World");
  console.log(obj.name);
})

watchFn(function (){
  console.log(obj.name);
})
function bar(){
  console.log("普通的其他的函数");
  console.log("这个函数不需要有任何响应式");
}

obj.name="hyz";
 
reactiveFns.forEach(fn=>fn())

4.2 响应式依赖的收集

前面我们收集的依赖是放到一个数组中保存的,但是这里会存在数据管理的问题:

所以我们要设计了一个类,这个类用来管理某一个对象的某一个顺序的所有响应式函数。


// * 每个属性对应一个类
class Depend{
  constructor() {
    this.reactiveFns=[];
  }
  addDepend(reactiveFn){
    this.reactiveFns.push(reactiveFn)
  }
  notify(){
    this.reactiveFns.forEach(fn=>fn())
  }
}

// * 封装一个响应式的函数
const depend=new Depend();
function watchFn(Fn){
  depend.addDepend(Fn)
}

const obj={
  name:"wjy",
  age:18
}
 
watchFn(function (){
  const newName=obj.name;
  console.log("你好~何沅洲");
  console.log("Hello World");
  console.log(obj.name);
})

watchFn(function (){
  console.log(obj.name);
})
function bar(){
  console.log("普通的其他的函数");
  console.log("这个函数不需要有任何响应式");
}

obj.name="hyz";
 
depend.notify()

4.3 对象的依赖管理的实现


// * 每个属性对应一个类
class Depend{
  constructor() {
    this.reactiveFns=[];
  }
  addDepend(reactiveFn){
    this.reactiveFns.push(reactiveFn)
  }
  notify(){
    this.reactiveFns.forEach(fn=>fn())
  }
}

// * 封装一个响应式的函数
const depend=new Depend();
function watchFn(Fn){
  depend.addDepend(Fn)
}


// * 封装一个获取depend的函数
const targetMap=new WeakMap();
function getDepend(target,key){
  // * 根据target对象获取map的过程
  const map=targetMap.get(target);
  if(!map){//第一次在使用的需要去创建一个
    map=new Map();
    targetMap.set(target,map);
  }
  
  // * 根据key获取depend对象
  const depend=map.get(key);
  if(!depend){ //* 第一次可能没有depend
   depend=new Depend();
   map.set(key,depend);
  }
  return depend;
}
const obj={
  name:"wjy",
  age:18
}
 // * 监听对象的属性变化 : Proxy(vue3)和Object.defineProperty(vue2)
const objProxy=new Proxy(obj,{
  get:function(target,key,receiver){
    return Reflect.get(target,key,receiver);
  },
  set:function(target,key,newValue,receiver){
    Reflect.set(target,key,newValue,receiver);
    // depend.notify(); 
    const depend=getDepend(target,key);
    depend.notify();
  }
})
watchFn(function (){
  const newName=obj.name;
  console.log("你好~何沅洲");
  console.log("Hello World");
  console.log(obj.name);
})

watchFn(function (){
  console.log(obj.name);
})
function bar(){
  console.log("普通的其他的函数");
  console.log("这个函数不需要有任何响应式");
}

objProxy.name="hyz";
objProxy.name="tqy";
objProxy.name="lzy"
// depend.notify() 


// * 什么时候去收集封装依赖最合适呢
const info={
  address:"怀化"
}

// obj对象
// name depend
// age depend
const objMap=new Map();
objMap.set("name","nameDepend");
objMap.set("age","ageDepend");

// info对象
// address depend

// const infoMap=new Map();
// infoMap.set("address","addressDepend")


// const targetMap=new WeakMap();
// targetMap.set(obj,objMap);
// targetMap.set(info,infoMap);

// // * 如果obj.name发生变化了
// targetMap.get(obj).get("name");//* 去找到name的depend
// depend.notify();//* 然后去执行 name的depend

5.总结

Proxy和Reflect的使用和部分响应式原理的实现.png
上一篇 下一篇

猜你喜欢

热点阅读