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 ...
随机推荐
- window Linux 双系统安装
我是先安装的win10,然后在其基础上又安装了Ubuntu 16.04,为了今后再次安装方便,这里记录一下安装过程. 我在安装时主要参考了文章:https://blog.csdn.net/flyyuf ...
- elk报警监控之sentinl 钉钉+邮件告警
注:我的elk sentinl版本都是6.5.1 前期知识 es的查询语法.es watcher使用方法. https://www.cnblogs.com/pilihaotian/p/5830754. ...
- install django采坑
1. install python 3 2. install pip 3. install virtual enviroment : python -m venv myvenv 4. 切换到virt ...
- 64位的windows服务安装问题
需要使用64位的安装exe文件才可以. @echo offC:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe -i &quo ...
- jenkins grunt 自动构建流程
1. grunt生成的压缩文件不建议上传到svn,src里的源码和grunt,npm的配置文件保存在svn里就够了2. grunt有watch任务,src里的文件改变了可以自动执行任务,比如压缩,3. ...
- HttpServletRequest 获取cookie
request.getHeader("cookie") 得到的是a=b,c=d Cookie[] cookies = request.getCookies(); 访问方在heade ...
- python表格导出--xlwt的使用
xlwt可以用来导出excel表,下面介绍一下它的用法: 1. 安装xlwt模块 pip install xlwt 2. 使用xlwt模块:后端接口编写 import xlwt #导出表格接口 def ...
- 字节、字、bit、Byte、byte的关系区分
1.位(bit) 来自英文bit,音译为"比特", 表示二进制位.位是计算机内部数据存储最小单位,11010100是一个8位二进制数.一个二进制位只可以表示 ...
- python_1_基础知识
数据类型: 整数 浮点数 字符串 布尔值:True/False 空值:None 变量 常量 int(整型):在Python3里不再有long类型了,全都是int -2**63-2**63-1即-922 ...
- Java I/O (1) - 输入/输出流
先说概念: Java API中,可以从其中读入一个字节序列的对象叫做输入流,可以向其中写入一个字节序列的对象叫做输出流.这些字节序列的来源地 和 目的地 可以文件.网络连接甚至内存块.抽象类Input ...