2024-04-19 深入JavaScript高级语法七——响应式原理实现

1、一个简单的响应式函数的封装

// 只针对name的变化做了封装
let reactiveFns = []
// 封装一个响应式的函数
function watchFn(fn) {
  reactiveFns.push(fn)
}

// 对象的响应式
const obj = {
  name: "张三",
  age: 12
}

watchFn(function() {
  const newName = obj.name
  console.log('hello world')
  console.log(obj.name)
})

watchFn(function() {
  console.log(obj.name, "-----")
})

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

// 对象的某个属性发生了变化,使用了该对象的方法自动重新执行
obj.name = "李四"
reactiveFns.forEach(fn => {
  fn()
})
// console.log打印值
// "hello world"
// "李四"
// "李四 -----"

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: "张三", // dep对象
  age: 12,  // dep对象
}

watchFn(function() {
  const newName = obj.name
  console.log('hello world')
  console.log(obj.name)
})

watchFn(function() {
  console.log(obj.name, "-----")
})

obj.name = "李四"
// 手动监听变化
depend.notify()

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)
}

// 对象的响应式
const obj = {
  name: "张三", // dep对象
  age: 12,  // dep对象
}

// 监听对象的属性变化: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()
  }
})


// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
  const newName = objProxy.name
  console.log('hello world')
  console.log(objProxy.name)
})

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

objProxy.name = "李四"
objProxy.name = "王二"
// 就算是age发生变化,依赖name的方法也会重新执行
objProxy.age= 17

4、依赖收集的管理

原因:真实开发中,是有多个对象的,而且每个对象也会对应不同的属性。

注释:每个对象单独对应一个Map(是为了防止有不同对象存在相同属性的情况),再把不同对象的Map通过一个统一的WeakMap管理起来。
对象的依赖管理
伪代码:

const obj = {
  name: "张三", // dep对象
  age: 12,  // dep对象
}
const info= {
  name: "张三", // dep对象
  address: "四川省成都市",  // dep对象
}
const objMap = new Map()
objMap.set('name', 'nameDepend')
objMap.set('age', 'ageDepend')

const infoMap = new Map()
infoMap.set('address', 'addressDepend')
objMap.set('name', 'nameDepend')

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

// 获取obj.name的depend
const depend = targetMap.get(obj).get('name')
depend.notify()

封装一个获取depend的函数

// 封装一个获取depend的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
  // 根据target对象获取map的过程
  let map = targetMap.get(target)
  if (!map) {
    targetMap.set(target, map)
  }
  
  // 根据key获取depend对象
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}
...
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)
    // 使用getDepend方法
    const depend = getDepend(target, key)
    depend.notify()
  }
})

5、正确的收集依赖

重点关注watchFn方法的变化和activeReactiveFn全局变量的作用,以及Proxy中的get方法

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

// 封装一个响应式的函数
// activeReactiveFn:一个全局中间变量,使得能够在Proxy的get方法中拿到需要响应的函数
let activeReactiveFn = null
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 封装一个获取depend的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
  // 根据target对象获取map的过程
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }
  
  // 根据key获取depend对象
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

// 对象的响应式
const obj = {
  name: "张三", // dep对象
  age: 12,  // dep对象
}

// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = new Proxy(obj, {
  get: function(target,key,receiver) {
  	// 收集对应的响应式函数
    // 1、根据target,key获取对应的depend
    const depend = getDepend(target, key)
    // 2、给depend对象中添加响应函数
    depend.addDepend(activeReactiveFn)
    return Reflect.get(target,key,receiver)
    
  },
  set: function(target,key,newValue,receiver) {
    Reflect.set(target,key,newValue,receiver)
    // 自动监听
    const depend = getDepend(target, key)
    depend.notify()
  }
})


// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
  console.log("--第一个name函数开始--")
  const newName = objProxy.name
  console.log('hello world')
  console.log(objProxy.name)
  console.log("--第一个name函数结束--")
})

watchFn(function() {
  console.log("--第一个age函数开始--")
  console.log(objProxy.age)
  console.log("--第一个age函数结束--")
})

watchFn(function() {
  console.log(objProxy.name, "新函数")
  console.log(objProxy.age, "新函数")
})

