JS响应式原理的实现

86 阅读5分钟

什么是响应式

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

m有一个初始化的值,有一段代码使用了这个值,那么在m有一个新的值时,这段代码可以自动重新执行。

let m = 100 //当m变成200,希望下面的代码自动执行一次

//一段代码
console.log(m)
console.log(m * 2)
console.log(m ** 2)

m = 200

上面的这样一种可以自动响应数据变量的代码机制,我们就称之为是响应式的。

响应式函数的设计

我们一开始的思路是封装一个响应式函数,并且设计一个需要响应的数组,将所有响应式函数存储进入数组中。


let reactiveFans = []  //需要响应的数组,全部放入这个数组

//封装一个响应式的函数 watchFn
function watchFn(fn){ //需要响应式的函数传给watchFn,这个函数值作为参数
  reactiveFans.push(fn)
}

//对象的响应式
const obj = {
  name:'kitty',
  age:20
}



//当name发生变化,这些需要重新执行
// console.log(obj.name);  //100行代码
// console.log(obj.age);

//将上面那段代码放到函数里面

//这样子或者
// function foo(){
//   //100行代码
//   console.log(obj.name);  
//   console.log(obj.age);
//   console.log('Hello World');
// }
// watchFn(foo) 

//这样子
watchFn(function(){
  //100行代码
  console.log(obj.name);  
  console.log(obj.age);
  console.log('Hello World');
})



function bar(){
  console.log('这个函数不需要任何响应式');
}


obj.name = 'kobe'
reactiveFans.forEach(fn =>{
  fn()
})

但是这种设计从数据结构来考虑并不好。考虑替换掉数组。因为这个数组只能存name发生变化的函数,如果要存别的属性发生变化的函数就要用别的数组。

我们考虑定义一个类,当一个属性发生变化我们就重新new一个实例。

class Depend{
  constructor(){
    this.reactiveFans = []

  }
  addDepend(reactiveFn){
    this.reactiveFans.push(reactiveFn)
  }
  //遍历,外界只要调用这个方法
  notify(){
    this.reactiveFans.forEach(fn=>{
      fn()
    })
  }
}

const depend = new Depend()
//封装一个响应式的函数 watchFn
function watchFn(fn){ //需要响应式的函数传给watchFn,这个函数值作为参数
  depend.addDepend(fn)
}

//对象的响应式
const obj = {
  name:'kitty', //这个对应一个对象 dep对象
  age:20       //这个对应一个对象,它们都有各自的数组 dep对象
}


watchFn(function(){
  //100行代码
  console.log(obj.name);  
  console.log(obj.age);
  console.log('Hello World');
})


obj.name = 'kobe'
depend.notify()

如果要自动监听对象呢?这就需要使用Proxy了。

class Depend{
  constructor(){
    this.reactiveFans = []

  }
  addDepend(reactiveFn){
    this.reactiveFans.push(reactiveFn)
  }
  //遍历,外界只要调用这个方法
  notify(){
    this.reactiveFans.forEach(fn=>{
      fn()
    })
  }
}

const depend = new Depend()
//封装一个响应式的函数 watchFn
function watchFn(fn){ //需要响应式的函数传给watchFn,这个函数值作为参数
  depend.addDepend(fn)
}

//对象的响应式
const obj = {
  name:'kitty', 
  age:20       
}

//自动监听的方法:Proxy Object.definepropery

const objProxy = new Proxy(obj,{
  get(target,key,receiver){
    return Reflect.get(target,key,receiver)
  },
  set(target,key,newValue,receiver){
    Reflect.set(target,key,newValue,receiver)
    depend.notify()
  }
})


watchFn(function(){
  //100行代码
  console.log(objProxy.name);  
  console.log(obj.age);
  console.log('Hello World');
})

watchFn(function(){
  console.log(objProxy.name,"demo ------------");
})

objProxy.name = 'kobe'
//写在set里面,就不需要每次都notify()了
objProxy.name = "curry"

如果要监听多个对象,多个属性呢?也就是要做依赖收集管理。我们得用到一个数据结构WeakMap

image.png

class Depend{
  constructor(){
    this.reactiveFns = []
  }
  addDepend(reactiveFn){
    this.reactiveFns.push(reactiveFn)
  }
  
  notify(){
    this.reactiveFns.forEach(fn=>{
      fn()
    })
  }

}

const obj = {
  name:'harry',
  age:18
}

const info = {
  address:'南京市'
}

//获取depend函数
const targetMap = new WeakMap()
function getDepend(target,key){
  let map = targetMap.get(target)
  if(!map){
    map = new Map()
    targetMap.set(target,map)
  }
  let depend = map.get(key)
  if(!depend){
    depend = new Depend()
    map.set(key,depend)
  }
  
   return depend
}

 

const objProxy = new Proxy(obj,{
  get(target,key,receiver){
    const depend = getDepend(target,key)
    depend.addDepend(activeReactiveFn)
    return Reflect.get(target,key,receiver)
  },
  set(target,key,newValue,receiver){
    Reflect.set(target,key,newValue,receiver)
    const depend = getDepend(target, key)
    depend.notify()
  }
})


