1、数据代理
js<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script type="text/javascript" src="js/compile.js"></script>
<script type="text/javascript" src="js/observer.js"></script>
<script type="text/javascript" src="js/watcher.js"></script>
<script type="text/javascript" src="js/mvvm.js"></script>
<script type="text/javascript">
const vm = new MVVM({
el:'#test',
data:{
name:'feifei2'
}
})
console.log(vm.name,vm);
vm.name = 'xiaoxiao2';
</script>
</body>
</html>
mvvm.js
/*
* 相当于VUE的构造函数
*/
function MVVM(options){
//将配置对象保存到vm
this.$options = options;//此时的this指向的是MVVM,后面将会传给vm,将指向vm
var data = this._data = this.$options.data;
var that = this;
//数据代理
//实现 vm.xxx -> vm._data.xxx
//遍历data中所有的属性
Object.keys(data).forEach(function(key){
//对指定的属性实现代理
that._proxy(key)
})
//对data中所有层次的属性通过数据劫持实现数据绑定
observe(data,this);
// 创建一个编译对象(解析模板)
this.$compile = new Compile(options.el || document.body,this);
}
MVVM.prototype = {
$watch:function(key,cb,options){
new watcher(this,key,cb)
},
//数据代理,更新vm的数据 ----》 数据绑定,更新_data的数据,从而更新页面
_proxy:function(key){
var that = this;
Object.defineProperty(that,key,{
configurable:false,
emumerable:true,
get:function proxyGetter(){
return that._data[key]
},
set:function proxySetter(newVal){
that._data[key] = newVal;
}
})
}
}
2、模板解析
compile.js
function Compile(el, vm) {
//保存vm到compile对象
this.$vm = vm;
//将el对应的元素对象保存到compile对象中
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
//如果有el元素
if(this.$el) {
//1.取出el元素中所有子节点保存到一个fragment对象中
this.$fragment = this.node2Fragment(this.$el);
//2.编译fragment中所有层次子节点
this.init();
//3.将编译好的fragment添加到页面的el元素中
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
node2Fragment: function(el) {
//1、创建空的容器fragment
var fragContent = document.createDocumentFragment(),
child;
//将el中所有子节点转移到fragment
while(child = el.firstChild) {
fragContent.appendChild(child);
}
//返回
return fragContent;
},
init: function() {
//编译容器中所有节点元素
this.compileElement(this.$fragment)
},
compileElement: function(el) {
//取出最外层的子节点
var childNodes = el.childNodes;
//保存compile对象
var that = this;
//遍历所有子节点(text/element)
[].slice.call(childNodes).forEach(function(node) {
//得到节点的文本内容
var text = node.textContent;
//正则匹配{{}},如{{name}}
var reg = /{{(.*)}}/; //匹配大括号表达式
//判断节点是否是一个元素节点
if(that.isElementNode(node)) {
//编译它(解析指令v-if等)---难点
that.compile(node);
//判断节点是否是大括号格式的文本节点
} else if(that.isTextNode(node) && reg.test(text)) {
//编译大括号表达式文本节点---难点
that.compileText(node, RegExp.$1);
}
//如果当前节点还有子节点,通过递归调用,实现所有层次节点的编译
if(node.childNodes&&node.childNodes.length){
that.compileElement(node);
}
})
},
compile: function(node) {
//得到标签的所有属性
var nodeAttrs = node.attributes;
var that = this;
//遍历所有属性
[].slice.call(nodeAttrs).forEach(function(attr){
//得到属性名,比如v-on:click
var attrName = attr.name;
//判断是否是指令属性
if(that.isDirective(attrName)){
//得到属性值 , 表达式:show
var exp =attr.value;
//从属性名中得到指令名:on:click
var dir = attrName.substring(2);
//判断是否是事件指令
if(that.isEventDirective(dir)){//解析处理事件指令
compileUtil.eventHandler(node,that.$vm,exp,dir);
}else{//普通指令
compileUtil[dir]&&compileUtil[dir](node,that.$vm,exp,dir);
}
//移除指令属性
node.removeAttribute(attrName);
}
})
},
compileText:function(node,exp){
compileUtil.text(node,this.$vm,exp);
},
isDirective:function(attr){
return attr.indexOf('v-') == 0;
},
isEventDirective:function(dir){
return dir.indexOf('on') === 0;
},
isElementNode:function(node){
return node.nodeType == 1;
},
isTextNode:function(node){
return node.nodeType == 3;
}
}
//包含多个解析指令的方法的对象(指令处理集合)
var compileUtil ={
//解析v-text === {{}}
text:function(node,vm,exp){
this.bind(node,vm,exp,'text');
},
//解析v-html
html:function(node,vm,exp){
this.bind(node,vm,exp,'html');
},
//解析v-model
model:function(node,vm,exp){
this.bind(node,vm,exp,'model');
var that = this;
var val =that._getVMVal(vm,exp);
node.addEventListener('input',function(e){
var newValue = e.target.value;
if(val===newValue){
return;
}
that._setVMVal(vm,exp,newValue);
val = newValue;
})
},
//解析v-class
class:function(node,vm,exp){
this.bind(node,vm,exp,'class');
},
bind:function(node,vm,exp,dir){
//得到更新节点的函数
var updaterFn = updater[dir+'Updater'];
//检查函数是否存在&&调用函数更新节点
updaterFn&&updaterFn(node,this._getVMVal(vm,exp));
new Watcher(vm,exp,function(value,oldValue){
updaterFn&&updaterFn(node,value,oldValue)
})
},
//事件处理
eventHandler:function(node,vm,exp,dir){
//得到事件类型//名 click
var eventType =dir.split(':')[1],
//从methods中得到表达式所对应的函数(事件回调函数)
fn = vm.$options.methods&&vm.$options.methods[exp];
if(eventType&&fn){
//给节点绑定指定事件名和回调函数(强制绑定this为vm)的DOM事件监听
node.addEventListener(eventType,fn.bind(vm),false);
}
},
_getVMVal:function(vm,exp){
var val = vm._data;
exp = exp.split('.');
exp.forEach(function(k){
val = val[k];
})
return val;
},
_setVMVal:function(vm,exp,value){
var val = vm._data;
exp = exp.split('.');
exp.forEach(function(k,i){
//非最后一个key,更新val 的值
if(i<exp.length-1){
val = val[k]
}else{
val[k] = value;
}
})
}
};
//包含多个更新节点的方法的工具对象
var updater = {
//更新节点的textContent属性值
textUpdater:function(node,value){
node.textContent = typeof value == 'undefined' ? '':value;
},
//更新节点的innerHTML属性值
htmlUpdater:function(node,value){
node.innerHTML = typeof value == 'undefined'?'':value;
},
//更新节点的className
classUpdater:function(node,value,oldValue){
var className = node.className;
// className = className.replace(oldValue,'').replace(/s$/,'');
//
// var space = className&&String(value)?' ':'';
node.className =className +(className?' ':'') +value;
},
//更新节点的value属性值
modelUpdater:function(node,value,oldValue){
node.value = typeof value == 'undefined' ?'':value;
}
}
3、数据绑定
![8a110f251acc2d20ea28906dcb9ddb39.png](https://img-blog.csdnimg.cn/img_convert/8a110f251acc2d20ea28906dcb9ddb39.png)
3、数据劫持
observer.js
function Observer(data){
this.data = data;
this.walk(data);
}
Observer.prototype = {
walk:function(data){
var that = this;
Object.keys(data).forEach(function(key){
that.convert(key,data[key]);
})
},
convert:function(key,val){
this.defineReactive(this.data,key,val)
},
defineReactive:function(data,key,val){
var dep = new Dep();
var childObj = observe(val);
Object.defineProperty(data,key,{
enumerable:true,//可枚举
configurable:false,//不能再define
get:function(){
if(Dep.target){
dep.depend();
}
return val;
},
set:function(newVal){
if(newVal === val){
return;
}
val = newVal;
//新的值是object的话,进行监听
childObj = observe(newVal);
//通知订阅者
dep.notify();
}
})
}
}
function observe(value,vm){
if(!value || typeof value !== 'object'){
return
}
return new Observer(value);
}
var uid = 0;
//它的实例是什么时候创建的,初始化的时候给data的属性进行数据劫持时创建的
function Dep(){
this.id = uid++;
//多个订阅者数组
this.subs = [];
}
Dep.prototype = {
addSub:function(sub){
this.subs.push(sub);
},
depend:function(){
Dep.target.addDep(this);
},
removeSub:function(){
var index = this.subs.indexOf(sub);
if(index != -1){
this.subs.splice(index,1);
}
},
notify:function(){
this.subs.forEach(function(sub){
sub.update();
})
}
}
Dep.target = null;
4、数据监听与更新,通知变化
watcher.js
function Watcher(vm,exp,cb){
this.cb = cb; //用于更新界面的回调
this.vm = vm; //vm
this.exp = exp; //对应的表达式
this.depIds = {}; //相关的n个dep的容器对象
this.value = this.get(); //当前 表达式对应的value
}
Watcher.prototype = {
update:function(){
this.run();
},
run:function(){
var value = this.get();
var oldVal = this.value;
if(value != oldVal){
this.value = value;
this.cb.call(this.vm,value,oldVal);
}
},
addDep:function(dep){
if(!this.depIds.hasOwnProperty(dep.id)){
dep.addSub(this);
this.depIds[dep.id] = dep;
}
},
get:function(){
Dep.target = this;
var value = this.getVMVal();
Dep.target = null;
return value;
},
getVMVal:function(){
var exp = this.exp.split('.');
var val = this.vm._data;
exp.forEach(function(k){
val = val[k]
});
return val;
}
};
4、总结
![f5beeaf02b39f297497376ed0c6b819c.png](https://img-blog.csdnimg.cn/img_convert/f5beeaf02b39f297497376ed0c6b819c.png)
Vue数据双向绑定是通过数据劫持结合* 发布订阅者模式