读vue的变化侦测
栏目: JavaScript · 发布时间: 6年前
内容简介:最近在看“深入浅出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 变化解决、非跨域变化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
深入理解Nginx(第2版)
陶辉 / 机械工业出版社 / 2016-2 / 99.00元
本书致力于说明开发Nginx模块的必备知识,第1版发行以后,深受广大读者的喜爱.然而由于Ng,nx功能繁多且性能强大,以致必须了解的基本技能也很庞杂,而第1版成书匆忙,缺失了几个进阶的技巧描述,因此第2版在此基础上进行了完善。 书中首先通过介绍官方Nginx的基本用法和配置规则,帮助读者了解一般Nginx模块的用法,然后重点介绍了女口何开发HTTP模块(含HTTP过滤模块)来得到定制化的Ng......一起来看看 《深入理解Nginx(第2版)》 这本书的介绍吧!