vue双向数据
整个过程分为三部分:
1、数据劫持监听Observer
2、发布订阅者模式Watcher
3、解析器Complier
其实很好理解,想要实现双向数据绑定,首先必须是视图和数据双向监听:
1、数据这边通过数据劫持的方式,可以监听到数据的读写,当发现读取数据的时候,判断是否为订阅者,如果是将这个对象存起来Dep,下次数据有更新的时候通知订阅者。
2、视图这边通过Complier解析器,将逐层遍历每个节点,判断是否有指令属性,并根据指令类型进行相应的操作(生成订阅器watcher并绑定更新函数,更新函数new Watcher同事添加一些更新动作:可能是监听input(v-model)、监听click(事件)、更新文本信息({{}})等),当生成订阅器的时候,每次读取数据的时候就会被存到上面说的Dep中,然后下次数据更新watcher就会通知到,进而更新视图层
小结:视图和数据有自己的劫持、监听方式,然后通过watcher(也就是核心发布订阅者模式)关联起来
具体代码实现
数据劫持监听Observer
//创建,传入要劫持的数据
function Observer (data) {
this.data = data;
this.goReactive(data);
}
Observer.prototype = {
goReactive: function (data) {
let self = this;
Object.key(data).forEach(function(key, index){
//每个属性进行
self.observerReactive(data, key, data[key]);
})
},
observerReactive: function (data, key, val) {//这里的val可能还是一个对象
let dep = new Dep();
let childObj = observer(val);//如果子属性也是对象的话,递归遍历
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function getter () {
if (Dep.needWatch) {//判断读取数据的是否需要订阅
dep.add(Dep.needWatch);
}
return val;
},
set: function setter (newVal) {
if (newVal === val) {
return;
} else {
val = newVal;
dep.notify();//通知订阅者
}
}
});
}
};
//创建监听存储器,上面提到的,每次数据劫持到监听到被读取的时候会判断是否是订阅者,是的话就会存储
function Dep(){
this.watchers = [];//订阅者们
}
Dep.prototype = {
add: function (watcher){
this.watchers.push(watcher);
},
notify: function () {
this.watchers.forEach(function (watcher) {
watcher.update();//也是文章开头提到的更新函数,当解析器解析指令之后,会进行对应的编译,然后当数据发生变化时会触发对应的更新数据
})
}
};
//这里主要是为了遍历子属性,当数据劫持的时候,很有可能是{1:{2:}}这种形式的,那么里面的对象2也是需要进行劫持监听的
function observe(value) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
};
发布订阅者模式Watcher
function Watcher (vm, attrName, callback) {
this.vm = vm;
this.attrName = attrName;
this.callback = callback;
this.value = this.get()//读取数据,添加上面的订阅者列表中dep
}
Watcher.prototype = {
update: function (){
this.start();
},
start: function (){
let oldVal = this.value,nowVal = this.vm.data[this.attrName];
if (nowVal !== oldVal) {
this.value = nowVal;
}
this.callback.call(vm, nowVal, oldVal);
},
get: function (){//读取并添加到订阅者
Dep.needWatch = this;
let value = this.vm.data[this.attrName];
Dep.needWatch = null;
return value;
},
};
解析器Complier
function Complier (vm, el) {
this.vm = vm;
this.el = el;
this.fragment= null;//创建一个文档碎片,把所有的新节点附加其上,然后把文档碎片一次性添加到document中
this.init();
}
Complier.prototype = {
init: function () {
if (this.ele) {
this.fragment= this.eleProducer(this.ele);
this.goComplier(this.fragment);
this.el.appendChild(this.fragment);
}
},
//碎片化
eleProducer: function (ele){
let fragment= document.createDocumentFragment();
let child = ele.firstChild;
while (child) {
// 将Dom元素移入fragment中
fragment.appendChild(child);
child = ele.firstChild
}
return fragment;
},
//开始解析
goComplier: (ele) {
let childNodes = ele.childNodes,self = this;
childNodes.each(function (index, node) {
self.goComplierByNodeType(node);
})
},
compileOrder: function (node) {
let attrs= node.attributes, self = this;
Array.prototype.forEach.call(attrs, function(attr) {
let attrName = attr.name;
if (attrName.indexOf('v-') !== -1) {//vue指令开头都是以v-开头,假设事件都是v-on:先不考虑简写情况
let attrVal = attr.value, order = attrName.substring(2);
if (order.indexOf('on:') !== -1) { // 事件指令
self.eventComplier(node, self.vm, attrValue, order);
} else { // v-model 指令
self.modelComplier(node, self.vm, attrValue, order);
}
node.removeAttribute(attrName);
}
});
},
eventComplier: function (node, vm, attrValue, order) {
let eventType = dir.split(':')[1], callback = vm.methods && vm.methods[order];
if (eventType && callback ) {
node.addEventListener(eventType, callback.bind(vm), false);
}
},
modelComplier: function (node, vm, attrValue, dataName) {
let self = this, val = this.vm[dataName];
node.value = typeof value == 'undefined' ? '' : value;//更新视图
new Watcher(this.vm, dataName, function (value) {
node.value = typeof value == 'undefined' ? '' : value;//更新视图
});
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
self.vm[dataName] = newValue;
val = newValue;
});
},
textComplier: function (node, dataName) {
let self = this, initText = this.vm[dataName];
node.textContent = typeof value == 'undefined' ? '' : value;
new Watcher(this.vm, dataName, function (value) {
node.textContent = typeof value == 'undefined' ? '' : value;
});
},
//根据不同的节点类型调用对应的更新函数
goComplierByNodeType: function (node) {
let reg = /\{\{(.*)\}\}/, text = node.textContent;
//元素节点1,属性节点2,文本3,一共12种,其余的用不到
if (node.nodeType == 1) { //判断指令
this.compileOrder(node);
} else if (node.nodeType == 3 && reg.test(text)) {//文本
this.textComplier(node, reg.exec(text)[1]);
}
if (node.childNodes && node.childNodes.length) {//遍历解析子节点
this.goComplier(node);
}
}
}
最后就是通过暴露一个全局对象,将这些全部关联起来,也就是vue源码中的index.js,肯定也是要先引入上面的watcher和observer还有complier
function qtVue (options) {
let self = this;
this.data = options.data;
this.methods = options.methods;
Object.keys(this.data).forEach(function(key) {
self.proxyEveryKey(key);
});
observe(this.data);
new Compile(options.el, this);
options.mounted.call(this); //就像vue一样,数据全部准备好以后进入mounted渲染dom
}
qtVue.prototype = {
proxyEveryKey: function (key) {
let self = this;
Object.defineProperty(this, key, {
enumerable: false,
configurable: true,
get: function getter () {
return self.data[key];
},
set: function setter (newVal) {
self.data[key] = newVal;
}
});
}
}
最后的最后,在index.html或者main.js中直接使用就可以啦,当然要引入index.js
new qtVue({
el: '#app',
data: {
title: 'my vue',
value: 'qt'
},
methods: {
clickFun: function () {
this.title = 'my click';
},
modelFun: function () {
this.value= 'qtt';
}
},
mounted: function () {
console.log(this.value);
},
created: function () {}
});
参考链接https://blog.csdn.net/qq_24122593/article/details/53284161