读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 变化解决、非跨域变化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入浅出HTML与CSS、XHTML
[美] 弗里曼 Freeman.E. / 东南大学出版社 / 2006-5 / 98.00元
《深入浅出HTML与CSS XHTML》(影印版)能让你避免认为Web-safe颜色还是紧要问题的尴尬,以及不明智地把标记放入你的页面。最大的好处是,你将毫无睡意地学习HTML、XHTML 和CSS。如果你曾经读过深入浅出(Head First)系列图书中的任一本,就会知道书中展现的是什么:一个按人脑思维方式设计的丰富的可视化学习模式。《深入浅出HTML与CSS XHTML》(影印版)的编写采用了......一起来看看 《深入浅出HTML与CSS、XHTML》 这本书的介绍吧!