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

前言

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

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

数据劫持——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重复更新

结尾

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • vue.js响应式原理解析与实现

    从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很好奇vue.js是如何监测数据更新并且重新渲染页面.今天,就我们就来一步步解析vue.js响应式的原理,并且来实现一个简单的demo. 首先,先让我们来了解一些基础知识. 基础知识 Object.defineProperty es5新增了Object.defineProperty这个api,它可以允许我们为对象的属性来设定gette

  • Vue响应式原理Observer、Dep、Watcher理解

    开篇 最近在学习Vue的源码,看了网上一些大神的博客,看起来感觉还是蛮吃力的.自己记录一下学习的理解,希望能够达到简单易懂,不看源码也能理解的效果

  • 代码详解Vuejs响应式原理

    响应式原理 > vuejs中的模型(model)和视图(view)是保持同步的,在修改数据的时候会自动更新视图,这其实依赖于Object.defineProperty方法,所以vuejs不支持IE8及以下版本,vuejs通过劫持getter/setter方法来监听数据的变化,通过getter进行依赖收集,在数据变更执行setter的时候通知视图更新. Object.defineProperty > Object.defineProperty可以定义对象的属性或修改对象的属性 > 目前可以

  • Vue实现双向绑定的原理以及响应式数据的方法

    一.vue中的响应式属性 Vue中的数据实现响应式绑定 1.对象实现响应式: 是在初始化的时候利用definePrototype的定义set和get过滤器,在进行组件模板编译时实现water的监听搜集依赖项,当数据发生变化时在set中通过调用dep.notify进行发布通知,实现视图的更新. 2.数组实现响应式: 对于数组则是通过继承重写数组的方法splice.pop.push.shift.unshift.sort.reverse.等可以修改原数组的方式实现响应式的,但是通过length以及直接

  • 浅谈Vue 数据响应式原理

    前言 Vue的数据响应主要是依赖了Object.defineProperty(),那么整个过程是怎么样的呢?以我们自己的想法来走Vue的道路,其实也就是以Vue的原理为终点,我们来逆推一下实现过程. 本文代码皆为低配版本,很多地方都不严谨,比如 if(typeof obj === 'object')这是在判断obj是否为为一个对象,虽然obj也有可能是数组等其他类型的数据,但是本文为了简便,就直接这样写来表示判断对象,对于数组使用Array.isArray(). 改造数据 我们先来尝试写一个函数

  • 浅谈Vue的响应式原理

    一.响应式的底层实现 1.Vue与MVVM Vue是一个 MVVM框架,其各层的对应关系如下 View层:在Vue中是绑定dom对象的HTML ViewModel层:在Vue中是实例的vm对象 Model层:在Vue中是data.computed.methods等中的数据 在 Model 层的数据变化时,View层会在ViewModel的作用下,实现自动更新 2.Vue的响应式原理 Vue响应式底层实现方法是 Object.defineProperty() 方法,该方法中存在一个getter和s

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

    前言 如果自己去实现数据驱动的模式,如何解决一下几个问题: 通过什么手段去知道我的数据变了? 通过什么东西去同步更新视图? 数据劫持--obvserver 我们需要知道数据的获取和改变,数据劫持是最基础的手段.在Obeserver中,我们可以看到代码如下: Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { // ... }, set:

  • Vue模拟响应式原理底层代码实现的示例

    目录 1.Vue.js功能: 2.Observer.js功能(数据劫持): 3.Compiler.js功能: 4.Dep.js功能: 5.Watcher.js功能: 整体分析Vue的基本结构如下图所示:(备注:完整代码github地址https://github.com/1512955040/MiniVue) 上图中,为我们模拟最小vue的整体结构,首先创建一个vue类型,它负责把data中的成员注入到vue实例中,并且转化成getter/setter,observer的作用是数据劫持,对dat

  • vue数据响应式原理知识点总结

    vue2.0数据响应式原理 对象 Obect.defineproperty 定义对象的属性mjm defineproperty 其实不是核心的为一个对象做数据双向绑定,而是去给对象做属性标签,设置一系列操作权限,只不过属性里的get和set实现了响应式 var ob = { a: 1, b: 2 } //1-对象 2-属性 3-对于属性的一系列配置 Object.defineProperty(ob, 'a' , { //a对象则是ob的绝对私有属性,,默认都是true writable: fal

  • 详解vue数据响应式原理之数组

    目录 src/core/observer/index.js src/core/observer/array.js arrayMethods 总结 src/core/observer/index.js src/core/observer/array.js arrayMethods 当data的数组对象中本来没有某个属性,然后点击按钮动态增加某个属性的时候,此时此属性是没有get和set的,也就是没有响应式机制,如果想要让你动态增加的某个属性有响应式变化,那么就直接在数据的源头给他初始化这个属性,具

  • 详解实现vue的数据响应式原理

    这篇文章主要是给不了解或者没接触过 vue 响应式源码的小伙伴们看的,其主要目的在于能对 vue 的响应式原理有个基本的认识和了解,如果在面试中被问到此类问题,能够知道面试官想让你回答的是什么?「PS:文中如有不对的地方,欢迎小伙伴们指正」 响应式的理解 响应式顾名思义就是数据变化,会引起视图的更新.这篇文章主要分析 vue2.0 中对象和数组响应式原理的实现,依赖收集和视图更新我们留在下一篇文章分析. 在 vue 中,我们所说的响应式数据,一般指的是数组类型和对象类型的数据.vue 内部通过

  • Vue响应式原理及双向数据绑定示例分析

    目录 前言 响应式原理 双向数据绑定 前言 之前公司招人,面试了一些的前端同学,因为公司使用的前端技术是Vue,所以免不了问到其响应式原理和Vue的双向数据绑定.但是这边面试到的80%的同学会把两者搞混,通常我要是先问响应式原理再问双向数据绑定原理,来面试的同学大都会认为是一回事,那么这里我们就说一下二者的区别. 响应式原理 是Vue的核心特性之一,数据驱动视图,我们修改数据视图随之响应更新,就很优雅~ Vue2.x是借助Object.defineProperty()实现的,而Vue3.x是借助

随机推荐