深入Vue.js响应式原理

一、创建一个Vue应用

new Vue({
data() {
return {
name: 'yjh',
};
},
router,
store,
render: h => h(App),
}).$mount('#app');

二、实例化一个Vue应用到底发生了什么?

  1. this._init()
  2. callHook(vm, 'beforeCreate')
  3. observe(vm._data)
vm._data = vm.$options.data()

proxy(vm, _data, key) 代理到vm上访问

function proxy(vm, _data, key)() {
Object.defineProperty(target, key, {
get() {
return vm._data.key
},
set(val) {
vm._data.key = val
}
})
}
  1. callHook(vm, 'created')
  2. mountComponent(vm.$mount执行后执行mountComponent)
  3. callHook(vm, 'beforeMount')
  4. new Watcher(vm, updateComponent)
const updateComponent = () => {
// 创建虚拟dom
const vnode = vm._render() // 创建虚拟dom的过程等同于如下代码行
// const vnode = vm.$options.render.call(vm, vm.$createElement) // 更新$el
vm._update(vnode)
}
  1. callHook(vm, 'mount')

在以上发生的行为当中,第3步与第7步两者相辅相成;也是我们最需要关心的,弄清楚这两者,vue响应式原理就基本掌握了

三、如何追踪数据变化

我们都知道 数据发生变化视图也随之更新,那么首先我们得知道如何监听数据的变化

class Observer {
constructor(value) {
this.value = value
this.walk(value)
} walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
function defineReactive(obj, key) {
Object.defineProperty(obj, key, {
get() {
// 数据被访问
return obj.key
},
set(val) {
if (val === obj.key) {
return
}
// 数据更新了
obj.key = val
}
})
}

四、定义一个发布订阅的Dep类

当我们在创建虚拟dom的过程中,也就是执行vm.$createElement方法,可能会在多个地方使用到同一个数据字段(如:vm.name),即多个订阅者订阅了name的更新,因此在Vue中定义了一个发布订阅的Dep类

class Dep {
constructor() {
this.subs = []
} addSub(sub) {
this.subs.push(sub)
} depend() {
if (Dep.target) {
this.addSub(Dep.target)
}
} notify() {
this.subs.forEach(sub => sub.update())
} removeSub(sub) {
const i = this.subs.findIndex(sub)
if (i > -1) {
this.subs.splice(i, 1)
}
}
}

五、数据订阅者

订阅数据更新的到底是谁,我们先看看如下场景

<!-- 场景1 -->
<div>名字:{{ userInfo.name }},全名:{{ fullName }}</div>
export default {
data() {
return {
userInfo: {
name: 'junhua',
},
}
},
mounted() {
// 场景2
this.$watch('name', (newVal, val) => {
// ...
})
},
// 场景2
watch: {
name(newVal, val) {
// ...
}
},
computed() {
// 场景3
fullName() {
return `yang${this.userInfo.name}`
}
}
}

从上面示例代码看,订阅数据更新的场景有:

  1. 模版插值 :new Watcher(vm, updateComponent)数据发生变化,更新组件
  2. vm.$watch : 监听单个数据做一些逻辑操作
  3. computed使用场景:计算属性

因此数据订阅者包含一个参数expOrFn([Function|String]),数据更新后需要执行的callback,如下:

class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.cb = cb || () => {}
this.value = this.get()
} get() {
Dep.target = this
const value = this.getter.call(this.vm, this.vm)
Dep.target = undefined
return value
} update() {
const val = this.value
const newVal = this.get()
this.cb.call(this.vm, newVal, val)
}
}

六、最终的观察者Observer

class Observer {
constructor(value) {
this.value = value
this.walk(value)
} walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i],)
}
}
} function defineReactive(obj, key) {
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
// 依赖收集,收集订阅者Watcher实例
dep.depend()
// 数据被访问
return obj.key
},
set(val) {
if (val === obj.key) {
return
}
// 数据更新了
obj.key = val
// 通知订阅者Watcher实例更新
dep.notify()
}
})
}

七、总结

我们再来回顾下实例化Vue应用的最重要的两点

observe(vm._data)
// vm.$mount()
const componentUpdateWatcher = new Watcher(vm, updateComponent)

updateComponent在更新渲染组件时,会访问1或多个数据模版插值,当访问数据时,将通过getter拦截器把componentUpdateWatcher作为订阅者添加到多个依赖中,每当其中一个数据有更新,将执行setter函数,对应的依赖将会通知订阅者componentUpdateWatcher执行update,即执行updateComponent;至此Vue数据响应式目的已达到,再来看官网的这张图片就很好理解了

github地址   文章来源:博客园-杨君华,转载请注明出处:杨君华

