学习 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.原型链.闭包 原型链 ...
随机推荐
- 【php】php数组相关操作函数片段
下面这些都是我在工作中用到的函数,现在整理下. 判断是否是一个数组 function _is_array($value){ if (is_array($value)) { return true; } ...
- 跟着 underscore 学节流
更多内容请参考:我的新博客 在上一篇文章中,我们了解了为什么要限制事件的频繁触发,以及如何做限制: debounce 防抖 throttle 节流 上次已经说过防抖的实现了,今天主要来说一下节流的实现 ...
- (六)Oracle 的 oracle表查询关键字
参考:http://www.hechaku.com/Oracle/oracle_tables2.html 1.使用逻辑操作符号问题:查询工资高于500或者是岗位为manager的雇员,同时还要满足他们 ...
- 第一篇:安装Android Studio问题及其解决方案
ubuntu18.04配置android studio3.2.1环境 1.JDK安装与配置:https://www.cnblogs.com/yuanbo123/p/5819564.html(按照文档操 ...
- matlab图片 latex显示
matlab图片另存为bmp格式,用在线网站转换格式为png. 莫要直接另存为jpg格式,在latex中显示,否则图片显示的质量不高.
- Jquery Mobile表单
三个前提: 1.每个form必须设置method和action属性 2.每个form必须有页面范围内唯一的id标识 3.每个form必须有一个label标签,通过设置它的for属性来匹配元素的id & ...
- Struts2上传文件出错
出现错误: Error setting expression 'myFile' with value '[Ljava.lang.String;@47fb02e8' 解决方法: 这是由于没有设置 < ...
- 【清北学堂2018-刷题冲刺】Contest 3
比较数学的一场,难度稍大. Task 1:数数 [问题描述] fadbec 很善于数数,⽐如他会数将a 个红球,b 个黄球,c 个蓝球,d个绿球排成⼀列,求出任意相邻不同⾊的方案数⽬. 现在R ...
- (BFS 二叉树) leetcode 515. Find Largest Value in Each Tree Row
You need to find the largest value in each row of a binary tree. Example: Input: 1 / \ 3 2 / \ \ 5 3 ...
- redis主从复制配置(1)
我们来配置一个一主两从的服务,根据前面写的已经配置好的redis基础上进行主从配置 一:进入redis的配置目录 cd /usr/local/redis 创建下面3个目录,命令为:make -p /u ...