读vue的变化侦测

栏目: JavaScript · 发布时间: 5年前

内容简介:最近在看“深入浅出vuejs”,第一篇变化侦测,想把自己的理解总结一下。初版:

由来

最近在看“深入浅出vuejs”,第一篇变化侦测,想把自己的理解总结一下。

Object的变化侦测

总结一下我看了后的理解

  1. 将数据变成可响应式的,即将数据变成可监听的。通过 Observer 类来实现
  2. 依赖是什么?就是这个数据在哪里用到了,相当于 this 当前的上下文;所以当数据变化时,我们可以通知他,触发 update ,从而触发渲染
  3. 那么这个依赖,谁来收集存起来。通过 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()
        }
    })
}

很容易看懂

  1. 将vue中的 data 对象进行遍历设置其属性描述对象
  2. get 的设置就是为了在数据被访问时,将依赖 dep.depend() 进去,至于做了什么看详细看Dep类
  3. 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)
        }
        
    }
}

分析一下

  1. 主要就是对 dep 实例对象的增删改查的操作
  2. 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)
    }
}

分析

  1. 怎么触发?可以利用
vm.$watch('data.a', function (newValue, oldValue) {
    //执行相关操作
})
  1. 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
    }
}
  1. new Watcher 时会执行 this.value ,从而执行 this.get() ,所以这时的 window.target 是当前 watcher 实例对象 this ;接着执行 this.getter.call(this.vm, this.vm) ,触发属性描述对象的 get 方法,进行 dep.depend() ,最后将其 window.target = undefined
  2. update 的方法是在数据改变后触发,但这边有个问题就是 会重复添加依赖

上面版本中比较明显的问题

key
key-value
Observer

Array的侦测

怎么实现在数组发生变化时来触发 dep.notify() ,以及如何收集数组的依赖

  1. 通过 push, pop, shift, unshift, splice, sort, reverse 这几个方法的封装来触发 dep.notify()
  2. 怎么的封装?分两种;第一种对于支持 _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] } 为例

  1. 首先对 data 对象进行 Observer ,将执行 this.walk(data)
  2. 接着执行 let childOb = observe(val) ,发现 value 是一个数组对象,进行 Observer ,主要进行是 augment(value, arrayMethods, arrayKeys) ,将7个方法进行拦截,接着遍历内部元素是否有引用数据类型,有继续 Observer ,最后返回 Observer 实例对象 ob
  3. 重点是 get 方法,当数据data被访问时,首先执行 dep.depend() 这里将依赖添加到 datadep 中;接着因为 childObtrue 所以执行 childOb.dep.depend() ,这里是将依赖加入到 observer 实例对象的 dep 中,为什么,这个 dep 是给数组发生变化时执行 this._ob_.dep.notify() ,这个this就是 value 对象,因为 def(value, "_ob_", this) ,所以可以执行 dep.notify()

这种数组变化侦测存在的问题

  1. 对于进行 this.list.length = 0 进行清空时,不会触发它的依赖更新,也就不会触发视图的渲染更新
  2. 对于 this.list[0] = 2 ,这种通过索引来改变元素值时页一样不会触发更新
  3. 所以我们尽量避免通过这种方式来改变数据

还有 vm.$watch,vm.$set,vm.$delete 下篇中进行整理

掘金地址


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Design and Analysis of Distributed Algorithms (Wiley Series on P

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》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具