9.js 实现VUE响应式监听数据

199 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情

vue3(vue3使用的是Proxy实现监听数据变化)

什么是 Proxy?

Proxy 这个词翻译过来就是“代理”,用在这里表示由它来“代理”某些操作。 Proxy 会在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

先来看下 proxy 的基本语法

const proxy = new Proxy(target, handler)
  • target :您要代理的原始对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
  • handler :一个对象,定义将拦截哪些操作以及如何重新定义拦截的操作

我们看一个简单的例子:

const person = {
    name: 'muyao',
    age: 27
};

const proxyPerson = new Proxy({}, {
  get: function(target, propKey) {
    return 35;
  }
});

proxy.name // 35
proxy.age // 35
proxy.sex // 35 不存在的属性同样起作用

person.name // muyao 原对象未改变

上面代码中,配置对象有一个get方法,用来拦截对目标对象属性的访问请求。get方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回35,所以访问任何属性都得到35

注意,Proxy 并没有改变原有对象 而是生成一个新的对象,要使得 Proxy 起作用,必须针对 Proxy 实例(上例是 proxyPerson)进行操作,而不是针对目标对象(上例是 person)进行操作

Proxy 支持的拦截操作一共 13 种:

  • get(target, propKey, receiver) :拦截对象属性的读取,比如 proxy.foo 和 proxy['foo']
  • set(target, propKey, value, receiver) :拦截对象属性的设置,比如 proxy.foo = v 或 proxy['foo'] = v, 返回一个布尔值。
  • has(target, propKey) :拦截 propKey in proxy 的操作,返回一个布尔值。
  • deleteProperty(target, propKey) :拦截 delete proxy[propKey] 的操作,返回一个布尔值。
  • ownKeys(target) :拦截 Object.getOwnPropertyNames(proxy)Object.getOwnPropertySymbols(proxy)Object.keys(proxy)for...in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名。
  • getOwnPropertyDescriptor(target, propKey) :拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
  • defineProperty(target, propKey, propDesc) :拦截Object.defineProperty(proxy, propKey, propDesc)Object.defineProperties(proxy, propDescs),返回一个布尔值。
  • preventExtensions(target) :拦截Object.preventExtensions(proxy),返回一个布尔值。
  • getPrototypeOf(target) :拦截 Object.getPrototypeOf(proxy),返回一个对象。
  • isExtensible(target) :拦截 Object.isExtensible(proxy),返回一个布尔值。
  • setPrototypeOf(target, proto) :拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。
  • apply(target, object, args) :拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)proxy.call(object, ...args)proxy.apply(...)
  • construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)

 具体实现:

//data
let state ={
    province:'',
    city:'',
    area:'',
}

//watch方法
let watcher = {
}
for(let key in state){
    watcher[key]=(res)=>{
        watcher[key+'_callback']=res
    }
}


const postParams  = new Proxy(state,{
    //获取值
    get(thisData,key){
        return thisData[key]
    },
    //修改值
    set(thisData,key,newValue){
        thisData[key]=newValue
        //放入watch方法
        if(watcher[key+'_callback'])watcher[key+'_callback'](newValue,thisData[key])
    }
})
watcher.province(res=>{
    console.log(res)
})


postParams.province=1//修改
let state ={
  province:'',
  city:'',
  area:'',
  data:{
    value:99
  }
}

// 设置对象
// valueData监听数据
// watcherValue:监听数据里面存在对象是需要传递watch
// watcherKey:监听数据里面存在对象是需要传递watch的key
// original:最初的数据
function setData(valueData,watcherValue=null,watcherKey=null,original=null){
  let watcher = {
  }
  if(watcherValue){
    watcher=watcherValue
  }
  //watch方法
  for(let key in valueData){
    // 是否对象
    if(valueData[key] instanceof Object){
      if(!original){
        // 最初对象赋值
        original=valueData;
      }
      let  childerObject=null;
      // watcher 是否存在
      if(watcherValue && watcherKey){
        childerObject =setData(valueData[key],watcherValue,watcherKey,original)
      }else{
        childerObject =setData(valueData[key],watcher,key,original)
      }
      childerData=childerObject.postParams
      childerWatcher=childerObject.watcher

      // 当前对象监听

      // 在原数据中加入初始值
      valueData['_'+key+'_original_']=valueData[key]
      // 将当前对象替换成Proxy对象
      valueData[key]=childerObject.postParams
      // 合并watch
      Object.assign(watcher,childerWatcher)
    }else{
      if(watcherValue && watcherKey){
        watcherValue[watcherKey]=(res)=>{
            watcher['_'+watcherKey+'_callback']=res
        }
      }else{
        watcher[key]=(res)=>{
            watcher['_'+key+'_callback']=res
        }
      }
     
    }
  }

  const postParams  = new Proxy(valueData,{
    //获取值
    get(thisData,key){
        return thisData[key]
    },
    //修改值
    set(thisData,key,newValue){
        //放入watch方法
        if(watcherValue && watcherKey){
        // 原数据
          let oddValueObject=Object.assign({},original[`_${watcherKey}_original_`])
          thisData[key]=newValue
          // 修改后的
          let newValueObject=original[`_${watcherKey}_original_`]
          if(watcherValue['_'+watcherKey+'_callback'])watcherValue['_'+watcherKey+'_callback'](newValueObject,oddValueObject)
        }else{
          let oddValue=thisData[key]
          thisData[key]=newValue
          if(watcher['_'+key+'_callback'])watcher['_'+key+'_callback'](newValue,oddValue)
        }
       
    }
  })
  return {
    postParams,watcher
  }
}




//使用
const {postParams,watcher}=setData(state)


watcher.province((newValue,oddValue)=>{
  console.log(newValue,oddValue)
})
watcher.data((newValue,oddValue)=>{
  console.log(newValue,oddValue)
})
setTimeout(()=>{
  postParams.province=9999
  postParams.data.value=9999
  
},1500)

image.png


vue2(vue2使用的是defineProperty监听每组数据里面的单个值变化)

具体讲解

 具体实现:

//data
const postParams = {
    province:'',
    city:'',
    area:'',
}

//watch方法
let watcher = {
}
 for(let key in postParams){
    let value = postParams[key]
    watcher[key]=(res)=>{
        watcher[key+'_callback']=res
    }
    //监听每一个数据变化
    Object.defineProperty(postParams,key,{
        get(){
           return value
        },
        set(newValue){
            value=newValue
            //放入watch
            if(watcher[key+'_callback'])watcher[key+'_callback'](newValue,value)
        }
    })
 }

watcher.province(res=>{
    console.log(res)
})

postParams.province=1//修改