一、初识vue响应式及简单实现

88 阅读2分钟

对于vue开发人员或者使用过vue的开发人员来说,响应式是大家非常熟悉的词汇,大家也都知道vue是基于响应式来实现的,什么数据驱动视图、MVVM框架大家也都有所耳闻,那么这到具体是怎么实现的呢,下面就带着大家实现一个比较简单但是会很全面的响应式系统。ps:这是一个系列的文章,不定时更新,想具体了解vue的可以参考霍春阳大佬的《vue.js设计与实现》。

在vue中,我们在template模板中使用一个响应式变量,当我们改变响应式变量的值时,视图会自动刷新,原因就是当我们改变值后,会去再次执行render方法(template模板会被编译成组件的render方法),然后再进行patch操作更新视图,其中改变值后自动去执行render方法的过程就是响应式系统要做的。下面代码简单模拟:

// 假设响应式数据是 data (暂不考虑简单数据类型和数组集合等,后续会逐渐补充)
const data = { message: 'Hello Vue' }
// 定义一个函数用来表示组件的render方法
function render() {
  console.log(data.message)
}
// 组件第一次挂载执行render
render()
// 改变data中的message
data.message = 'message change'
// 这里应该自动再次执行render函数 那么这要怎么实现呢??

这里很多人肯定都会想到数据劫持,那么数据劫持我们应该怎么做呢。答案是在执行render函数访问data里的message时,将render函数收集起来,当我们改变设置message的值时再将render函数拿出来执行一下就可以了。以下为具体实现

// 存储副作用函数的桶
const bucket = new Set()

// 原始数据
const data = { message: 'Hello Vue' }
// 对原始数据的代理
const dataProxy = new Proxy(data, {
  // 拦截读取操作
  get(target, key) {
    // 将副作用函数 effect 添加到存储副作用函数的桶中
    bucket.add(render)
    // 返回属性值
    return target[key]
  },
  // 拦截设置操作
  set(target, key, newVal) {
    // 设置属性值
    target[key] = newVal
    // 把副作用函数从桶里取出并执行
    bucket.forEach(fn => fn())
  }
})

function render() {
  console.log(dataProxy.message)
}
render()
dataProxy.message = 'change message'
// 执行上述代码会打印Hello Vue和change message
// 可见render在修改后又执行了一次

以上代码就实现了我们想要实现的内容,我们使用Proxy(不了解的可以去mdn进行详细学习)对data进行了一层代理,后续我们都将对生成的代理对象进行操作而不是直接操作data,这样我们才能进入到get、set等拦截方法中。

可以看到上面的代码存在很多局限性,比如我们直接将render添加到桶中的硬编码,以及我们只考虑了message属性的响应式等,后续我们将对这些问题进行解决完善