一、什么是双向绑定
单向绑定:把Model
绑定到View
,当我们用JavaScript
代码更新Model
时,View
就会自动更新;
双向绑定:在单向绑定的基础上,当更新View时,Model也跟着更新。
二、双向绑定的流程是什么
1、new Vue()首先执行初始化,对data执行响应化处理,这个过程发生Observe中;
2、同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile中;
3、同时定义⼀个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数;
4、由于data的某个key在⼀个视图中可能出现多次,所以每个key都需要⼀个管家Dep来管理多个Watcher;
5、将来data中数据⼀旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数。
3、实现原理
// 构造vue实例,对其初始化
class Vue {
constructor(obj) {
this.$data = obj.data
// 对其中的数据进行响应式处理
Observe(this.$data)
// 执行编译
Compile(obj.el, this)
}
}
// 对data进行响应式处理
function Observe(data) {
if (!data || typeof data !== 'object') return
const dependency = new Dependency()
Object.keys(data).forEach((key) => {
let val = data[key]
Observe(val)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
console.log(`获取了当前的val为${val}`)
Dependency.temp && dependency.addSub(Dependency.temp)
return val
},
set(newVal) {
console.log(`修改了当前的val为${newVal}`)
val = newVal
Observe(newVal)
// 通知依赖
dependency.notify()
}
})
})
}
// 编译模板
function Compile(element, vm) {
vm.$el = document.querySelector(element)
const fragment = document.createDocumentFragment()
let child
while (child = vm.$el.firstChild) {
fragment.append(child)
}
fragment_compile(fragment)
function fragment_compile(node) {
const pattern = /\{\{\s*(\S+)\s*\}\}/
if (node.nodeType == 3) {
const temp = node.nodeValue
const result_reg = pattern.exec(node.nodeValue)
if (result_reg) {
const arr = result_reg[1].split('.')
const val = arr.reduce((total, current) => total[current], vm.$data)
node.nodeValue = temp.replace(pattern, val)
new Watcher(vm, result_reg[1], (newVal) => {
node.nodeValue = temp.replace(pattern, newVal)
})
}
return
}
if (node.nodeType == 1 && node.nodeName == 'INPUT') {
const attr = Array.from(node.attributes)
attr.forEach(i => {
if (i.nodeName === 'v-model') {
const val = i.nodeValue.split('.').reduce((total, current) => total[current], vm.$data)
node.value = val
new Watcher(vm, i.nodeValue, (newVal) => {
node.value = newVal
})
node.addEventListener('input', e => {
const arr1 = i.nodeValue.split('.')
const arr2 = arr1.slice(0, arr1.length - 1)
const final = arr2.reduce((total, current) => total[current], vm.$data)
final[arr1[arr1.length - 1]] = e.target.value
})
}
})
}
node.childNodes.forEach((child) => { fragment_compile(child) })
}
// 挂载到页面上
vm.$el.appendChild(fragment)
}
class Dependency {
constructor() {
// 依赖管理
this.subscriber = []
}
// 添加依赖
addSub(sub) {
this.subscriber.push(sub)
}
// 派发依赖
notify() {
this.subscriber.forEach((sub) => { sub.update() })
}
}
// 收集依赖,多个Watcher由一个Dep管理,要更新时由Dep统⼀通知
class Watcher {
constructor(vm, key, callback) {
this.vm = vm
this.key = key
this.callback = callback
// 临时触发getter,保存依赖
// 创建实例时,把当前实例指定到Dep.target静态属性上
Dependency.temp = this
// 读一下key,触发get
vm[key]
// 置空
Dependency.temp = null
}
// 未来执行dom更新函数,由dep调用的
update() {
const val = this.key.split('.').reduce((total, current) => total[current], vm.$data)
this.callback(val)
}
}