<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>复制粘贴</title>
</head>
<body >
<div id="app-7">
<h3>{{msg}}</h3>
<div v-text='msg'></div>
<div v-text='person.name'></div>
<!-- <div >{{person.name}}</div> -->
<div v-html='htmlStr'></div>
<div v-text='person.fav.a'></div>
<input type='text' v-model='msg'></input>
<input type='text' v-model='msg'></input>
<button v-on:click='handlerClick'>点我</button>
<!-- <button @click='handlerClick'>点我222</button> -->
</div>
<!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->
<script src="./Observer.js"></script>
<script src="./MVVM.js"></script>
<script>
let vm=new MVue({
el:'#app-7',
data:{
person:{
name:'小马哥',
age:18,
fav:{a:22222},
},
msg:'学习mvvm原理',
htmlStr:'232'
},
methods:{
handlerClick(){
console.log(this.$data);
this.person.name='921319'
console.log(this.person.name)
}
}
})
</script>
</body>
<style>
</style>
</html>
MVVM
const compileUtils = {
getVal (expr, vm) {
return expr.split('.').reduce((data, currentVal) => {
return data[currentVal];
}, vm.$data)
},
setVal (expr, vm, inputVal) {
return expr.split('.').reduce((data, currentVal) => {
data[currentVal] = inputVal;
}, vm.$data)
},
getContentVal (expr, vm) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(args[1], vm)
})
},
text (node, expr, vm) {
let value;
if (expr.indexOf('{{') !== -1) {
expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
// 绑定观察者,将来数据变化,触发这里的回调,进行更新
new Watcher(vm, args[1], () => {
this.updater.textUpdater(node, this.getContentVal(expr, vm))
})
return this.getVal(args[1], vm)
})
} else {
value = this.getVal(expr, vm)
}
this.updater.textUpdater(node, value)
},
html (node, expr, vm) {
value = this.getVal(expr, vm)
// 绑定观察者,将来数据变化,触发这里的回调,进行更新
new Watcher(vm, expr, (newValue) => {
this.updater.htmlUpdater(node, newValue)
})
// const value = vm.$data[expr];
this.updater.htmlUpdater(node, value)
},
model (node, expr, vm) {
// const value = vm.$data[expr];
value = this.getVal(expr, vm)
// 绑定更新函数;数据=>视图
new Watcher(vm, expr, (newValue) => {
this.updater.modelUpdater(node, newValue)
})
// 视图=》数据=》视图
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);
},
bind (node, expr, vm, eventName) {
// 自己实现
},
// 更新的函数
updater: {
textUpdater (node, value) {
node.textContent = value
},
htmlUpdater (node, value) {
// console.log(value)
node.innerHTML = value
},
modelUpdater (node, value) {
node.value = 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);
// 2.编译模版
this.compile(fragment)
// 3.追加子节点到根元素
this.el.appendChild(fragment)
}
compile (fragment) {
const childNodes = fragment.childNodes;
// 遍历dom的子节点
[...childNodes].forEach(child => {
if (this.isElementNode(child)) {
// 是元素节点,编译元素节点
this.compileEleNode(child)
} else {
// 文本节点
// 编译文本节点
this.compileTextNode(child)
}
if (child.childNodes && child.childNodes.length) {
this.compile(child)
}
})
}
compileEleNode (node) {
const attr = node.attributes;
[...attr].forEach(attr => {
const { name, value } = attr;
if (this.isDirective(name)) {//如果是v-指令:v-text,v-html,v-modal,v-on:click;
const [, dirctive] = name.split('-');// text,html,modal,on:click;
const [dirName, eventName] = dirctive.split(':'); //text,html,modal,on;
// 更新数据,数据驱动视图
compileUtils[dirName](node, value, this.vm, eventName);
// 删除有指令的标签上的属性
node.removeAttribute('v-' + dirctive);
} else if (this.isEventName(name)) {
let [, eventName] = name.split('@');
compileUtils['on'](node, value, this.vm, eventName)
}
})
}
compileTextNode (node) {
const content = node.textContent;
// 匹配{{}}翻译变量
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtils['text'](node, content, this.vm)
}
}
node2Fragment (el) {
const f = document.createDocumentFragment();
let firstChild;
while (el.firstChild) {
firstChild = el.firstChild
f.appendChild(firstChild)
}
return f;
}
isElementNode (node) {
return node.nodeType === 1;
}
isDirective (attrName) {
return attrName.startsWith('v-');
}
isEventName (attrName) {
return attrName.startsWith('@');
}
}
// 初始化
class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 实现一个数据观察者
new Observer(this.$data)
// 实现一个模版解析器
new Compile(this.$el, this)
// 代理
this.proxyData(this.$data)
}
}
proxyData (data) {
for (const key in data) {
Object.defineProperty(this, key, {
get () {
return data[key];
},
set (newVal) {
alert(1)
data[key] = newVal;
}
})
}
}
}
observe
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm;
this.expr = expr;
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)
}
// 通知观察者去更新
notify () {
// console.log('通知了观察者', this.subs);
this.subs.forEach(w => w.update())
}
}
// observe: 劫持监听所有属性
class Observer {
constructor(data) {
this.observe(data);
}
observe (data) {
// console.log(data)
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: (newVal) => {
this.observe(newVal);
if (newVal !== value) {
value = newVal;
}
// 通知Dep变化
dep.notify();
}
})
}
}