vue中v-module双向数据绑定理解:我们可以简单分为四个过程
实现一个监听器Observer:对数据对象进行遍历,包括子属性对象的属性,利用Object.definePropery()对属性都加上setter和getter。这样的话,给这个对象的每个值赋值,就回触发setter,那么就能监听到数据变化。
实现一个解析器Compile:解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
实现一个订阅者Watcher:Watcher订阅者是Observer和Compile之间的通信桥梁,主要的任务是订阅Observer中的属性值变化的消息,当收到属性变化的消息时,触发解析器Compile中对应的更新函数。
实现一个订阅器Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者Watcher,对监听器Observer和订阅者Watcher进行统一管理。
js代码:
class Dep{
constructor() {
this.listenFunc = []
}
addFunc(obj) {
this.listenFunc.push(obj);
}
changeWatch() {
this.listenFunc.forEach(val => {
val.sendVal()
})
}
}
Dep.target = null;
const dep = new Dep()
class Watcher{
constructor(data, key, cbk) {
// 每一次实例watcher的时候,均会把当前实例赋值给Dep的target静态属性
Dep.target = this;
this.data = data;
this.key = key;
this.cbk = cbk;
// 每一次的实例都会调用该函数
this.init()
}
// 获取对应key的值
init() {
// 获取对应key的值
this.value = utils.getValue(this.data, this.key);
Dep.target = null;
return this.value;
}
sendVal() {
let newVal = this.init()
this.cbk(newVal)
}
}
class Observer{
constructor(data) {
if (!data || typeof data !== 'object') {
return;
}
this.data = data;
this.init()
}
init() {
Object.keys(this.data).forEach(val => {
this.observer(this.data, val, this.data[val])
})
}
observer(obj, key, value) {
// 通过递归实现每个属性的数据劫持
new Observer(obj[key])
Object.defineProperty(obj, key, {
// 添加劫持之后的属性获取方法
get() {
if (Dep.target) {
// 给dep实例属性listenFunc添加一个watcher实例
dep.addFunc(Dep.target)
}
return value
},
// 添加劫持之后的属性设置方法
set(newValue) {
if (value === newValue) {
return;
}
value = newValue;
// 触发每一个listenFunc里面的watcher实例
dep.changeWatch();
// 为了兼容新值为一个对象的时候,该对象的属性也得添加劫持
new Observer(value);
}
})
}
}
const utils = {
setValue(node, data, key) {
node.value = this.getValue(data, key)
},
getValue(data, key) {
if (key.indexOf('.') > -1) {
let arr = key.split('.');
for(let i = 0; i < arr.length; i++) {
data = data[arr[i]]
}
return data
} else {
return data[key]
}
},
getContent(node, key, data) {
node.textContent = this.getValue(data, key)
},
// 2.在input事件发生之后,改变对应的属性值
changeKeyVal(data, key, newVal) {
if (key.indexOf('.') > -1) {
let arr = key.split('.');
for(let i = 0; i < arr.length - 1; i++) {
data = data[arr[i]]
}
data[arr[arr.length - 1]] = newVal
} else {
data[key] = newVal
}
}
}
// 实现双向数据绑定
class Mvvm{
constructor({el, data}) {
this.el = el;
this.data = data;
// 初始化执行数据绑定实例对象的过程(以及数据劫持)
this.init();
// 替换文本中的属性为真实的数据
this.initDom();
}
init() {
Object.keys(this.data).forEach(val => {
this.observer(this, val, this.data[val])
})
// 给当前数据集合的每一个属性添加劫持
new Observer(this.data)
}
observer(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
return value
},
set(newValue) {
value = newValue
}
})
}
initDom() {
this.$el = document.getElementById(this.el);
// 文本碎片--> 避免因为操作DOM而导致浏览器的多次重绘(操作完成之后可把整个碎片添加进去,浏览器课一并识别渲染)
let newFargment = this.createFragment();
// 根据nodeType来替换对应的属性值
this.compiler(newFargment);
this.$el.appendChild(newFargment);
}
createFragment() {
let newFragment = document.createDocumentFragment();
let firstChild;
while(firstChild = this.$el.firstChild) {
newFragment.appendChild(firstChild);
}
return newFragment;
}
compiler(node) {
if (node.nodeType === 1) {
let attributes = node.attributes;
Array.from(attributes).forEach(val => {
if (val.nodeName === 'v-model') {
// 1.捕捉input输入框的修改事件
node.addEventListener('input', (e) => {
utils.changeKeyVal(this.data, val.nodeValue, e.target.value)
})
utils.setValue(node, this.data, val.nodeValue)
}
})
} else if (node.nodeType === 3) {
let contentVal = node.textContent.indexOf("{{") > -1 && node.textContent.split('{{')[1].split('}}')[0];
contentVal && utils.getContent(node, contentVal, this.data);
// 添加属性监听
contentVal && new Watcher(this.data, contentVal, (newVal) => {
node.textContent = newVal
})
}
// 通过递归的形式保证每一级的文本都可获取到并替换
if (node.childNodes && node.childNodes.length > 0) {
node.childNodes.forEach(val => {
this.compiler(val)
})
}
}
}