尝试自行实现一个简版的vue

本文深入探讨了Vue框架的响应式系统实现,通过手写简化版Vue,详细解析了Watcher、Observer、Dependency及Complier等核心组件的作用与交互机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

一直在使用vue作为前端框架,预计将来很长一段时间,前端框架都将持续存在并发展,因此,更加深入了解vue便是一件非常重要的事。要想真正深入了解vue,除了根据问题去查询他人对vue的看法外,直接自行实现一个简版的vue,也是非常好的方式。

简要分析

  • vue 的特点:响应式。具体点:根据数据模型变动情况,自行更新视图。因此,实际上要实现两个基本的功能:①监听数据模型的变动;②更新视图。最后,还要有一个将两者联系起来的中间桥梁,去进行分发。

  • 做到监听的前提条件,也就是采用es6约定的 Object.defineProperty() 中的 get 和 set。举个例子:

    	let responsiveObj = {name: '', age: '', intro: ''}
    	Object.defineProperty(responsiveObj, 'name', {
    		get () {
    			console.log('someone is getting name now')
    			return 'long'
    		},
    		set (val) {
    			console.log('someone is setting name now')
    			this._name = val
    		}
    	})
    

    通过这种方式,就能够在每一次读取值的时候和更新值的时候进行一次处理,在这个处理中,就可以加入我们想要的动作。

  • 那么,我们想要做什么呢?
    如上所说,我们需要①对于每一个 ‘希望响应式更新’ 的实例,都对自己 ‘用过的值’ 进行监听。 ②数据模型中的每一个项,都要保存 ‘谁引用了自己’ 的列表。 ③数据模型中的每一项进行了值更新时,就循环通知 ‘引用了自己的实例’,并把新的值一块儿传过去。④每一个收到了通知的实例,都要根据更新的内容对自身做一些改变。

    在vue中,①就对应着我们写的 <template> 中的每个节点,在html中,也就直接对应着 ‘视图’,因为要具有‘监听’的功能,因此每个节点都要有一个 Watcher 实例; ②对应着一个类 Dependency,数据模型也就是 export default 中的部分内容,包括 data () {}、 computed () {},也就意味着数据模型中的每一项都要有一个Dependency实例; ③对应着我们在上面的例子中写的 set 后面要进行的处理。④对应着①中的每一个实例要对自己做的事,也就是做 ‘替换视图’ 的事,这是由节点中的另一个 Complier实例 去实现。

总结一下几个类

  • Watcher 订阅者
    这是每一个节点具有的实例,用来监听自己需要的值的变化情况,依据监听的结果,对节点进行处理(complier)。

  • Observer 观察者
    数据模型中的每一项都是一个Observer,实际就是使用 Object.defineProperty() 实现的。

  • Dependency 依赖项
    数据模型中的每一项都具有此实例,用来收集对某个数据源有依赖的节点。

  • Complier 编译者
    每一个节点具有的实例,用于进行 DOM 的更新。

探讨一下‘名字’

在上面,我们用了 Watcher 订阅者,Observer 观察者,Dependency 依赖项,Complier 编译者 这几个名字,之前看这一块儿内容的时候,总觉得名字起的很奇怪,尤其是不懂 Watcher 和 Observer 的区别,以自己二流的英语能力,会把 Watcher 翻译成观察者,而 Observer 似乎更像是 ‘被观察对象’, 把 Dependency 理解成 ‘在观察自己的实例的列表’

但其实,‘观察者’ 指的是 ‘更近距离’ 地接触到 ‘被观察对象’ 的实例,当这个观察者发现被观察的内容发生了变化(调用了 set , 例如 $data.name = ‘xxx’),这里的 $data.name 才是被观察的对象,而在 set 中所执行的一系列操作,都是 ‘观察者’ 在做自己该做的事(调用一些方法让节点知道值发生了变化)。

而 Watcher 实际是一个 ‘订阅者’ 的概念,就像一个读者在出版社的 ‘订阅表’ 上填上了自己的地址(添加到 数据模型的 Dependency 实例中),当出版社出新书的时候(值发生了变化),出版社就会拿着新书(新值),送到每一位订阅了的读者所填的地址去。

依赖项 Dependency,其实表示的是 ‘依赖了当前数据模型的节点’ ,而不是 ‘自己所依赖的数据’。

实现一下几个方法

// 用来控制 Watcher 加入到 Dependency 中的全局变量
this.globalWatcher = null

function defineObj (obj, key, val) { // 实际进行响应式改造的方法
  let dep = new Set() // 依赖收集,可以把这个抽象成一个 Dependency 类
  Object.defineProperty(obj, key, {
    get () {
      if (globalWatcher) {
        dep.add(globalWatcher) // 把当前读取该值的 watcher 添加到依赖项中
        globalWatcher = null
      }
      return val
    },
    set (newVal) { 
      val = newVal // 赋新值
      observe(val) // 变成响应式
      // 通知依赖项中的每一个watcher
      Array.from(dep).forEach(item => {
        item.update && item.update(val)
      })
    }
  })
}

function observe (obj) { // 处理‘是否为对象’
  let isObj = (tar) => Object.prototype.toString.call(tar) === '[object Object]'

  if (isObj(obj)) {
    Object.keys(obj).forEach(prop => {
      if (isObj(obj[prop])) { // 子属性为对象,则往下遍历
        observe(obj[prop])
      } else {
        defineObj(obj, prop, obj[prop])
      }
    })
  }
}

// 网上大家基本都是用 es6 中的 class 关键字实现的
// 这里试着用原始版的 js类 实现方式
function Watcher (val, prop) { // 观察者类
  globalWatcher = this
  this.nameVal= val[prop]
}

Watcher.prototype.update = function (val) { // 接收更新后的处理
  console.log('update this val in update')
  // 这里可以做很多事情,编译器的调用也可以在这里
  this.nameVal = val
}

// 举个例子,定义一个数据模型
let obj = {
  name: 'long',
  age: 18,
  say () {
    console.log('hello , I am ' + this.name + ', and I am ' + this.age + ' years old.')
  }
}
// 把这个数据模型变成响应式的
observe(obj)

// 创建一个观察数据模型中的name的实例
let eleNameNode = new Watcher(obj, 'name')

// 之后改变 obj.name 的时候,都会触发到 eleNameNodede 原型上 update 方法
// 虽然现在 update 中只进行了 console.log ,但这里面可以添加一切想做的事
obj.name = 'longalong' // => 'update this val in update'

总结一下

上面的内容,还没有包含 Complier,这部分主要是和正则表达式进行字符串解析相关,我目前对正则表达式的掌握还远远不够,因此暂时省略了这部分内容,实现了响应式的前半部分,后半部分明天再弄一下。

要去做的

从上面写这篇文章的过程,能看出我至少在以下几个方面的熟练度是不够的,认识是不清晰的:

  • 面向对象的思想。 => 总是在想要怎么‘一步步实现’,而不是‘实现这个功能的实例要具有什么方法’。
  • 正则表达式。 => 嗯,只知道点皮毛,应用层面差得不是一点半点。
  • DOM 对象实例。 => 对DOM对象的理解太浅,没有真正把它当做一个 对象 去看待。
  • 数据结构与算法。 => 任何问题,最终都可以归结到数据结构上,捋清数据结构,就能比较清晰地明白用什么样的算法去实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iamlongalong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值