学习 vue 源码 -- 响应式原理
2017-08-25 16:57
901 查看
概述
由于刚开始学习 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.js学习 Item12 – 内部响应式原理探究
- Vue 源码解析:深入响应式原理
- Vue.js学习 Item12 – 内部响应式原理探究
- vue学习总结:响应式系统&vue实例
- Vue源码学习之初始化模块init.js解析
- Mysql源码学习——用户认证原理与实现
- Vue 2.3.4源码分析之双向绑定原理
- Vue2.0源码阅读笔记--双向绑定实现原理
- mybatis源码学习--mybatis懒加载内部原理
- Vue的响应式原理
- 从Clojure的源码学习STM对引用变量的版本控制原理
- Vue.js源码学习三 —— 事件 Event 学习
- Vue源码学习(一)———数据双向绑定 Observer
- spring源码学习之路---IOC实现原理(三)
- Vue.js 源码学习笔记 -- 分析前准备1 -- vue三大利器
- Vue学习之源码分析--从Vue.js源码角度再看数据绑定(三)
- Vue.js源码学习四 —— 渲染 Render 初始化过程学习
- Netty学习之旅------源码分析Netty解码编码器实现原理
- Vue.js内部响应式原理探究