数据绑定
- 数据绑定
- 一旦更新了data中的某个属性数据,所有界面上直接(this.xxx='aaa')或间接(计算属性或方法)使用了此属性的节点都会更新
- 数据劫持
- 数据劫持是vue中用来实现数据绑定的一种技术
- 基本思想:通过defineProperty()来监视data中所有属性(任意层次)数据的变化,一旦变更就去更新界面
Dep与Watcher的关系
- Dep何时创建?—— 初始化时给data的属性进行数据劫持时创建的
- 有几个?—— 与data中的属性一一对应
- 结构是什么?—— id:标识,subs:[]相关的n个watcher容器
- Watcher何时创建?—— 初始化时解析大括号表达式/一般指令时创建
- 有几个?—— 与模板表达式(不包含事件指令)一一对应
- 结构是什么?
this.cb = cb // 用于更新界面的回调
this.vm = vm // vm
this.exp = exp // 对应的表达式
this.depIds = {} // 相关的n个dep容器对象
this.value = this.get() // 当前表达式对应的value
- 当执行vm.name='abc'表达式时,data中的name属性值变化会触发set方法,set中通知dep,dep通知所有watcher更新
- Dep与Watcher之间是多对多的关系
- 一个data属性对应一个Dep,一个Dep对应n个Watcher(属性多次在模板中被使用时n>1:{{a}}/v-text='a')
- 一个表达式对应一个Watcher, 一个Watcher对应n个Dep(多层表达式时n>1:a.b.c)
function MVVM (options) {
this.$options = options
var data = this._data = this.$options.data
// 对data中所有层次的属性通过数据劫持实现数据绑定
observe(data, this)
}
var compileUtil = {
bind: function (node, vm, exp, dir) {
var updateFn = updater[dir+'Updater']
updateFn && updateFn(node, this.getVMVal(vm, exp))
// 为表达式创建一个对应的watcher,实现节点的更新
new Watcher(vm, exp, function (value, oldValue) {//当表达式对应的一个属性值变化时回调
// 更新界面中的指定节点
updateFn && updateFn(node, value, oldValue)
})
},
updateFn: function (node, val) {
node.textContent = typeof val === 'undefined' ? '' : val
},
getVMVal: function (vm, exp) {
var val = vm
exp = exp.split('.')
exp.forEach(function (k, v) {
val = val[k]
})
return val
},
}
var uid = 0
function Dep () {
this.id = uid++
this.subs = []
}
Dep.prototype = {
notify: function () {
// 遍历所有watcher,通知watcher更新
this.subs.forEach(function (sub) {
sub.update()
})
},
// 建立dep与watcher之间的关系
depend: function () {
Dep.target.addDep(this)
},
addSub: function (sub) { // 添加watcher到dep中
this.subs.push(sub)
}
}
Dep.target = null
function Watcher (vm, exp, cb) {
this.cb = cb // 更新界面的回调
this.vm = vm //
this.exp = exp // 表达式
this.depIds = {} // 包含所有相关的dep的容器对象
this.value = this.get() // 得到初始值保存
}
Watcher.prototype = {
// 得到表达式的值,建立dep与watcher的关系
get: function () {
Dep.target = this;
var value = compileUtil.getVMVal(this.vm, this.exp)
// 去除dep中指定的当前watcher
Dep.target = null
return value
},
update: function () {
this.run()
},
run: function () {
var value = this.get()
var oldValue = this.value
if (value !== oldValue) {
this.cb.call(this.vm, value, oldValue)
}
},
addDep: function (dep) {
// 判断dep与watcher的关系是否已经建立
if (!this.depIds.hasOwnProperty(dep.id)) {
dep.addSub(this) // 将watcher添加到dep中
this.depIds[dep.id] = dep // 将dep添加到watcher中
}
}
}
function Observer (data) {
this.data = data
this.walk(data)
}
Observer.prototype = {
walk: function (data) {
var me = this
Object.keys(data).forEach(function (key) {
// 对指定属性实现响应式绑定
me.defineReactive(key, data[key], me.data)
})
},
defineReactive: function (key, val, data) {
// 创建属性对应的dep对象
var dep = new Dep()
// 通过间接的递归调用实现对data中所有层次属性的数据劫持
var childObj = observe(val)
// 给data重新定义属性,添加set和get方法
Object.defineProperty({
configurable: false,
enumerable: true,
get: function () {
if (Dep.target) {
dep.depend() //建立dep与watcher的关系
}
return val
},
set: function (newVal) { // 监视key属性的变化,更新界面
if (newVal === val) {
return
}
val = newVal
childObj = observe(newVal) // 新值是object的话,进行监听
dep.notify() // 通知所有相关的订阅者
},
})
}
}
function observe (data, vm) {
if (!data || typeof data !== 'object') {
return
}
return new Observer(data)
}
来源:CSDN
作者:慕斯策划一场流浪
链接:https://blog.csdn.net/qq_16049879/article/details/104681114