深入Vue响应式原理的更多相关文章

  1. 深度解析 Vue 响应式原理

    深度解析 Vue 响应式原理 该文章内容节选自团队的开源项目 InterviewMap.项目目前内容包含了 JS.网络.浏览器相关.性能优化.安全.框架.Git.数据结构.算法等内容,无论是基础还是进 ...

  2. Vue源码--解读vue响应式原理

    原文链接:https://geniuspeng.github.io/2018/01/05/vue-reactivity/ Vue的官方说明里有深入响应式原理这一节.在此官方也提到过: 当你把一个普通的 ...

  3. 详解Vue响应式原理

    摘要: 搞懂Vue响应式原理! 作者:浪里行舟 原文:深入浅出Vue响应式原理 Fundebug经授权转载,版权归原作者所有. 前言 Vue 最独特的特性之一,是其非侵入性的响应式系统.数据模型仅仅是 ...

  4. vue响应式原理,去掉优化,只看核心

    Vue响应式原理 作为写业务的码农,几乎不必知道原理.但是当你去找工作的时候,可是需要造原子弹的,什么都得知道一些才行.所以找工作之前可以先复习下,只要是关于vue的,必定会问响应式原理. 核心: / ...

  5. vue响应式原理解析

    # Vue响应式原理解析 首先定义了四个核心的js文件 - 1. observer.js 观察者函数,用来设置data的get和set函数,并且把watcher存放在dep中 - 2. watcher ...

  6. 浅析Vue响应式原理(三)

    Vue响应式原理之defineReactive defineReactive 不论如何,最终响应式数据都要通过defineReactive来实现,实际要借助ES5新增的Object.definePro ...

  7. 深入解析vue响应式原理

    摘要:本文主要通过结合vue官方文档及源码,对vue响应式原理进行深入分析. 1.定义 作为vue最独特的特性,响应式可以说是vue的灵魂了,表面上看就是数据发生变化后,对应的界面会重新渲染,那么响应 ...

  8. 浅谈vue响应式原理及发布订阅模式和观察者模式

    一.Vue响应式原理 首先要了解几个概念: 数据响应式:数据模型仅仅是普通的Javascript对象,而我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率. 双向绑定:数据改变,视图 ...

  9. Vue响应式原理及总结

    Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty 中的访问器属性中的 get 和 set 方法,data 中声明的属性都被添加了访问器属性,当读取 ...

随机推荐

  1. C++传递不定参函数

    定义不定参数函数,要用到下面这些宏: va_start(ap, farg): 初始化一个va_list变量ap,farg是第一个形参 va_arg(ap, type): 获取(下)一个type类型的参 ...

  2. 第6章 初识MyBatis

    6.1 什么是MyBatis Mybatis(前身是iBatis)是一个支持普通SQL查询.存储过程以及高级映射的持久层框架.MyBatis框架也被称为ORM(Object/Relational Ma ...

  3. org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 19

    在Maven上部署Struts2时,突然发生了如下错误,但是tomcat还能运行,发送错误的原因时Struts2的版本太高,和tomcat7不兼容. 开始Struts2用了2.5.20版本,后来换成2 ...

  4. 0.学习springmvc补充

    一.我的截图中的程序错误: 其中的路径写的是:"/user/testString" 这样写会错误当tomcat中配置的根目录为"/"或” “成功,但当配置为”x ...

  5. Python 检查代码占用内存 工具和模块

    只介绍简单的使用, 更多使用方法请查看官方文档 tracemalloc 官方文档 tracemalloc文档地址 使用 import tracemalloc tracemalloc.start() # ...

  6. cf1187解题报告

    cf1187解题报告 cf A 去掉都有的,剩下的取最大值+1 #include <bits/stdc++.h> #define int long long using namespace ...

  7. GitBook github

    创建一个新的仓库 创建一个新文件,名为SUMMARY.md 创建一本书首先进入gitbook的官网:https://www.gitbook.com/ 创建账户https://github.com/ 在 ...

  8. 如何把Eclipse项目迁移到AndroidStudio(如何把项目导入安卓)--这我很困惑

    学习android对我来说,就是兴趣,所以我以自己的兴趣写出的文章,希望各位多多支持!多多点赞,评论讨论加关注. 大佬必备功能. 把Eclipse项目迁移到AndroidStudio 现在就叫你如何把 ...

  9. Java中Set真的是无序的吗?

    我们经常听说List是有序且可重复的,Set是无序且不重复的.这是一个误区,这里所说的顺序有两个概念,一是按照添加的顺序排列,二是按,照自然顺序a-z排列.Set并不是无序的传统所说的Set无序指的是 ...

  10. eclipse修改代码没用、debug无法进断点、

    设置eclipse自动编译代码 处理tomcat不进断点问题 添加需要debug的项目 以上操作还是不行的话   可以把tomcat删除重新添加试试看