1、实现原理图
2、实现思路
三大模块:
- 模板编译
主要简单处理两种:1指令编译 v-modle 2 文本编译 {{data}}
对应 更新元素数据(input) 方法 更新文本数据方法
- 数据劫持
遍历传进来的数据对象,分别给对象的属性重写get \ set 方法
利用了Object.defineProperty
- 观察者
将劫持和编译联系起来 ----》发布订阅
订阅: 将watcher 添加进数组中,
发布: 触发update方法时,一次遍历执行数组中的方法
3、代码实现
class MVVM {
constructor(options) {
/* 首先把可用的东西挂在实例上 */
this.$el = options.el;
this.$data = options.data;
//如果有编译的模版就开始编译
if(this.$el){
//数据劫持,把对象的所有属性改为get 和 set
new Observer(this.$data)
this.proxyData(this.$data)
//用数据和元素进行编译
new Compile(this.$el,this);
}
}
proxyData(data){
Object.keys(data).forEach(key =>{
Object.defineProperty(this,key,{
get(){
return data[key]
},
set(newValue){
data[key] = newValue;
}
})
})
}
}
其中,proxyData做了一个数据代理
当修改或访问数据时,vm.$data.message 改为直接访问 vm.message
上述核心代码主要实现两部分,一是数据劫持,二是数据编译
1.数据编译
compileElement(node){
//带v-model
let attrs = node.attributes;
var self = this;
Array.from(attrs).forEach(function(attr){
let attrName = attr.name;
if(self.isDirective(attrName)){//attr.name attr.value;判断属性名字是否包含v-
//取到对应的值放到节点上面
let expr = attr.value;
//node this.vm.$data
//let [,type] = attrName.split("-")
let type = attrName.slice(2);
CompileUtil[type](node,self.vm,expr);
}
})
}
compileText(node){
//带{{}}
let expr = node.textContent;//取文本中的内容
let reg = /\{\{([^}]+)\}\}/g;
if(reg.test(expr)){
//node this.vm.$data text
CompileUtil['text'](node,this.vm,expr);
}
}
2.数据劫持
//定义响应式
defineReactive(data,key,value){
var that = this;
let dep = new Dep(); //每个变化的数据都会对应一个数组,这个数组是存放所有更新的操作
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get(){//当取值时调用的方法
Dep.target && dep.addSub(Dep.target)
return value
},
set(newValue){//当给data属性中设置值的时候,更改获取的属性的值
if(newValue!==value){
//这里的this并不是实例 vm.message = {b:1}
that.observe(newValue);//如果是对象,继续劫持
value = newValue;
dep.notify();//通知所有人,数据更新了
}
}
})
}
3、watcher
get(){
Dep.target = this;
let value = this.getVal(this.vm,this.expr);
Dep.target = null;
return value
}
//对外暴露的方法
update(){
let newValue = this.getVal(this.vm,this.expr);
let oldValue = this.value;
if(newValue != oldValue){
this.cb(newValue) //对应watch 的callback
}
}
watcher主要对比新值 和旧值,不一致则调用回调函数cb
发布订阅
class Dep{
constructor(){
//订阅的数组
this.subs = [];
}
addSub(watcher){
this.subs.push(watcher);
}
notify(){
this.subs.forEach(watcher =>watcher.update());
}
}
全部实现代码:https://github.com/wanghuixiago/mvvm