1. 首页

看,这就是Vue3响应式原理

关于Vue3 话说,Vue3已经进行到rc4版本了,4月份beta发布的时候前端圈红红火火,不知道大家开始学了没

vue2 响应式原理回顾

对象响应化:遍历每个key,通过 Object.defineProperty API定义getter,setter

// 伪代码
function observe(){
    if(typeof obj !='object' || obj == null){
        return
    }
    if(Array.isArray(obj)){
        Object.setPrototypeOf(obj,arrayProto)
    }else{
    const keys = Object.keys()
    for(let i=0;i<keys.length;i++){
      const key = keys[i]
      defineReactive(obj,key,obj[key])
    }``
    }
}
function defineReactive(target, key, val){
  observe(val)
  Object.defineProperty(obj, key, {
    get(){
      // 依赖收集
      dep.depend()
      return val
    },
    set(newVal){
      if(newVal !== val){
        observe(newVal)
        val = newVal
        // 通知更新
        dep.notify()
      }
    }
  })
}

数组响应化:覆盖数组的原型方法,增加通知变更的逻辑

// 伪代码
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push','pop','shift','unshift','splice','reverse','sort'].forEach(key=>{
    arrayProto[key] = function(){
        originalProto[key].apply(this.arguments)
        notifyUpdate()
    }
})

vue2响应式痛点

递归,消耗大 新增/删除属性,需要额外实现单独的API 数组,需要额外实现 Map Set Class等数据类型,无法响应式 修改语法有限制

vue3响应式方案

使用ES6的 Proxy 进行数据响应化,解决上述Vue2所有痛点

Proxy可以在目标对象上加一层拦截/代理,外界对目标对象的操作,都会经过这层拦截

相比 Object.defineProperty ,Proxy支持的对象操作十分全面:get、set、has、deleteProperty、ownKeys、defineProperty……等等

// reactive 伪代码
function reactice(obj){
  return new Proxy(obj,{
    get(target, key, receiver){
      const ret = Reflect.get(target, key, receiver)
      return isObject(ret) ? reactice(ret) : ret
    },
    set(target, key, val, receiver){
      const ret = Reflect.set(target, key, val, receiver)
      return ret
    },
    deleteProperty(target, key){
      const ret = Reflect.deleteProperty(target, key)
      return ret
    },
  })
}

响应式原理

看,这就是Vue3响应式原理

通过 effect 声明依赖响应式数据的函数cb ( 例如视图渲染函数render函数),并执行cb函数,执行过程中,会触发响应式数据 getter 在响应式数据 getter 中进行 track 依赖收集:建立 数据&cb 的映射关系存储于 targetMap 当变更响应式数据时,触发 trigger , 根据 targetMap 找到关联的cb执行 映射关系 targetMap 结构:

targetMap: WeakMap{ 
    target:Map{ 
        key: Set[cb1,cb2...] 
    }
}

手写vue3响应式

大致结构

/// mini-vue3.js

/* 建立响应式数据 */
function reactice(obj){}

/* 声明响应函数cb(依赖响应式数据) */
function effect(cb){}

/* 依赖收集:建立 数据&cb 映射关系 */
function track(target,key){}

/* 触发更新:根据映射关系,执行cb */
function trigger(target,key){}

reactive


/* 建立响应式数据 */ function reactive(obj){   // Proxy:http://es6.ruanyifeng.com/#docs/proxy   // Proxy相当于在对象外层加拦截   // Proxy递归是惰性的,需要添加递归的逻辑      // Reflect:http://es6.ruanyifeng.com/#docs/reflect   // Reflect:用于执行对象默认操作,更规范、更友好,可以理解成操作对象的合集   // Proxy和Object的方法Reflect都有对应   if(!isObject(obj)) return obj   const observed = new Proxy(obj,{     get(target, key, receiver){       const ret = Reflect.get(target, key, receiver)       console.log('getter '+ret)       // 跟踪 收集依赖       track(target, key)       return reactive(ret)     },     set(target, key, val, receiver){       const ret = Reflect.set(target, key, val, receiver)       console.log('setter '+key+':'+val + '=>' + ret)       // 触发更新       trigger(target, key)       return ret     },     deleteProperty(target, key){       const ret = Reflect.deleteProperty(target, key)       console.log('delete '+key+':'+ret)       // 触发更新       trigger(target, key)       return ret     },   })   return observed }

effect


/* 声明响应函数cb */ const effectStack = [] function effect(cb){   // 对函数进行高阶封装   const rxEffect = function(){     // 1.捕获异常     // 2.fn出栈入栈     // 3.执行fn     try{       effectStack.push(rxEffect)       return cb()     }finally{       effectStack.pop()     }   }   // 最初要执行一次,进行最初的依赖收集   rxEffect()   return rxEffect }

track

JS中文网 – 前端进阶资源教程 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容

/* 依赖收集:建立 数据&cb 映射关系 */
const targetMap = new WeakMap()
function track(target,key){
  // 存入映射关系
  const effectFn = effectStack[effectStack.length - 1]  // 拿出栈顶函数
  if(effectFn){
    let depsMap = targetMap.get(target)
    if(!depsMap){
      depsMap = new Map()
      targetMap.set(target, depsMap)
    }
    let deps = depsMap.get(key)
    if(!deps){
      deps = new Set()
      depsMap.set(key, deps)
    }
    deps.add(effectFn)
  }
}

trigger

/* 触发更新:根据映射关系,执行cb */
function trigger(target, key){
  const depsMap = targetMap.get(target)
  if(depsMap){
    const deps = depsMap.get(key)
    if(deps){
      deps.forEach(effect=>effect())
    }
  }
}

测试demo

<!-- test.html -->
<div id="app">
 {{msg}}
</div>

<script src="./mini-vue3.js"></script>

<script>
  // 定义一个响应式数据
  const state = reactive({
    msg:'message'
  })

  // 定义一个使用到响应式数据的 dom更新函数
    function updateDom(){
        document.getElementById('app').innerText = state.msg
    }

    // 用effect声明更新函数
  effect(updateDom)

  // 定时变更响应式数据
  setInterval(()=>{
    state.msg = 'message' + Math.random()
  },1000)
</script>

效果: 看,这就是Vue3响应式原理

作者:注销用户
链接:https://juejin.im/post/6856661502053744654

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:看,这就是Vue3响应式原理

链接:https://www.javascriptc.com/4144.html

« HTTP系列之HTTP缓存
尝鲜Vue3——vite源码分析»
Flutter 中文教程资源

相关推荐

QR code