//利用技巧传函数
let activeReactiveFn = null
//封装一个响应式函数
function watchFn(fn){
  //只有函数执行了才知道用到了谁
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null //防止乱添加

}

watchFn(function(){
  console.log("你好啊,这是测试Obj.name的");
  console.log(objProxy.name); //调用函数后立马会执行get方法
})

watchFn(function(){
  console.log("Hello啊,这是测试Obj.age的22222");
  console.log(objProxy.age);
})


objProxy.name = '张三'
objProxy.age = 33


//数据结构推导
// const weakMap = new WeakMap()
// const objMap = new Map()
// const infoMap = new Map()
// objMap.set("name","nameDepend")
// objMap.set("age","ageDepend")
// infoMap.set("adress","addressDepend")

// weakMap.set(obj,objMap)
// // console.log(weakMap.get(obj).get("name"));


Vue3响应式的设计

//当前需要收集的响应式函数
let activeReactiveFn = null
/**
 * Depend的优化
 * 1》depend方法
 * 2》使用Set而不是数组
 */
class Depend{
  constructor(){
    this.reactiveFns = new Set() //防止二次重新调用
  }
  // addDepend(reactiveFn){
  //   this.reactiveFns.push(reactiveFn)
  // }
  
  notify(){
    this.reactiveFns.forEach(fn=>{
      fn()
    })
  }
  depend(){
    if(activeReactiveFn){
      this.reactiveFns.add(activeReactiveFn)
    }
  }
  
}

const obj = {
  name:'harry',
  age:18
}
const objProxy = reactive(obj)

const info = {
  address:'南京市'
}
const infoProxy = reactive(info)

//
function reactive(obj){
  return new Proxy(obj,{
    get(target,key,receiver){
      const depend = getDepend(target,key)
      // depend.addDepend(activeReactiveFn)
      depend.depend()
      return Reflect.get(target,key,receiver)
    },
    set(target,key,newValue,receiver){
      Reflect.set(target,key,newValue,receiver)
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}


//获取depend函数
const targetMap = new WeakMap()
function getDepend(target,key){
  let map = targetMap.get(target)
  if(!map){
    map = new Map()
    targetMap.set(target,map)
  }
  let depend = map.get(key)
  if(!depend){
    depend = new Depend()
    map.set(key,depend)
  }
  
   return depend
}





//利用技巧传函数

//封装一个响应式函数
function watchFn(fn){
  //只有函数执行了才知道用到了谁
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null //防止乱添加

}


watchFn(()=>{
  console.log(objProxy.name,"----");
  console.log(objProxy.age,"+++++");
})

watchFn(()=>{
  console.log(infoProxy.address,"######");
  // console.log(objProxy.age,"+++++");
})

objProxy.name = 'lebro' //它其实不应该执行两次
infoProxy.address = '上海市'

Vue2响应式的设计

//当前需要收集的响应式函数
let activeReactiveFn = null
/**
 * Depend的优化
 * 1》depend方法
 * 2》使用Set而不是数组
 */
class Depend{
  constructor(){
    this.reactiveFns = new Set() //防止二次重新调用
  }
  // addDepend(reactiveFn){
  //   this.reactiveFns.push(reactiveFn)
  // }
  
  notify(){
    this.reactiveFns.forEach(fn=>{
      fn()
    })
  }
  depend(){
    if(activeReactiveFn){
      this.reactiveFns.add(activeReactiveFn)
    }
  }
  
}

const obj = {
  name:'harry',
  age:18
}
const objProxy = reactive(obj)

const info = {
  address:'南京市'
}
const infoProxy = reactive(info)

//Vue2实现的过程
function reactive(obj){
  Object.keys(obj).forEach(key=>{
    let value = obj[key]
    Object.defineProperty(obj,key,{
      get(){
        const depend = getDepend(obj,key)
        depend.depend()
        return value
      },
      set(newValue){
        value = newValue
        const depend = getDepend(obj,key)
        depend.notify()
      }
    })
  })

  return obj
}


//获取depend函数
const targetMap = new WeakMap()
function getDepend(target,key){
  let map = targetMap.get(target)
  if(!map){
    map = new Map()
    targetMap.set(target,map)
  }
  let depend = map.get(key)
  if(!depend){
    depend = new Depend()
    map.set(key,depend)
  }
  
   return depend
}





//利用技巧传函数

//封装一个响应式函数
function watchFn(fn){
  //只有函数执行了才知道用到了谁
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null //防止乱添加

}


watchFn(()=>{
  console.log(objProxy.name,"----");
  console.log(objProxy.age,"+++++");
})

watchFn(()=>{
  console.log(infoProxy.address,"######");
  // console.log(objProxy.age,"+++++");
})

objProxy.name = 'lebro' //它其实不应该执行两次
infoProxy.address = '上海市'