vue的MVVM响应式原理
1、数据劫持
vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter.getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
2、完整代码
const compileUtil = {
getVal(expr,vm){
return expr.split('.').reduce((data,currentVal) => {
console.log(currentVal);
return data[currentVal];
},vm.$data)
},
setVal(expr,vm,inputVal){
return expr.split('.').reduce((data,currentVal) => {
data[currentVal] = inputVal;
},vm.$data);
},
getContentVal(expr, vm){
retrun expr.repalce(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(ages[1],vm);
})
},
//处理text模式
text(node,expr,vm) {//expr相当于msg msg在data里面的定义假定为:学习MVVM原理
//const value = vm.$data[expr];
//this.updater.textUpdater(node,value)
//以上的写法并不是很严谨,只是可以将<div v-text='msg'></div>这样的进行正确的解析,那<div v-text='person.fav'></div>这样的应该如何处理呢?
let value;
if (expr.indexOf('{{')!== -1) {
value = expr.repalce(/\{\{(.+?)\}\}/g, (...args) => {
//在这里可以打印一下args看看拿到的到底是什么?
//绑定观察者,将来数据发生变化 触发这里的回调 进行更新
new Watcher(vm, args[1], (newVal) => {
this.updatertextUpdater(node, this.getContentVal(expr,vm));
})
return this.getVal(ages[1],vm);
})
}else {
value = this.getVal(expr,vm);
}
this.updater.textUpdater(node,value)
},
//处理html模式
html(node.expr,vm) {
const value = this.getVal(expr,vm);
new Watcher(v, expr, (newVal) => {
this.updater.htmlUpdater(node, newVal);
})
this.updater.htmlUpdater(node,value);
},
//处理model模式
model(node.expr,vm) {
const value = this.getVal(expr,vm);
//绑定更新数据 数据驱动=》视图
new Watcher(v, expr, (newVal) => {
this.updater.modelUpdater(node, newVal);
})
//当视图进行变化的时候,数据也进行相应的变化 视图=》数据=》视图
node.addEventListener('input', (e) => {
this.setVal(expr, vm, e.target.value);
})
this.updater.modelUpdater(node,value);
},
on(node,expr,vm,eventName) {
let fn = vm.$options.methods && vm.$options.methods[expr];
node.addEventListener(eventName,fn.bind(vm),false);
},
//更新的对象
updater:{
modelUpdater(node,value){
node.value = value;
},
textUpdater(node, value){
node.textContent = value;
},
htmlUpdater(node, value){
node.innerHTML = value;
}
}
}
class Compile {
constructor(el,vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
//1、获取文档碎片对象,放入内存中会减少页面的回流和重绘
const fragment = this.node2Fragment(this.el);
console.log(fragment);
//2、编译模板
this.compile(fragment);
//3、追加子元素到根元素
this.el.appendChild(fragment);
}
complie(fragment) {
//1、获取子节点
const childNodes = fragment.childNodes;
[...childNodes].forEach(child => {
if(this.isElementNode(child){
//是元素节点,则编译元素节点
this.compileElement(child);
}else {
//文本节点,编译文本节点
this.compileText(child);
}
if(child.childNodes && child.childNodes.length) {
this.compile(child);
}
)
})
}
//编译元素
compileElement(node){
const attributes = node.attributes;
[...attributes].forEach( attr => {
const {name, value} = attr;
//在这里可以打印看一下拿到的值
console.log(name);
//当是一个指令的时候
if(this.isDirective(name)){//是一个指令:v-text v-html v-model v-on:click
const [,dirctive] = name.split('-');//text,html,model,on:click
const [dirName,eventName] = dirctive.split(':');//text html model on
//更新数据 数据驱动视图
compileUtil[dirName](node,value,this.vm,eventName)//value则是指令所对应的值比如:msg,this.vm是实例对象,eventName则是方法的名字
//删除有指令标签上的属性
node.removeAttribute('v-' + dirctive);
}else if(this.isEventName(name)){
//@click = 'handleClick'
let [,eventName] = name.split('@');
compileUtil['on'](node,value,this.vm,eventName)
}
})
},
//编译文本
compileText(node){
console.log(node.textContent);//这里输出的是:全部的文本内容,里面会包含空以及其它,接下来需要做匹配,匹配到具有双大括号的
const content = node.textContent;
if(/\{\{(.+?)\}\}/.test(content)){
//处理文本
compileUtil['text'](node,content,this.vm);
}
}
//判断是不是指令
isDirective(attrName) {
return attrName.startsWith('v-');
}
//判断是不是以@开头的
isEventName(attrName) {
return attrName.startsWith('@');
}
//创建文档碎片
node2Fragment(el) {
const f = document.createDocumentFragment();
let firstChild;
//在这里需要注意的while循环中的el.firstChild有移动的作用,也就是说:当将第一个值进行赋值之后,则第二个将变为el中的第一个,依次类推
while(firstChild = el.firstChild){
f.appendChild(firstChild)
}
return f;
}
//判断是否是元素节点的对象
isElementNode(node) {
return node.nodeType === 1;
}
}
class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if(this.$el) {
//1、实现一个数据观察者
new Observer(this.$data);
//2、实现一个指令解析器
new Compile(this.$el,this);
this.proxyData(this.$data);
}
}
proxyData(data){
for(const key in data){
Object.defindeProperty(this,key , {
get(){
return data[key]
},
set(newVal){
data[key] = newVal;
}
})
}
}
}
3、数据的观察Observer
class Watcher {
constructor(vm,expr,cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
this.oldVal = this.getOldVal();
}
getOldVal() {
Dep.target = this;
compileUtil.getVal(this.expr,this.vm);
Dep.target = null;
return oldVal;
}
update() {
const newVal = compileUtil.getVal(this.expr,this.vm);
if(newVal !== this.oldVal){
this.cb(newVal);
}
}
}
class Dep {
constructor() {
this.subs = [];
}
//收集观察者
addSub(watcher) {
this.subs.push(watcher);
}
//通知观察者去更新
notify(){
console.log('通知了观察者');
this.subs.forEach(w => w.update())
}
}
class Observer {
constructor(data) {
this.observe(data);
}
observe(data){
/*
{
person: {
name:'张三',
fav: {
a:'爱好'
}
}
}*/
if(data && typeof data === 'object') {
Object.keys(data).forEach(key => {
this.defineReactive(data,key, data[key]);
})
}
}
defineReactive(obj,key,value){
//递归遍历
this.observe(value);
const dep = new Dep();
Object.defineProperty(obj,key,{
enumerable:true,
configurable;false,
get(){
//订阅数据变化时,往Dep中添加观察者
Dep.target && dep.addSub(Dep.target);
return value;
},
set:(newValue) => {
this.observe(newVal);
if(newValue !== value) {
value = newvalue
}
//告诉Dep通知变化
dep.notify();
}
})
}
}
4、简述一下你所理解的MVVM响应式原理
vue采用数据劫持配合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变化时,发布消息给依赖收集器,做出对应的回调函数,去更新视图
MVVM作为绑定的入口,整合Observe,Compile和Watcher三者,通过Observe来监听数据的变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer,Compile之间的桥梁,达到数据变化=》更新视图,视图交互变化=》数据变化的双向绑定效果