读vue的变化侦测
栏目: JavaScript · 发布时间: 5年前
内容简介:最近在看“深入浅出vuejs”,第一篇变化侦测,想把自己的理解总结一下。初版:
由来
最近在看“深入浅出vuejs”,第一篇变化侦测,想把自己的理解总结一下。
Object的变化侦测
总结一下我看了后的理解
-
将数据变成可响应式的,即将数据变成可监听的。通过
Observer
类来实现 -
依赖是什么?就是这个数据在哪里用到了,相当于
this
当前的上下文;所以当数据变化时,我们可以通知他,触发update
,从而触发渲染 -
那么这个依赖,谁来收集存起来。通过
Dep
类来实现
先看Observer
class Observer { constructor(value) { this.value = value if(!Array.isArray(value) { this.walk(value) } } walk (obj) { const keys = Object.keys(obj) for(let i = 0; i < keys.length; i++) { definedReactive(obj, keys[i], obj[keys[i]]) } } } function definedReactive(data, key, value) { if(typeof val === 'object') { new Observer(value) } let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() return value }, set: function (newVal) { if(value === newVal) { //这边最好是value === newVal || (value !== value && newVal !== newVal) return } value = newVal //这边新的newVal如果是引用类型也应该进行进行new Observer() dep.notify() } }) }
很容易看懂
-
将vue中的
data
对象进行遍历设置其属性描述对象 -
get
的设置就是为了在数据被访问时,将依赖dep.depend()
进去,至于做了什么看详细看Dep类 -
set
的设置则是为了判断新值和旧值是否一样(注意NaN),若不一样,则执行dep.notify()
,通知相应依赖进行更新变化
Dep类
class Dep { constructor () { this.subs = [] //存放依赖 } addSub () { this.subs.push(sub) }, remove () { remove(this.subs, sub) }, depend () { if(window.target) { this.addSub(window.target) //window.target 是this,watcher的上下文 } }, notify () { const subs = this.subs.slice() for(let i = 0, l = subs.length; i < l; i ++) { subs[i].update() //update这个方法来自watcher实例对象的方法 } } } function remove(arr, item) { if(arr.length) { const index = arr.indexOf(item) if(index > -1) { return arr.splice(index, 1) } } }
分析一下
-
主要就是对
dep
实例对象的增删改查的操作 -
window.target
这个依赖怎么来,就看watcher
实例对象了
Watcher类
初版:
class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.getter = parsePath(expOrFn) this.cb = cb this.value = this.get() } get() { window.target = this let value = this.getter.call(this.vm, this.vm) window.target = undefined return value } update() { const oldValue = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldValue) } }
分析
- 怎么触发?可以利用
vm.$watch('data.a', function (newValue, oldValue) { //执行相关操作 })
-
parsePath(expOrFn)
做了什么?从下面代码中可以看出作用就是返回一个函数,这个函数用来读取value
值
const bailRE = /[^\w.$]/ // function parsePath(path) { if(bailRE.test(path) { return //当path路径中有一个字符不满足正则要求就直接return } return function () { const arr = path.split('.') let data = this for(let i = 0, l = arr.length; i < l; i ++) { let data = data.arr[i] } return data } }
-
在
new Watcher
时会执行this.value
,从而执行this.get()
,所以这时的window.target
是当前watcher
实例对象this
;接着执行this.getter.call(this.vm, this.vm)
,触发属性描述对象的get
方法,进行dep.depend()
,最后将其window.target = undefined
-
update
的方法是在数据改变后触发,但这边有个问题就是 会重复添加依赖
上面版本中比较明显的问题
key key-value Observer
Array的侦测
怎么实现在数组发生变化时来触发 dep.notify()
,以及如何收集数组的依赖
-
通过
push, pop, shift, unshift, splice, sort, reverse
这几个方法的封装来触发dep.notify()
-
怎么的封装?分两种;第一种对于支持
_proto_
属性的,直接改写原型链的这些方法;第二种对于不支持的,直接在实例对象上添加改变后的7个方法
const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) //新建对象,继承Array的原型链 class Observer { constructor (value) { this.value = value this.dep = new Dep() //在Observer中添加dep属性为了记录数组的依赖 def(value, "_ob_", this) //在当前value上新增`_ob_`属性,其值为this,当前observer实例对象 if(Array.isArray(value) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observerArray(value) //将数组内元素也进行Observer }else { this.walk(value) } } //新增 observerArray (items) { for(let i = 0, l = items.length; i < l; i ++) { observe(items[i]) } } } //作用就是为obj,添加key值为val function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) } function observe(value, asRootData) { if(!isObject(value)) { return } let ob //判断value是否已经是Observer实例对象,避免重复执行Observer if(hasOwn(value, "_ob_") && value._ob_ instanceof Observer) { ob = value._ob_ } else { ob = new Observer(value) } return ob } function definedReactive(data, key, value) { let childOb = observe(value) //修改 let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() if(childOb) { //新增 childOb.dep.depend() } return value }, set: function (newVal) { if(value === newVal) { //这边最好是value === newVal || (value !== value && newVal !== newVal) return } value = newVal //这边新的newVal如果是引用类型也应该进行进行new Observer() dep.notify() } }) } //触发数组拦截 ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator() { const result = original.apply(this, args) const ob = this._ob_ //this就是数据value let inserted //对于新增变化的元素页进行observerArray() switch (method) { //因为这几个是有参数的 case 'push': case 'unshift': //因为push和unshift都是一样的取args,所以push不需要加break了 inserted = args break case 'splice': //新增变化元素是从索引2开始的 inserted = args.slice(2) break } ob.dep.notify() //通知依赖执行update return result }) }
分析,已 data = { a: [1, 2, 3] }
为例
-
首先对
data
对象进行Observer
,将执行this.walk(data)
-
接着执行
let childOb = observe(val)
,发现value
是一个数组对象,进行Observer
,主要进行是augment(value, arrayMethods, arrayKeys)
,将7个方法进行拦截,接着遍历内部元素是否有引用数据类型,有继续Observer
,最后返回Observer
实例对象ob
-
重点是
get
方法,当数据data被访问时,首先执行dep.depend()
这里将依赖添加到data
的dep
中;接着因为childOb
为true
所以执行childOb.dep.depend()
,这里是将依赖加入到observer
实例对象的dep
中,为什么,这个dep
是给数组发生变化时执行this._ob_.dep.notify()
,这个this就是value
对象,因为def(value, "_ob_", this)
,所以可以执行dep.notify()
这种数组变化侦测存在的问题
-
对于进行
this.list.length = 0
进行清空时,不会触发它的依赖更新,也就不会触发视图的渲染更新 -
对于
this.list[0] = 2
,这种通过索引来改变元素值时页一样不会触发更新 - 所以我们尽量避免通过这种方式来改变数据
还有 vm.$watch,vm.$set,vm.$delete
下篇中进行整理
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue 源码剖析 —— 对象变化侦测
- Vue 源码剖析 —— 变化侦测相关 API 实现原理
- Vue侦测相关api
- 海康威视网络摄像头-预览出现绿色移动侦测规则框
- Sophos Intercept X EDR:智能、简单的端点侦测与响应
- 记一次ajax的JSESSIONID 变化解决、非跨域变化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design and Analysis of Distributed Algorithms (Wiley Series on P
Nicola Santoro / Wiley-Interscience / 2006-10-27 / USD 140.95
This text is based on a simple and fully reactive computational model that allows for intuitive comprehension and logical designs. The principles and techniques presented can be applied to any distrib......一起来看看 《Design and Analysis of Distributed Algorithms (Wiley Series on P》 这本书的介绍吧!