console.log("----------------改变obj的name值----------------")
// objProxy.name = '王五'
objProxy.age = 20

6、对Depend类进行重构优化

Depend优化:

  1. depend方法
  2. 使用Set来报错依赖函数,而不是数组[]
// activeReactiveFn:一个中间变量,使得能够在Proxy的get方法中拿到需要响应的函数
let activeReactiveFn = null

class Depend {
  constructor() {
    // 优化2:通过Set结构收集需要响应的方法,防止同一个响应式函数被重复收集
    this.reactiveFns = new Set()
  }
  addDepend(reactiveFn) {
    this.reactiveFns.add(reactiveFn)
  }
  // 优化1:增加depend方法,使得在Proxy.get方法里面添加响应式函数时,不需要关心activeReactiveFn参数
  depend() {
    if (activeReactiveFn) {
      this.addDepend(activeReactiveFn)
    }
  }
  notify() {
    this.reactiveFns.forEach(fn => {
      fn()
    })
  }
}

// 封装一个响应式的函数
function watchFn(fn) {
  activeReactiveFn = fn
  fn()
  activeReactiveFn = null
}

// 封装一个获取depend的函数
const targetMap = new WeakMap()
function getDepend(target, key) {
  // 根据target对象获取map的过程
  let map = targetMap.get(target)
  if (!map) {
    map = new Map()
    targetMap.set(target, map)
  }
  
  // 根据key获取depend对象
  let depend = map.get(key)
  if (!depend) {
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}

// 对象的响应式
const obj = {
  name: "张三", // dep对象
  age: 12,  // dep对象
}

// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = new Proxy(obj, {
  get: function(target,key,receiver) {
    // 根据target,key获取对应的depend
    const depend = getDepend(target, key)
    // 给depend对象中添加响应函数
    // depend.addDepend(activeReactiveFn)
    // 直接调用depend方法,不需要传递activeReactiveFn参数
    depend.depend()
    return Reflect.get(target,key,receiver)
    
  },
  set: function(target,key,newValue,receiver) {
    Reflect.set(target,key,newValue,receiver)
    // 自动监听
    const depend = getDepend(target, key)
    depend.notify()
  }
})


// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
  console.log(objProxy.name,"------")
  console.log(objProxy.name,"++++++")
})

objProxy.name = "李四"

7、对象的响应式操作

7.1、vue3实现方式——Proxy

重点关注reactive方法的封装和使用

...
// 把传入的对象变成响应式对象
function reactive(obj) {
  return new Proxy(obj, {
    get: function(target,key,receiver) {
      // 根据target,key获取对应的depend
      const depend = getDepend(target, key)
      // 给depend对象中添加响应函数
      // depend.addDepend(activeReactiveFn)
      // 直接调用depend方法,不需要传递activeReactiveFn参数
      depend.depend()
      return Reflect.get(target,key,receiver)

    },
    set: function(target,key,newValue,receiver) {
      Reflect.set(target,key,newValue,receiver)
      // 自动监听
      const depend = getDepend(target, key)
      depend.notify()
    }
  })
}

// 对象的响应式
const obj = {
  name: "张三", // dep对象
  age: 12,  // dep对象
}

// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const objProxy = reactive(obj)

// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
  console.log(objProxy.name,"------")
  console.log(objProxy.name,"++++++")
})

objProxy.name = "李四"

const info = {
  address: "四川省成都市"
}

// 监听对象的属性变化:Proxy(vue3)/Object.defineProperty(vue2)
const infoProxy = reactive(info)

// 注意:原对象通过Proxy代理后,要想监听到对象变化,就不能使用原对象了,而要使用代理对象objProxy
watchFn(function() {
  console.log(infoProxy.address,"------")
})

infoProxy.address = "广州市"

const foo = reactive({
  name: "foo"
})

watchFn(() => {
  console.log(foo.name)
})
foo.name = "bar"

7.2、vue2实现方式——Object.defineProperty

function reactive(obj) {
  Object.keys(obj).forEach(key => {
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function() {
        const depend = getDepend(obj, key)
        depend.depend()
        return value
      },
      set: function(newValue) {
        value = newValue
        const depend = getDepend(obj, key)
        depend.notify()
      }
    })
  })
  return obj
}
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值