资讯专栏INFORMATION COLUMN

读vue的变化侦测

zhoutk / 2270人阅读

摘要:由来最近在看深入浅出,第一篇变化侦测,想把自己的理解总结一下。的变化侦测总结一下我看了后的理解将数据变成可响应式的,即将数据变成可监听的。

由来

最近在看“深入浅出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 ");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()这里将依赖添加到datadep中;接着因为childObtrue所以执行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下篇中进行整理

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/6785.html

相关文章

  • vue变化侦测

    摘要:由来最近在看深入浅出,第一篇变化侦测,想把自己的理解总结一下。的变化侦测总结一下我看了后的理解将数据变成可响应式的,即将数据变成可监听的。 由来 最近在看深入浅出vuejs,第一篇变化侦测,想把自己的理解总结一下。 Object的变化侦测 总结一下我看了后的理解 将数据变成可响应式的,即将数据变成可监听的。通过Observer类来实现 依赖是什么?就是这个数据在哪里用到了,相当于th...

    Freeman 评论0 收藏0
  • 深入浅出Vue响应式原理

    摘要:总结最后我们依照下图参考深入浅出,再来回顾下整个过程在后,会调用函数进行初始化,也就是过程,在这个过程通过转换成了的形式,来对数据追踪变化,当被设置的对象被读取的时候会执行函数,而在当被赋值的时候会执行函数。 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解...

    yiliang 评论0 收藏0
  • Vue侦测相关api

    vm.$watch 用法: vm.$watch( expOrFn, callback, [options] ),返回值为unwatch是一个函数用来取消观察;下面主要理解options中的两个参数deep和immediate以及unwatch Vue.prototype.$watch = function (expOrFn, cb, options) { const vm = this ...

    zhangfaliang 评论0 收藏0
  • Vue源码分析之Observer

    摘要:中的观察者模式观察者模式一般包含发布者和订阅者两种角色顾名思义发布者负责发布消息,订阅者通过订阅消息响应动作了。中主要有两种类型的,一种是另外一种是是通过或者中的属性定义的。结束好了,基本结束,如有错漏,望指正。 碎碎念 四月份真是慵懒无比的一个月份,看着手头上没啥事干,只好翻翻代码啥的,看了一会Vue的源码,忽而有点感悟,于是便记录一下。 Vue中的观察者模式 观察者模式一般包含发布...

    CoderBear 评论0 收藏0
  • Vue面试题精选:Vue原理以及双向数据绑定实战过程

    摘要:双向数据绑定指的是,将对象属性变化与视图的变化相互绑定。数据双向绑定已经了解到是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过来实现对属性的劫持,达到监听数据变动的目的。和允许观察数据的更改并触发更新。 1 MVVM 双向数据绑定指的是,将对象属性变化与视图的变化相互绑定。换句话说,如果有一个拥有name属性的user对象,与元素的内容绑定,当给user.name赋予一个新...

    malakashi 评论0 收藏0

发表评论

0条评论

zhoutk

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<