最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

通过图带你深入了解vue的响应式原理

来源:动视网 责编:小采 时间:2020-11-27 21:54:30
文档

通过图带你深入了解vue的响应式原理

通过图带你深入了解vue的响应式原理:前言 如果自己去实现数据驱动的模式,如何解决一下几个问题: 通过什么手段去知道我的数据变了? 通过什么东西去同步更新视图? 数据劫持——obvserver 我们需要知道数据的获取和改变,数据劫持是最基础的手段。在Obeserver中,我们可以看到代码如下:
推荐度:
导读通过图带你深入了解vue的响应式原理:前言 如果自己去实现数据驱动的模式,如何解决一下几个问题: 通过什么手段去知道我的数据变了? 通过什么东西去同步更新视图? 数据劫持——obvserver 我们需要知道数据的获取和改变,数据劫持是最基础的手段。在Obeserver中,我们可以看到代码如下:


前言

如果自己去实现数据驱动的模式,如何解决一下几个问题:

  • 通过什么手段去知道我的数据变了?
  • 通过什么东西去同步更新视图?
  • 数据劫持——obvserver

    我们需要知道数据的获取和改变,数据劫持是最基础的手段。在Obeserver中,我们可以看到代码如下:

    Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    // ...
    },
    set: function reactiveSetter (newVal) {
    // ...
    }
    })

    通过Object.defineProperty这个方法,我们可以在数据发生改变或者获取的时候,插入一些自定义操作。同理,vue也是在这个方法中做依赖收集和派发更新的。

    绑定和更新视图——watcher

    从初始化开始,我们渲染视图的时候,便会生成一个watcher,他是监视视图中参数变化以及更新视图的。代码如下:

    // 在mount的生命钩子中
    new Watcher(vm, updateComponent, noop, {
    before () {
    if (vm._isMounted && !vm._isDestroyed) {
    callHook(vm, 'beforeUpdate')
    }
    }
    }, true /* isRenderWatcher */)

    当然,我们可以保留疑问:

  • watcher是怎么去更新视图的
  • 数据又是怎么和watcher联动起来的
  • 具体的绑定和更新的流程,我们到后续的依赖收集中讲解。

    我们先来讲讲响应式系统中涉及到的设计模式。

    发布订阅模式

    在发布订阅模式中,发布者和订阅者之间多了一个发布通道;一方面从发布者接收事件,另一方面向订阅者发布事件;订阅者需要从事件通道订阅事件

    以此避免发布者和订阅者之间产生依赖关系

    vue的响应式流程

    vue的响应式系统借鉴了数据劫持和发布订阅模式。

    Vue用Dep作为一个中间者,解藕了Observer和Watcher之间的关系,使得两者的职能更加明确。

    那具体是如何来完成依赖收集和订阅更新的呢?

    依赖收集过程

    依赖收集的流程

    举个例子

    <div id="app">
    {{ message }}
    {{ message1 }}
    <input type="text" v-model="message">
    <div @click="changeMessage">改变message</div> 
    </div>
    var app = new Vue({
    el: '#app',
    data: {
    message: '1',
    message1: '2',
    },
    methods: {
    changeMessage() {
    this.message = '2'
    }
    },
    watch: {
    message: function(val) {
    this.message1 = val
    }
    }
    })

    依赖收集流程图:

    如何看懂这个依赖收集流程?关键在watcher代码中:

    get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
    value = this.getter.call(vm, vm)
    } catch (e) {
    // 省略
    } finally {
    if (this.deep) {
    traverse(value)
    }
    popTarget()
    this.cleanupDeps()
    }
    return value
    }

    调用的这个this.getter有两种,一种是key值的getter方法,还有一种是expOrFn,比如mounted中传入的updateComponent。

    如何防止重复收集

    我们不妨想想什么才算是重复收集了?

    笔者想到一种情况:就是dep数组中,出现了多个一样的watcher。

    比如renderWatch就容易被重复收集,因为我们在html模版中,会重复使用data中的某个变量。那他是如何去重的呢?

    1、只有watch在执行get时,触发的取数操作,才会被收集

    Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
    const value = getter ? getter.call(obj) : val
    if (Dep.target) {
    dep.depend()
    // ...
    }
    return value
    },
    set: function reactiveSetter (newVal) {
    // ...
    dep.notify()
    }
    })

    当只有Dep.target这个存在的时候才进行依赖收集。Dep.target这个值只有在watcher执行get方法的时候才会存在。

    2、在dep.depend的时候会判断watch的id

    depend () {
    if (Dep.target) {
    Dep.target.addDep(this)
    }
    }
    addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
    dep.addSub(this)
    }
    }
    }

    我们会发现,在depend过程中,会有一个newDepIds去记录已经存入的dep的id,当一个watcher已经被该dep存过时,便不再会进行依赖收集操作。

    派发更新过程

    收集流程讲完了,不妨在听听更新流程。

    订阅更新的流程

    老例子

    <div id="app">
    {{ message }}
    {{ message1 }}
    <input type="text" v-model="message">
    <div @click="changeMessage">改变message</div> 
    </div>
    var app = new Vue({
    el: '#app',
    data: {
    message: '1',
    message1: '2',
    },
    methods: {
    changeMessage() {
    this.message = '3'
    }
    },
    watch: {
    message: function(val) {
    this.message1 = val
    }
    }
    })

    依赖收集的最终结果:

    当触发click事件的时候,便会触发订阅更新流程。

    订阅更新流程图:

    当renderWatch执行更新的时候,回去调用beforeUpdate生命钩子,然后执行patch方法,进行视图的变更。

    如何防止重复更新

    如何去防止重复更新呢?renderWatch会被很多dep进行收集,如果视图多次渲染,会造成性能问题。

    其实问题的关在在于——queueWatcher

    在queueWatcher中有两个操作:去重和异步更新。

    function queueWatcher (watcher) {
    const id = watcher.id
    if (has[id] == null) {
    has[id] = true
    queue.push(watcher)
    // ...
    if (!waiting) {
    waiting = true
    // ...
    nextTick(flushSchedulerQueue)
    }
    }
    }

    其实queueWatcher很简单,将所有watch收集到一个数组当中,然后去重。

    这样至少可以避免renderWatch频繁更新。

    比如上述例子中的,message和message1都有一个renderWatch,但是只会执行一次。

    异步更新也可以保证当一个事件结束之后,才会触发视图层的更新,也能防止renderWatch重复更新

    结尾

    文章讲述了响应式流程的原因,代码细节并未深入,

    文档

    通过图带你深入了解vue的响应式原理

    通过图带你深入了解vue的响应式原理:前言 如果自己去实现数据驱动的模式,如何解决一下几个问题: 通过什么手段去知道我的数据变了? 通过什么东西去同步更新视图? 数据劫持——obvserver 我们需要知道数据的获取和改变,数据劫持是最基础的手段。在Obeserver中,我们可以看到代码如下:
    推荐度:
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top