最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue响应式原理解析 (进阶必备知识)

    正文概述 掘金(青莲使者)   2021-08-27   572

    这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战

    vue响应式原理解析 (进阶必备知识)

    前言

    理解 Vue 响应式原理,我个人比较推荐的方法是结合源码来看。Vue响应式原理,你所需要知道的 首先,各位再熟悉不过的,一定是 Vue 官方提供的这张示意图了:

    vue响应式原理解析 (进阶必备知识)

    我们以这张图为基础,先帮助大家重新捋一遍响应式的机制。在这个基础上,再去做更进一步的分析。注意我们图中有三个关键角色:Watcher、Data、和 Render。

    Vue 会对传入的 data 做处理:为每一个属性添加 getter 和 setter。在这个过程中,涉及到了 Object.defineProperty 这个方法。

    同时每一个 Vue 组件实例,都对应着一个 watcher 实例;这个 watcher 实例仿佛一个跟踪狂,它的目光永远跟随着 data: 由于 render 函数的执行依赖于数据的读取,因此渲染时必定会读取 data 属性进而触发其对应的 getter 方法。getter 方法被调用后,会通知到 watcher,watcher 就会把这些 getter 方法被触发的属性记录为“依赖”—— 这一过程,就是大家常常听到的“依赖收集”过程。

    如果 data 发生了更新,也就是说被“写”了,此时对应属性的 setter 方法就会被触发。setter 也会去通知 watcher, 告诉它“我改变了”。watcher  拿到消息后,立刻跑去告诉  render:“data变了,你也给我跟着变!”。由此去触发一个 re-render 的过程、与数据更新相关的组件会重新渲染。

    源码分析

    Object.defineProperty

    Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。它的调用形式如下:

    Object.defineProperty(obj, prop, descriptor)
    

    其中第一个入参,是我们操作的目标对象;第二个入参,是我们需要修改的属性的名称;第三个入参,是一个描述符,用来描述你到底要对这个目标属性做什么。

    我们在 Vue 响应式原理中涉及到的“描述符”,就是 getter/setter 方法:

    getter方法: 一个给属性提供 getter 的方法,实际方法名为“get”;如果没有 getter 则为 undefined 。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this 对象(由于继承关系,这里的this 并不一定是定义该属性的对象)。默认为 undefined

    setter方法: 一个给属性提供 setter 的方法,实际方法名为“set”;如果没有 setter 则为 undefined 。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。默认为 undefined

    ObserverDepWatcher 的关系

    在源码层次,大家需要把握好这三个角色:

    Observer:处理 data 的家伙。它会给 data 安装 getter 和 setter,这些安装上的逻辑会联动 Dep 去完成依赖收集和更新的派发;

    Dep:实际通知 Watcher 的人。在 getter 和 setter 逻辑中,正是通过调度 Dep 来完成信息的收集、以及和Watcher 间的通信;

    Watcher:Watcher 被通知之后,就会通知 render、进而触发重渲染了。

    Observer

    Observer 的作用是遍历所有的属性,给它们安装上 getter/setter 方法:

    class Observer { 
        constructor() {
            // 具体逻辑在 observe 函数里
            observe(this.data);
        }
    }
    function observe (data) {
        // 取出所有的 key
        const keys = Object.keys(data);
        // 遍历所有属性
        for (let i = 0; i < keys.length; i++) {
            // 绑定 getter/setter 方法
            defineReactive(obj, keys[i]);
        }
    }
    

    这里我们看到,具体的绑定操作是在 defineReactive 里做的:

    function defineReactive (obj, key, val) {
        // 定义一个 Dep 对象,它的作用正如我们上文所说const dep = new Dep();
        Object.defineProperty(obj, key, 
           { enumerable: true, 
           configurable: true,
            get() {
                // 收集依赖、关联到 watcher dep.depend();
                return val;
            },
            set(newVal) {
                if (newVal === val) return;
                // 感知更新、通知 watcher 
                dep.notify();
            }
        });
    }
    

    在 defineReactive 里面,每一个 getter/setter 里面都出现了 Dep 实例。正如我们前面所介绍的一样,实际收集信息和通知 watcher 的工作是 Dep 来做的。每一个属性都对应一个单独的 Dep 实例。

    在 getter 方法里面,调用了 dep 的 depend 方法,这个方法有什么玄机呢?我们来看看 Dep 的结构:

    Dep

    Dep 的角色,宛如一个“工具人”,它是 Watcher 和 Observer 之间的纽带,是“通信兵”:

    class Dep { 
        constructor () {
            // 存储 Watcher 实例的数组
            this.subs = []
        }
        // 将 watcher 实例添加到 subs 中(这个方法在 Watcher 类的实现里会用到)
        addSub (sub: Watcher) { 
            this.subs.push(sub)
        }
        // 收集依赖
        depend() {
            // Dep.target 实际上就是当前 Dep 对应的 watcher,我们下文会提及
            if (Dep.target) {
                // 把当前的 dep 实例关联到组件对应的 watcher 上去
                Dep.target.addDep(this)
            }
        }
        // 通知 watcher 对象发生更新
        notify () {
            const subs = this.subs.slice()
            // 这里 subs 的元素是 watcher 实例,逐个调用 watcher 实例的 update 方法
            for (let i = 0, l = subs.length; i < l; i++) {
                subs[i].update()
            }
        }
    }
    

    在 Dep 内部,会维护一个 watcher 队列。

    depend 方法在每次 getter 触发时都会把 watcher 实例和 dep 实例做一次关联。

    在 setter 触发时,dep 实例便会逐个通知每一个和自己有关联的 watcher:我对应的属性发生了更新!进而调度watcher 实例的 update 方法,实现视图更新。

    Watcher

    class Watcher {
        constructor() {
            ...
            // Dep 的 target 属性是有赋值过程的^_^,它是组件对应的 watcher 对象
            Dep.target = this
            ...
        }
        addDep (dep: Dep) {
            ...
            // 把当前的 watcher 推入 dep 实例的 watcher 队列(subs)里去
            dep.addSub(this)
            ...
        }
        update() {
            // 更新视图
        }
    }
    

    这里需要大家注意一点:宏观上看,咱们说“收集依赖”,是指 watcher 去收集自己所依赖的数据属性;不过从实现上来看,实际上是把 watcher 对象推入了 dep 实例的队列里,更像是 dep 在“收集” watcher。

    其实,不管是谁来维护队列、谁“收集”谁,其本质目的都是建立起 dep 和 watcher 间的关联,达到 dep 发生变化后可以立刻通知到 watcher 的目的。


    起源地下载网 » vue响应式原理解析 (进阶必备知识)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元