Vue源码解析---数据的双向绑定
本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定
核心思想是ES5的Object.defineProperty()和发布-订阅模式
整体结构
- 改造Vue实例中的data,通过Object.defineProperty()将其所有属性设置为访问器属性
- 对每个属性添加Observer,并在observer中添加订阅者对象序列Dep
- 添加订阅者对象Watcher,每次初始化的时候添加到对应data属性中的Dep之中
所有,我们从代码的角度将整体分为三个部分:监听数据变化、管理订阅者、订阅者
监听数据变化
使用ES5中的Object.defineProperty将data中的属性修改为访问者属性
// Dep用于订阅者的存储和收集,将在下面实现
import Dep from 'Dep'
// Observer类用于给data属性添加set&get方法
export default class Observer{
constructor(value){
this.value = value
this.walk(value)
}
walk(value){
Object.keys(value).forEach(key => this.convert(key, value[key]))
}
convert(key, val){
defineReactive(this.value, key, val)
}
}
export function defineReactive(obj, key, val){
// 用于存放某个属性的所有订阅者
var dep = new Dep()
// 给当前属性的值添加监听
var chlidOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=> {
console.log('get value')
// 如果Dep类存在target属性,将其添加到dep实例的subs数组中
// target指向一个Watcher实例,每个Watcher都是一个订阅者
// Watcher实例在实例化过程中,会读取data中的某个属性,从而触发当前get方法
if(Dep.target){
dep.addSub(Dep.target)
}
return val
},
set: (newVal) => {
console.log('new value setted')
if(val === newVal) return
val = newVal
// 对新值进行监听
chlidOb = observe(newVal)
// 通知所有订阅者,数值被改变了
dep.notify()
}
})
}
export function observe(value){
// 当值不存在,或者不是复杂数据类型时,不再需要继续深入监听
if(!value || typeof value !== 'object'){
return
}
return new Observer(value)
}
管理订阅者
对订阅者进行收集,存储和通知
export default class Dep{
constructor(){
this.subs = []
}
addSub(sub){
// 在收集订阅者的时候,需要对subs中的订阅者进行去重,这边不详细解析
this.subs.push(sub)
}
notify(){
// 通知所有的订阅者(Watcher),触发订阅者的相应逻辑处理
this.subs.forEach((sub) => sub.update())
}
}
订阅者
每个watcher对象都是对data中每个属性的订阅,是多对一的关系,每个watcher只能对应一个data属性,而一个data属性可以对应多个watcher
import Dep from 'Dep'
export default class Watcher{
constructor(vm, expOrFn, cb){
this.vm = vm // 被订阅的数据一定来自于当前Vue实例
this.cb = cb // 当数据更新时想要做的事情
this.expOrFn = expOrFn // 被订阅的数据
this.val = this.get() // 维护更新之前的数据
}
// 对外暴露的接口,用于在订阅的数据被更新时,由订阅者管理员(Dep)调用
update(){
this.run()
}
run(){
const val = this.get()
if(val !== this.val){
this.val = val;
this.cb.call(this.vm)
}
}
get(){
// 当前订阅者(Watcher)读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者
Dep.target = this
const val = this.vm._data[this.expOrFn]
// 置空,用于下一个Watcher使用
Dep.target = null
return val;
}
}
实例
下边我们创建一个简易的Vue来实际运行下对数据的监听
import Observer, {observe} from 'Observer'
import Watcher from 'Watcher'
export default class Vue{
constructor(options = {}){
// 简化了$options的处理
this.$options = options
// 简化了对data的处理
let data = this._data = this.$options.data
// 将所有data最外层属性代理到Vue实例上
Object.keys(data).forEach(key => this._proxy(key))
// 监听数据
observe(data)
}
// 对外暴露调用订阅者的接口,内部主要在指令中使用订阅者
$watch(expOrFn, cb){
new Watcher(this, expOrFn, cb)
}
_proxy(key){
Object.defineProperty(this, key, {
configurable: true,
enumerable: true,
get: () => this._data[key],
set: (val) => {
this._data[key] = val
}
})
}
}
import Vue from './Vue';
let demo = new Vue({
data: {
'a': {
'ab': {
'c': 'C'
}
},
'b': [
'bb': 'BB',
'bbb': 'BBB'
],
'c': 'C'
}
});
demo.$watch('c', () => console.log('c is changed'));
// get value
demo.$watch('a.ab', () => console.log('a.ab is changed'));
demo.$watch('b', () => console.log('b is changed'));
// get value
demo.c = 'CCC';
// new value setted
// get value
// c is changed
demo.a.ab = 'AB';
// get value
// new value setted
demo.b.push({'bbbb': 'BBBB'});
// get value
根据实例的输出结果,我们很奇怪的发现,只有对简单的数据监听才能实现数据双向绑定。
demo.$watch('a.ab', () => console.log('a.ab is changed'))注册订阅者并没有调用getterdemo.a.ab = 'AB'有监听到数据的变化,并没有调用对应的callbackdemo.b.push({'bbbb': 'BBBB'})对数值进行操作,并没有调用对应的callback
这是为什么呢?因为我们对数据的监听的实现,目前仅限于简单对应,对于某个属性内部有更多复杂属性时,就无能为力了。
为了实现进一步对数据和复杂对象的监听,请戳Vue源码解析---数组的双向绑定和Vue源码解析---复杂队形的双向绑定
Vue源码解析---数据的双向绑定的更多相关文章
- 【VUE】Vue 源码解析
Vue 源码解析 Vue 的工作机制 在 new vue() 之后,Vue 会调用进行初始化,会初始化生命周期.事件.props.methods.data.computed和watch等.其中最重要的 ...
- 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现
写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...
- 【vuejs深入二】vue源码解析之一,基础源码结构和htmlParse解析器
写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. vuejs是一个优秀的前端mvvm框架,它的易用性和渐进式的理念可以使每一个前端开发人员感到舒服,感到easy.它内 ...
- Vue源码解析之nextTick
Vue源码解析之nextTick 前言 nextTick是Vue的一个核心功能,在Vue内部实现中也经常用到nextTick.但是,很多新手不理解nextTick的原理,甚至不清楚nextTick的作 ...
- vue中如何实现数据的双向绑定
vue中如何实现数据的双向绑定 实现视图变化数据跟着变:分两步,上面get中的为第二步(即再次读取的时候会调用get方法得到之前设置的值,以此来实现动态改变) 由于直接写obj.name = this ...
- Vue源码解析之数组变异
力有不逮的对象 众所周知,在 Vue 中,直接修改对象属性的值无法触发响应式.当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变. 这是什么原因? 原因在于: Vue 的响应式 ...
- Vue源码解析(一):入口文件
在学习Vue源码之前,首先要做的一件事情,就是去GitHub上将Vue源码clone下来,目前我这里分析的Vue版本是V2.5.21,下面开始分析: 一.源码的目录结构: Vue的源码都在src目录下 ...
- Vue源码解析:AST语法树转render函数
开始 今天要说的代码全在codegen文件夹中,在说实现原理前,还是先看个简单的例子! <div class="container"> <span>{{ms ...
- VUE源码解析心得
解读vue源码比较好奇的几个点: VUE MVVM 原理 http://www.cnblogs.com/guwei4037/p/5591183.html https://cn.vuejs.org/v2 ...
随机推荐
- 很不幸,装win10和Ubuntu双系统还是入坑了
具体问题描述:在安装Ubuntu的时候,没有发现有与windows共存的提示,直接说本计算机没有安装系统.在安装完成后,发现通过BIOS只有进入Ubuntu的启动项,没有进入Windows的启动项. ...
- js学习2
1.打开新窗体 -window.open([URL], [窗口名称], [参数字符串]) - 窗口名称: _blank:在新窗口显示目标网页 _self:在当前窗口显示目标网页 _top:框架网页中在 ...
- leetcode top 100 题目汇总
首先表达我对leetcode网站的感谢,与高校的OJ系统相比,leetcode上面的题目更贴近工作的需要,而且支持的语言广泛.对于一些比较困难的题目,可以从讨论区中学习别人的思路,这一点很方便. 经过 ...
- MATLAB入门笔记
% MATLAB:MATLAB 为 Matrix Laboratory ,用来处理矩阵可编程可实现算法逻辑的计算工具% % eg:1 绘制正弦和余弦曲线x=[0:0.1:4*pi]; %建立角度向量p ...
- MFC 读写文件
写文件: #include<fstream> CStdioFile file; file.Open(_T("parametertable.txt"), CFile::m ...
- ReactiveX 学习笔记(27)使用 RxJS + Vue.js 进行 GUI 编程
课题 程序界面由3个文本编辑框和1个文本标签组成. 要求文本标签实时显示3个文本编辑框所输入的数字之和. 文本编辑框输入的不是合法数字时,将其值视为0. 3个文本编辑框的初值分别为1,2,3. 创建工 ...
- UWP简单测试
随便写下,试试.Net Core与UWP开发,后台WCF XAML <Page x:Class="App3.MainPage" xmlns="http://sche ...
- 20175213 2018-2019-2 《Java程序设计》第9周学习总结
教材学习内容总结 根据课本的介绍下载了MySQL和Navicat for MySQL并成功对后者进行破解: 操作方法可分为三步: 一.配置程序--让我们程序能找到数据库的驱动jar包 1.把.jar文 ...
- 基于WebGL架构的3D可视化平台ThingJS-搭建设备管理系统
国内高层建筑不断兴建,它的特点是高度高.层数多.体量大.面积可达几万平方米到几十万平方米.这些建筑都是一个个庞然大物,高高的耸立在地面上,这是它的外观,而随之带来的内部的建筑设备也是大量的.为了提高设 ...
- JAVA8 Stream集合操作:中间方法和完结方法
StreamLambda为java8带了闭包,这一特性在集合操作中尤为重要:java8中支持对集合对象的stream进行函数式操作,此外,stream api也被集成进了collection api, ...