学习 vue 源码 -- 响应式原理
概述
由于刚开始学习 vue 源码,而且水平有限,有理解或表述的不对的地方,还请不吝指教。
vue 主要通过 Watcher、Dep 和 Observer 三个类来实现响应式视图。另外还有一个 scheduler 来进行调度,本次暂时不做讨论。
Watcher 和 Dep 是订阅者和发布者的关系,每个 Watcher 可以订阅多个 Dep,而每个 Dep 也可以被多个 Watcher 订阅。当 Observer 监听的数据发生改变时,相应的 Dep 就会触发其订阅者 Watcher 更新视图。
下面通过一个简单的例子来说明其实现流程和原理:
<div id="app">
{{someVar}}
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
someVar: 'init'
},
mounted(){
setTimeout(() => this.someVar = 'changed', 3000)
}
})
</script>
页面初始会显示 "init" 字符串,3秒钟之后,会更新为 "changed" 字符串。
为了便于理解,将整个流程分为三个阶段:
- 初始化data,这个阶段 vue 通过 Observer 监听 data 对象,并将普通的 someVar 属性代理为 get\set 属性
- 初次挂载el,这个阶段 vue 使用默认的 someVar 数据渲染视图,并将 watcher 添加到 dep 的订阅者列表
- someVar 更改触发视图更新,这个阶段 someVar 被赋予了新值,vue 根据 watcher 和 dep 的订阅关系触发视图的更新
下面我们来逐步分析这三个阶段的流程。
第一阶段
主要流程
new Vue(options) => vm._init(options) => initState(vm) => initData(vm) => observe(data) => new Observer(data) => defineReactive(data, key, value)
说明
初始化操作会监听 data 对象,对其每一个属性调用 defineReactive() 方法,将其改造为响应式属性,代码如下(去掉了不影响表述主流程的代码,以便能更清晰的抓住重点):
/**
* Define a reactive property on an Object.
*/
export function defineReactive (
obj: Object,
key: string,
val: any
) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
dep.depend()
return val
},
set: function reactiveSetter (newVal) {
if (newVal === val || (newVal !== newVal && val !== val)) {
return
}
val = newVal
dep.notify()
}
})
}
可以看到,当 get() 方法执行的时候,会调用 dep.depend() ;当 set() 方法执行时,会调用 dep.notify()。后面我们会看到这两个方法的作用。
第二阶段
主要流程
vm.$mount(el) => mountComponent(vm, el) => new Watcher(vm, updateComponent) => watcher.get() => updateComponent() => vm._update(vm._render())
说明
vm._render() 调用 vm.$options.render() 方法生成 vnode,vm._update() 方法根据 vnode 对视图做更新。
vm.$options.render() 方法是在 $mount() 方法中生成的,生成后的代码如下:
(function() {
with (this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_v("\n " + _s(someVar) + "\n ")])
}
})
可以看到,代码中会使用到 vm.someVar 属性,而该属性最终会代理到之前定义的响应式属性上,从而调用其 get() 方法,进而调用 dep.depend() 方法将 watcher 添加到订阅者列表。
dep.depend() => Dep.target.addDep(dep) => dep.addSub(watcher) => dep.subs.push(watcher)
dep.subs 就是 dep 的订阅者列表,通过这个流程,就建立起了 dep 和 watcher 之间的订阅关系。
其中,Dep.target 就是当前的 watcher,因为在 watcher.get() 方法执行时,有如下流程:
watcher.get() => pushTarget(watcher) => Dep.target = watcher
第三阶段
3秒之后,vm.someVar 被赋予了新的值,从而最终会调用到响应式属性的 set() 方法,进而调用 dep.notify(),触发 watcher 更新视图。
主要流程
dep.notify() => watcher.update() => watcher.run() => watcher.get() => watcher.getter.call(vm, vm) == updateComponent() => vm._update(vm._render())
说明
watcher 的 getter() 方法就是第二阶段 new Watcher(vm, updateComponent) 中的 updateComponent 方法,可以看到,通过这个流程,视图得到了更新。
以上就构成了一个响应式视图模型,其核心是利用 defineProperty() 方法将普通属性转换为带有钩子的 set\get 属性,从而实现了数据监听。
学习 vue 源码 -- 响应式原理的更多相关文章
- 手牵手,从零学习Vue源码 系列一(前言-目录篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 手牵手,从零学习Vue源码 系列三(虚拟DOM篇) 陆续更新中... 预计八月中旬更新 ...
- 手牵手,从零学习Vue源码 系列二(变化侦测篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...
- 一起学习vue源码 - Object的变化侦测
作者:小土豆biubiubiu 博客园:www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d 简书:h ...
- Vue数据绑定和响应式原理
Vue数据绑定和响应式原理 当实例化一个Vue构造函数,会执行 Vue 的 init 方法,在 init 方法中主要执行三部分内容,一是初始化环境变量,而是处理 Vue 组件数据,三是解析挂载组件.以 ...
- Vue 2.0 与 Vue 3.0 响应式原理比较
Vue 2.0 的响应式是基于Object.defineProperty实现的 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 prop ...
- 一起学习vue源码 - Vue2.x的生命周期(初始化阶段)
作者:小土豆biubiubiu 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/58c61b4361ff4b005d9e8 ...
- vue 数据劫持 响应式原理 Observer Dep Watcher
1.vue响应式原理流程图概览 2.具体流程 (1)vue示例初始化(源码位于instance/index.js) import { initMixin } from './init' import ...
- 手写实现vue的MVVM响应式原理
文中应用到的数据名词: MVVM ------------------ 视图-----模型----视图模型 三者与 Vue 的对应:view 对应 te ...
- 学习Vue源码前的几项必要储备(二)
7项重要储备 Flow 基本语法 发布/订阅模式 ES6+ 语法 原型链.闭包 函数柯里化 event loop 接上讲 聊到了ES6的几个重要语法,加下来到第四点继续开始. 4.原型链.闭包 原型链 ...
随机推荐
- 【转】无法获得锁 /var/lib/dpkg/lock - open (11: 资源暂时不可用) ubuntu 安装vim 及遇到的错误处理
今天,处理完问题,闲来无事,打算在虚拟机中的Ubuntu中练习shell脚本编写. 无奈,虚拟机系统所装的只有vi,这个编辑软件对于我们来说还是比较不习惯的,所以打算安装vim.好了,闲言少叙. 安装 ...
- Ability
Base:网络安全,sklearn(ML),日本語,企业存储 Branch1:自动化,Git Branch2:HW系统架构 Branch3:shadowsocks源码:
- noiac26 T1 (并查集)
考虑计算每个位置的数作为最小值时有多少种情况 方便起见,以位置为第二关键字比较大小,这样就不会出现“相同的”数 可以方便地计算出以i位置为最小值的区间端点的可行位置:[A,i],[i,B] 这是我选的 ...
- [ZJOI2007]仓库建设(斜率优化)
L公司有N个工厂,由高到底分布在一座山上. 工厂1在山顶,工厂N在山脚. 由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在露天,以节省费用. 突然有一天,L公司的总裁L先生接到气象部 ...
- Diagnostic Trouble Code诊断故障码
所有电子控制单元(ECU)都会根据整车厂规范要求对相关故障进行记录,并储存在NVM(也称作EEPROM)相当于平常电脑上硬盘里.除故障代码外,还会记录故障发生时相关整车情况(如供电电压,环境温度 ...
- dbForge Studio for MySQL V8.0 Enterprise
上篇文章:JetBrains全家桶破解思路(最新更新:2018-12-24) 最适合从SQLServer转向MySQL的人使用(用起来基本上差不多) 最适合Net开发人员的MySQL IDE (不装V ...
- 如何刻录cd音乐
用nero,选择cd,音乐光盘(第一个)可以添加入wav,MP3等.刻录即可.
- docker file 示例
报错 Cannot connect to the Docker daemon. Is the docker daemon running on this host? 这个错误只要输入docker -d ...
- CF235D Graph Game
CF235D Graph Game 好题 树? 考虑每个点被计算多少次 但是和当前分治中心有关系的 所以,f(a,b),对于a作为中心时候,和b相连的概率 也就是两者必然分离,最后一次连在一起的时候, ...
- A1018. Public Bike Management
There is a public bike service in Hangzhou City which provides great convenience to the tourists fro ...