let vm = new MVVM({
el:'#app',
data:{
message:{
a: 'hello zfpx',
},
a:1
}
})
复制代码
vue中初始化一个vue实例是这样用的,那么MVVM内部原理到底是怎么实现的呢?vue
MVVM实现图示以下 node
1. MVVM类实现数组
MVVM类主要功能:bash
class MVVM{
constructor(options){
// 先把可用的东西挂载在实例上
this.$el = options.el;
this.$data = options.data;
// 若是有要编译的模板,就开始编译
if(this.$el){
// 数据劫持 就是把对想的全部属性 改为get和set方法
new Observer(this.$data);
//将$data代理到this上
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
}
})
})
}
}
复制代码
2. Observer类实现 Observer类主要功能:app
class Observer{
constructor(data){
this.observe(data);
}
observe(data){
// 要对这个data数据将原有的属性改为set和get的形式
if(!data || typeof data !== 'object'){
return;
}
// 要将数据 一一劫持 先获取取到data的key和value
Object.keys(data).forEach(key=>{
// 劫持
this.defineReactive(data,key,data[key]);
this.observe(data[key]);// 深度递归劫持
});
}
// 定义响应式
defineReactive(obj,key,value){
let that = this;
// 每一个属性 都会对应一个数组,这个数组是存放全部更新的操做
let dep = new Dep();
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
// 当取值时,将Dep.target(watcher即set属性时,要执行的回调函数)推入数组
Dep.target && dep.addSub(Dep.target);
return value;
},
set(newValue){
// 当给data属性中设置值的时候 更改获取的属性的值
if(newValue!=value){
// 这里的this不是实例
// 若是是对象继续劫持
// 好比你this.$data = {a: '2'}换成了一个新对象
that.observe(newValue);
value = newValue;
dep.notify(); // 通知全部watcher数据更新了
}
}
});
}
}
复制代码
3. Dep类实现 Dep类的主要功能dom
class Dep{
constructor(){
// 订阅的数组
this.subs = []
}
addSub(watcher){
this.subs.push(watcher);
}
notify(){
this.subs.forEach(watcher=>watcher.update());
}
}
复制代码
4.watcher类实现函数
watcher功能ui
class Watcher{
// vm: vm实例 expr: {{message.a}}中的message.a cb:属性更新后的回调函数
constructor(vm,expr,cb){
this.vm = vm;
this.expr = expr;
this.cb = cb;
// 先获取一下老的值,进入get方法,将watcher实例赋给Dep.target
//而后 执行this.getval()方法时,会读取data对象上的属性
// 一旦读取属性(数据劫持)
// 就会执行Observer类中的defineReactive方法
// 执行这里:Dep.target && dep.addSub(Dep.target);
//将watcher实例推入订阅的数组
this.value = this.get(); // 读取老值
}
// 获取data对象上对应的expr属性值
getVal(vm, expr) {
expr = expr.split('.'); // [message,a]
return expr.reduce((prev, next) => { // vm.$data.a
return prev[next];
}, vm.$data);
}
get(){
Dep.target = this;
let value = this.getVal(this.vm,this.expr);
Dep.target = null;
return value;
}
// 对外暴露的方法
//一旦给data对象的属性设置值,就会执行Observer类defineReactive方法中的
// dep.notify()--》执行订阅的数组中的watcher队列的update方法
update(){
let newValue = this.getVal(this.vm, this.expr);
let oldValue = this.value;
if(newValue != oldValue){
this.cb(newValue); // 对应watch的callback
}
}
}
// 用新值和老值进行比对 若是放生变化 就调用更新方法
复制代码
5. Compile类实现 Compile类功能this
class Compile {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
if (this.el) {
// 若是这个元素能获取到 咱们才开始编译
// 1.先把这些真实的DOM移入到内存中 fragment
let fragment = this.node2fragment(this.el);
// 2.编译 => 提取想要的元素节点 v-model 和文本节点 {{}}
this.compile(fragment);
// 3.把编译好的fragment在塞回到页面里去
this.el.appendChild(fragment);
}
}
/* 专门写一些辅助的方法 */
isElementNode(node) {
return node.nodeType === 1;
}
// 是否是指令
isDirective(name) {
return name.includes('v-');
}
/* 核心的方法 */
compileElement(node) {
// 带v-model v-text
let attrs = node.attributes; // 取出当前节点的属性
Array.from(attrs).forEach(attr => {
// 判断属性名字是否是包含v-model
let attrName = attr.name;
if (this.isDirective(attrName)) {
// 取到对应的值放到节点中
let expr = attr.value;
let [, type] = attrName.split('-');
// node this.vm.$data expr
CompileUtil[type](node, this.vm, expr);
}
})
}
compileText(node) {
// {{}}的处理
let expr = node.textContent; // 取文本中的内容
let reg = /\{\{([^}]+)\}\}/g; // {{a}} {{b}} {{c}}
if (reg.test(expr)) {
// node this.vm.$data text
CompileUtil['text'](node, this.vm, expr);
}
}
compile(fragment) {
// 递归
let childNodes = fragment.childNodes;
Array.from(childNodes).forEach(node => {
if (this.isElementNode(node)) {
// 是元素节点,还须要继续深刻的检查
// 这里须要编译元素
this.compileElement(node);
this.compile(node)
} else {
// 文本节点
// 这里须要编译文本
this.compileText(node);
}
});
}
node2fragment(el) {
// 须要将el中的内容所有放到内存中
// 文档碎片 内存中的dom节点
let fragment = document.createDocumentFragment();
let firstChild;
while (firstChild = el.firstChild) {
fragment.appendChild(firstChild);
}
return fragment; // 内存中的节点
}
}
CompileUtil = {
getVal(vm, expr) { // 获取实例上对应的数据
expr = expr.split('.'); // [message,a]
return expr.reduce((prev, next) => { // vm.$data.a
return prev[next];
}, vm.$data);
},
getTextVal(vm, expr) {
// 获取编译文本后的结果 {{message.a}}--> message.a-->data.message.a
return expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
return this.getVal(vm, arguments[1]);
})
},
text(node, vm, expr) { // 文本处理
let updateFn = this.updater['textUpdater'];
// {{message.a}} => hello,zfpx;
let value = this.getTextVal(vm, expr);
// {{a}} {{b}}
expr.replace(/\{\{([^}]+)\}\}/g, (...arguments) => {
//每一个属性都对应一个watcher
new Watcher(vm, arguments[1],(newValue)=>{
// 若是数据变化了,文本节点须要从新获取依赖的属性更新文本中的内容
updateFn && updateFn(node,this.getTextVal(vm,expr));
});
})
updateFn && updateFn(node, value)
},
setVal(vm,expr,value){ // [message,a]
expr = expr.split('.');
// 收敛
return expr.reduce((prev,next,currentIndex)=>{
if(currentIndex === expr.length-1){
return prev[next] = value;
}
return prev[next];
},vm.$data);
},
model(node, vm, expr) { // 输入框处理
let updateFn = this.updater['modelUpdater'];
// 这里应该加一个监控 数据变化了 应该调用这个watch的callback
new Watcher(vm,expr,(newValue)=>{
// 当值变化后会调用cb 将新的值传递过来 ()
updateFn && updateFn(node, this.getVal(vm, expr));
});
node.addEventListener('input',(e)=>{
let newValue = e.target.value;
this.setVal(vm,expr,newValue)
})
updateFn && updateFn(node, this.getVal(vm, expr));
},
updater: {
// 文本更新
textUpdater(node, value) {
node.textContent = value
},
// 输入框更新
modelUpdater(node, value) {
node.value = value;
}
}
}
复制代码