Vue响应原理理解
Vue采用的数据劫持+发布订阅模式实现数据响应
原理流程图:
细节图
代码操作过程的响应流程:
当编译类编译时,需读取数据,此时调用数据劫持中的get方法,后生成了一个Watcher实例,实例执行getOldVal方法,将实例赋给Dep.target,进入get方法,将实例添加进入观察者,当数据跟新时,执行Watcher的回调函数,执行跟新函数。
// 代码
const CompileUtils={
'text':function (node,expr,vm) {
let value;
if(expr.indexOf('{{')>-1){
// {{person.name}} -- {{person.age}}
value = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
new Watcher(args[1],vm,()=>{
this.updater.updateText(node,this.getContent(expr,vm));
})
return this.getVal(args[1],vm)
})
}else{
new Watcher(expr,vm,(newVal)=>{
this.updater.updateText(node,newVal);
})
value = this.getVal(expr,vm);
}
this.updater.updateText(node,value);
},
'html':function (node,expr,vm) {
const value = this.getVal(expr,vm);
// 跟新中new一个Watcher
new Watcher(expr,vm,(newVal)=>{
this.updater.updateHtml(node,newVal);
})
this.updater.updateHtml(node,value);
},
'model':function (node,expr,vm) {
const value = this.getVal(expr,vm);
new Watcher(expr,vm,(newVal)=>{
this.updater.updateModel(node,newVal);
})
// 数据->视图
this.updater.updateModel(node,value);
// 视图->数据->视图
node.addEventListener('input',(e)=> {
this.setVal(expr,vm,e.target.value);
})
},
'on':function (node,expr,vm,eventName) {
const fn = vm.$methods[expr];
node.addEventListener(eventName,fn.bind(vm));
},
'bind':function (node,expr,vm,attrName) {
const value = this.getVal(expr,vm);
node.setAttribute(attrName,value);
},
getContent(expr,vm){
return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getVal(args[1],vm);
})
},
getVal(expr,vm){
return expr.split('.').reduce((init,current)=>{
return init[current];
},vm.$data)
},
setVal(expr,vm,value){
expr.split('.').reduce((init,current,index,arr)=>{
if(index === (arr.length-1)){
init[current] = value;
}
return init[current];
},vm.$data);
},
updater:{
updateHtml:function (node,value) {
node.innerHTML = value;
},
updateText:function (node,value) {
node.textContent = value;
},
updateModel:function (node,value) {
node.value = value;
}
}
}
class Compile {
constructor(node,vm) {
this.vm = vm;
// 把节点放到文档碎片中
const fragment = this.getFragment(node);
// 编译文档碎片
this.compile(fragment);
// 追加文档碎片到node下
node.appendChild(fragment);
}
compileText(node){
const value = node.textContent;
// 用正则筛选{{}}
if(/\{\{(.+?)\}\}/.test(value)){
CompileUtils['text'](node,value,this.vm);
}
}
compileElement(node){
// <h1 v-html v-text v-on:click></h1>
const attributes = node.attributes;
[...attributes].forEach(attr=>{
const {name,value} = attr;
// 判断是否v-开头的指令
if(this.isDirective(name)){
// v | text html on:click bind:src
const [,directive] = name.split('-');
const [directName,eventName] = directive.split(':');
// 传入修改的节点,属性值
CompileUtils[directName](node,value,this.vm,eventName);
node.removeAttribute(name);
}else if(this.isMethod(name)){
// @开头的
const [,eventName] = name.split('@');
CompileUtils['on'](node,value,this.vm,eventName);
node.removeAttribute(name);
}
})
}
isMethod(eventName){
return eventName.startsWith('@');
}
isDirective(attrName){
return attrName.startsWith('v-');
}
compile(node){
// 遍历里面所有的节点
const childnodes = node.childNodes;
[...childnodes].forEach(i=>{
// 判断是文本节点还是元素节点
if(this.isTextElement(i)){
// 编译文本节点
this.compileText(i);
}else{
// 编译元素节点
this.compileElement(i);
}
if(i.childNodes && i.childNodes.length){
this.compile(i);
}
})
}
isTextElement(node){
return node.nodeType === 3;
}
getFragment(node){
const fragment2Node = document.createDocumentFragment();
// 遍历node节点的第一个子元素
let firstnode;
while(firstnode = node.firstChild){
fragment2Node.appendChild(firstnode);
}
return fragment2Node;
}
}
class MVue {
constructor(options) {
// 判断el是否节点
this.$el = this.isElementNode(options.el) ? options.el : document.querySelector(options.el);
this.$data = options.data;
this.$methods = options.methods;
// 如果el存在。
if(this.$el){
// 1.观察者类,劫持数据
new Observer(this.$data)
// 2.编译类
new Compile(this.$el,this);
// 3.代理
this.proxyData(this.$data);
}else{
throw new Error('el元素不存在')
}
}
isElementNode(node){
return node.nodeType === 1;
}
proxyData(data){
Object.keys(data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable:true,
configurable:false,
set(v) {
data[key] = v;
},
get() {
return data[key];
}
})
})
}
}
class Watcher {
constructor(expr,vm,cb) {
this.expr = expr;
this.vm = vm;
this.cb = cb;
this.oldVal = this.getOldVal();
}
// 获取旧值
getOldVal(){
Dep.target = this;
const oldval = CompileUtils.getVal(this.expr,this.vm);
Dep.target = null;
return oldval;
}
// 跟新方法
update(){
const newVal = CompileUtils.getVal(this.expr,this.vm);
if(newVal !== this.oldVal){
// 将值回调回去
this.cb(newVal);
}
}
}
class Dep {
constructor() {
// 收集订阅者
this.subs = [];
}
// 添加订阅者方法
addSub(watcher){
this.subs.push(watcher);
}
// 通知Watcher跟新方法
notify(){
this.subs.forEach(w=>w.update())
}
}
class Observer {
constructor(data) {
// 劫持数据
this.observe(data);
}
observe(data){
// data存在且为对象只遍历对象
if(data && typeof data ==='object'){
// 遍历数据
// for in 遍历包括原型链
// Object.keys 只包含自身可枚举
Object.keys(data).forEach(key=>{
this.defineReactive(data,key,data[key]);
})
}
}
defineReactive(data,key,value){
const dep = new Dep();
this.observe(data[key]);
Object.defineProperty(data,key,{
enumerable:true,
configurable:false,
get() {
// 初始化,收集订阅者
Dep.target && dep.addSub(Dep.target);
return value;
},
set(v) {
value = v;
// 通知变化
dep.notify();
}
})
}
}