深入理解Vue.js:Object的变化侦测机制

前端 潘老师 2周前 (04-09) 10 ℃ (0) 扫码查看

Vue.js Object的变化侦测机制 不仅是Vue.js响应式系统的核心,还影响着数据更新与视图渲染之间的交互逻辑。接下来,咱们就一起深入探究一下Vue.js中Object的变化侦测原理及实现方式。

一、变化侦测的概念与类型

(一)什么是变化侦测

在Vue.js应用里,状态(数据)与DOM之间存在紧密联系。Vue.js会依据状态自动生成DOM,并将其输出到页面进行显示,这个过程被称为渲染。然而,在应用运行时,内部状态是动态变化的,每当状态改变,往往需要重新渲染页面。此时,确定状态中具体发生了哪些变化就成了关键问题,而变化侦测正是用来解决这一难题的。

(二)变化侦测的类型

变化侦测主要分为“推”(push)和“拉”(pull)两种类型。像Angular和React中的变化侦测采用的是“拉”的方式,而Vue.js则使用“推”的机制。在Vue.js中,当状态发生变化时,它能够快速感知,并且在一定程度上明确哪些状态发生了改变。这就好比在一个团队里,有专人时刻关注成员的动态,一旦有人有变动,能马上知晓。

二、追踪Object变化的方法

在JavaScript中,有两种常用的方法来侦测一个对象的变化,分别是Object.defineProperty和ES6的Proxy。这里我们主要探讨Vue.js中基于Object.defineProperty的实现方式。

function defineReactive(data, key, val) {
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            return val
        },
        set: function(newVal) {
            if (val === newVal) {
                return
            }
            val = newVal
        }
    })
}

这段代码通过Object.defineProperty为对象的属性定义了gettersettergetter用于获取属性值,setter则在属性值发生变化时被触发。不过,这段代码目前还只是基础示例,后续会进一步完善。

三、依赖收集与相关概念

(一)依赖收集的位置与方式

在Vue.js中,依赖收集是变化侦测的重要环节。依赖收集主要在getter中进行,而在setter中则会触发依赖。收集到的依赖会被存放在Dep类中。

export default class Dep {
    constructor() {
        this.subs = []
    }
    addSub(sub) {
        this.subs.push(sub)
    }
    removeSub(sub) {
        remove(this.subs, sub)
    }
    depend() {
        if (window.target) {
            this.addSub(window.target)
        }
    }
    notify() {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
            subs[i].update()
        }
    }
}
function remove(arr, item) {
    if (arr.length) {
        const index = arr.indexOf(item)
        if (index > -1) {
            arr.splice(index, 1)
        }
    }
}

Dep类就像是一个“仓库”,用来管理依赖。addSub方法用于添加依赖,removeSub方法用于移除依赖,depend方法在getter中被调用,用于收集依赖,notify方法则在setter中被触发,通知所有依赖进行更新。

(二)Watcher的作用

Watcher在Vue.js的变化侦测中扮演着中介角色。当数据发生变化时,会先通知Watcher,然后Watcher再通知其他相关地方。

export default 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)
    }
}

Watcher类的构造函数接收vm(Vue实例)、expOrFn(用于获取数据的表达式或函数)和cb(回调函数)。get方法用于收集依赖并获取数据,update方法在数据更新时被调用,执行回调函数cb,从而实现数据变化时的相关逻辑处理。

四、递归侦测对象的所有属性

前面的代码只能侦测对象中的某一个属性,为了实现对数据中所有属性的变化侦测,我们需要封装一个Observer类。

export 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++) {
            defineReactive(obj, keys[i], obj[keys[i]])
        }
    }
}


function defineReactive(data, key, val) {
    // 新增:递归子属性
    if (typeof val === 'object') {
        new Observer(val)
    }
    let dep = new Dep()
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function() {
            // 新增:收集依赖
            dep.depend()
            return val
        },
        set: function(newVal) {
            if (val === newVal) {
                return
            }
            val = newVal
            // 新增:触发依赖
            dep.notify()
        }
    })
}

Observer类的walk方法会遍历对象的所有属性,并通过defineReactive将每个属性都转换成getter/setter的形式,以便追踪它们的变化。同时,在defineReactive函数中,新增了对子属性的递归侦测、依赖收集和触发依赖的逻辑。

五、Object.defineProperty的局限性与Vue.js的解决方案

虽然Object.defineProperty能够将对象的属性转换成getter/setter形式来追踪变化,但它存在一定的局限性,无法追踪新增属性和删除属性的变化。因此,Vue.js专门提供了vm.$setvm.$delete方法来解决这一问题,让开发者在处理对象属性的添加和删除时更加灵活。

通过以上对Vue.js中Object变化侦测机制的深入讲解,相信大家对Vue.js的响应式原理有了更清晰的认识。在实际开发中,理解和掌握这些知识还是有点必要的。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/front/17053.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】