理解Vue的watch实现原理和其实现方式
理解Vue中Watch的实现原理和方式之前,你需要深入的理解MVVM的实现原理,如果你还不是很理解,推荐你阅读我之前的几篇文章:
vue.js源码解读系列 - 剖析observer,dep,watch三者关系 如何具体的实现数据双向绑定
也可以关注我的博客查看关于Vue更多的源码解析:github.com/wangweiange…
备注:
1、此文大部分代码来自于Vue源码
2、此文MVVM部分代码来自于【彻底搞懂Vue针对数组和双向绑定(MVVM)的处理方式】,若有不懂之处,建议先看上文
3、部分代码为了兼容测试做了部分更改,但原理跟Vue一致
画一张watch的简单工作流程图:
把上文的 Dep,Oberver,Wather拿过来并做部分更改(增加收集依赖去重处理):
Dep代码如下:
//标识当前的Dep id
let uidep = 0
class Dep{
constructor () {
this.id = uidep
// 存放所有的监听watcher
this.subs = []
}
//添加一个观察者对象
addSub (Watcher) {
this.subs.push(Watcher)
}
//依赖收集
depend () {
//Dep.target 作用只有需要的才会收集依赖
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 调用依赖收集的Watcher更新
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i ) {
subs[i].update()
}
}
}
Dep.target = null
const targetStack = []
// 为Dep.target 赋值
function pushTarget (Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = Watcher
}
function popTarget () {
Dep.target = targetStack.pop()
}
Watcher代码如下:
//去重 防止重复收集
let uid = 0
class Watcher{
constructor(vm,expOrFn,cb,options){
//传进来的对象 例如Vue
this.vm = vm
if (options) {
this.deep = !!options.deep
this.user = !!options.user
}else{
this.deep = this.user = false
}
//在Vue中cb是更新视图的核心,调用diff并更新视图的过程
this.cb = cb
this.id = uid
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
if (typeof expOrFn === 'function') {
//data依赖收集走此处
this.getter = expOrFn
} else {
//watch依赖走此处
this.getter = this.parsePath(expOrFn)
}
//设置Dep.target的值,依赖收集时的watcher对象
this.value =this.get()
}
get(){
//设置Dep.target值,用以依赖收集
pushTarget(this)
const vm = this.vm
//此处会进行依赖收集 会调用data数据的 get
let value = this.getter.call(vm, vm)
//深度监听
if (this.deep) {
traverse(value)
}
popTarget()
return value
}
//添加依赖
addDep (dep) {
//去重
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
//收集watcher 每次data数据 set
//时会遍历收集的watcher依赖进行相应视图更新或执行watch监听函数等操作
dep.addSub(this)
}
}
}
//更新
update () {
this.run()
}
//更新视图
run(){
const value = this.get()
const oldValue = this.value
this.value = value
if (this.user) {
//watch 监听走此处
this.cb.call(this.vm, value, oldValue)
}else{
//data 监听走此处
//这里只做简单的console.log 处理,在Vue中会调用diff过程从而更新视图
console.log(`这里会去执行Vue的diff相关方法,进而更新数据`)
}
}
// 此方法获得每个watch中key在data中对应的value值
//使用split('.')是为了得到 像'a.b.c' 这样的监听值
parsePath (path){
const bailRE = /[^w.$]/
if (bailRE.test(path)) return
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i ) {
if (!obj) return
//此处为了兼容我的代码做了一点修改
//此处使用新获得的值覆盖传入的值 因此能够处理 'a.b.c'这样的监听方式
if(i==0){
obj = obj.data[segments[i]]
}else{
obj = obj[segments[i]]
}
}
return obj
}
}
}
//深度监听相关代码 为了兼容有一小点改动
const seenObjects = new Set()
function traverse (val) {
seenObjects.clear()
_traverse(val, seenObjects)
}
function _traverse (val, seen) {
let i, keys
const isA = Array.isArray(val)
if (!isA && Object.prototype.toString.call(val)!= '[object Object]') return;
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
while (i--){
if(i == '__ob__') return;
_traverse(val[i], seen)
}
} else {
keys = Object.keys(val)
i = keys.length
while (i--){
if(keys[i] == '__ob__') return;
_traverse(val[keys[i]], seen)
}
}
}
Observer代码如下:
class Observer{
constructor (value) {
this.value = value
// 增加dep属性(处理数组时可以直接调用)
this.dep = new Dep()
//将Observer实例绑定到data的__ob__属性上面去,后期如果oberve时直接使用,不需要从新Observer,
//处理数组是也可直接获取Observer对象
def(value, '__ob__', this)
if (Array.isArray(value)) {
//这里只测试对象
} else {
//处理对象
this.walk(value)
}
}
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i ) {
//此处我做了拦截处理,防止死循环,Vue中在oberve函数中进行的处理
if(keys[i]=='__ob__') return;
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
//数据重复Observer
function observe(value){
if(typeof(value) != 'object' ) return;
let ob = new Observer(value)
return ob;
}
// 把对象属性改为getter/setter,并收集依赖
function defineReactive (obj,key,val) {
const dep = new Dep()
//处理children
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
console.log(`调用get获取值,值为${val}`)
const value = val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
}
}
return value
},
set: function reactiveSetter (newVal) {
console.log(`调用了set,值为${newVal}`)
const value = val
val = newVal
//对新值进行observe
childOb = observe(newVal)
//通知dep调用,循环调用手机的Watcher依赖,进行视图的更新
dep.notify()
}
})
}
//辅助方法
function def (obj, key, val) {
Object.defineProperty(obj, key, {
value: val,
enumerable: true,
writable: true,
configurable: true
})
}
此文的重点来了,watch代码的实现
watch代码大部摘自于Vue源码,我做了部分修改,把Watch改写成一个cass类,代码如下:
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhfhkikc
系列文章
更多
同类精品
更多
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01