实现简易的 Vue 响应式

87 篇文章 1 订阅
46 篇文章 0 订阅

本文的目的是掌握 Vue2 中的 响应式原理,学习的过程是手写一个简易版的
Vue。从数据劫持,到模板编译,再到依赖收集,完完整整的自己实现整套数据响应式的流程。

一个最基础的响应式

实现一个属性的响应式

我们首先封装一个响应式处理的方法 defineReactive,通过 defineProperty 这个方法重新定义对象属性的 getset 描述符,来实现对数据的劫持,每次 读取数据 的时候都会触发 get ,每次 更新数据 的时候都会触发 set ,所以我们可以在 set 中触发更新视图的方法 update 来实现一个基本的响应式处理。

/**
 * @param {*} obj  目标对象
 * @param {*} key  目标对象的一个属性
 * @param {*} val  目标对象的一个属性的初始值
 */
function defineReactive(obj, key, val) {
  // 通过该方法拦截数据
  Object.defineProperty(obj, key, {
    // 读取数据的时候会走这里
    get() {
      console.log('🚀🚀~ get:', key);
      return val
    },
    // 更新数据的时候会走这里
    set(newVal) {
      // 只有当新值和旧值不同的时候 才会触发重新赋值操作
      if (newVal !== val) {
        console.log('🚀🚀~ set:', key);
        val = newVal
        // 这里是触发视图更新的地方
        update()
      }
    }
  })
}

我们写点代码来测试一下,每 1s 修改一次 obj.foo 的值 , 并定义一个 update 方法来修改 app 节点的内容。

// html
<div id='app'>123</div>

// js
// 劫持 obj.foo 属性
const obj = {}
defineReactive(obj, 'foo', '')

// 给 obj.foo 一个初始值
obj.foo = new Date().toLocaleTimeString()

// 定时器修改 obj.foo
setInterval(() => {
  obj.foo = new Date().toLocaleTimeString()
}, 1000)

// 更新视图
function update() {
  app.innerHTML = obj.foo
}

可以看到,每次修改 obj.foo 的时候,都会触发我们定义的 get 和 set ,并调用 update 方法更新了视图,到这里,一个最简单的响应式处理就完成了。在这里插入图片描述

处理深层次的嵌套

一个对象通常情况下不止一个属性,所以当我们要给每个属性添加响应式的时候,就需要遍历这个对象的所有属性,给每个 key 调用 defineReactive 进行处理。

/**
 * @param {*} obj  目标对象
 */
function observe(obj) {
  // 先判断类型, 响应式处理的目标一定要是个对象类型
  if (typeof obj !== 'object' || obj === null) {
    return
  }
  // 遍历 obj, 对 obj 的每个属性进行响应式处理
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
}
// 定义对象 obj
const obj = {
  foo: 'foo',
  bar: 'bar',
  friend: {
    name: 'aa'
  }
}

// 访问 obj 的属性 , foo 和 bar 都被劫持到,就不在浏览器演示了。
obj.bar = 'barrrrrrrr' // => 🚀🚀~ set: bar
obj.foo = 'fooooooooo' // => 🚀🚀~ set: foo

// 访问 obj 的属性 obj.friend.name 
obj.friend.name = 'bb' // => 🚀🚀~ get: friend

当我们访问 obj.friend.name 的时候,也只是打印出来 get: friend ,而不是 friend.name , 所以我们还要进行个 递归,把 深层次的属性 同样也做响应式处理。

function defineReactive(obj, key, val) {
  // 递归
  observe(val)
  
  // 继续执行 Object.defineProperty...
  Object.defineProperty(obj, key, {
    ... ...
  })
}

// 再次访问 obj.friend.name
obj.friend.name = 'bb' // => 🚀🚀~ set: name

递归的时机在 defineReactive 这个方法中,如果 value 是对象就进行递归,如果不是对象直接返回,继续执行下面的代码,保证 obj 中嵌套的属性都进行响应式的处理,所以当我们再次访问 obj.friend.name 的时候,就打印出了 set: name 。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值