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 ...
随机推荐
- mysql添加外键无法成功的原因
最近很忙,碰到很多问题都忘了发上来做个记录,现在又忘了,FUCK,现在碰到一个问题, 就是mysql添加外键总是无法成功,我什么都试了,就是没注意signed和unsigned,FUCK,因为我用my ...
- nginx内容
nginx工作在7层:web server(静态内容 static contents)web reverse proxy(反向代理http,https,mail),cache(带缓存功能) proxy ...
- React props
class WebSite extends React.Component { constructor() { super(); this.state = { name: "菜鸟教程&quo ...
- element-ui Form表单校验
使用element-ui自带的表单校验,注意几个点: 1.el-form通过rules属性,绑定校验规则 2.el-form-item的prop属性,设置为需要校验的字段名 3.提交后二次校验 sav ...
- Django03-视图系统views
一.编写视图 一个视图函数,是一个简单的Python函数,它接受web请求,并且返回web响应.响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片. . . 是 ...
- SHA-256算法和区块链原理初探
组内技术分享的内容,目前网上相关资料很多,但读起来都不太合自己的习惯,于是自己整理并编写一篇简洁并便于(自己)理解和分享的文章. 因为之前对密码学没有专门研究,自己的体会或理解会特别标注为" ...
- Oracle 11gR2 客户端windows 10安装后PL/SQL配置
操作系统:windows 10 软件:Oracle 11gR2 客户端 (64 bit) PLSQL Developer 13 (64 bit) 注意:PLSQL与oracle客户端版本要一致 1. ...
- MySQL 还原
## sql 还原:mysql -default-character-set=utf8 -h127.0.0.1 -uroot -pxxxxxx test2 < /data/test/db/201 ...
- week05 codelab01 Babel ES6 webpack Nodejsserver等
Babel 他出现的原因就是很多浏览器还未完全兼容ES6 需要将你写的ES6的内容转换成ES5让浏览器兼容运行 ES5和ES6相比出现很多新内容 比如拼接字符串 ES6可以` ` 里面如果引用变量就用 ...
- 浅谈Tomcat和Servlet
本文浅谈下对Tomcat和Servlet总体的理解,初学时有用过一段时间,但当时疲于应对如何xml配置和使用,对他们的理解就像是一个黑匣子.现在回顾一下帮助自己加深网络的理解.开始还是先推荐我看的文章 ...