<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="message">
{{message}}
</div>
<!-- 最后展示給用戶看的視圖 -->
<!-- ===> -->
<!-- <input type="text" value="hyj">
hyj -->
</body>
function Mvvm({el, $data}) {
// 獲取到了根元素
this.root = document.querySelector(el);
// 掛載了數(shù)據(jù)
this.$data = $data;
// 劫持和監(jiān)聽數(shù)據(jù)
observer(this.$data, this); // 將數(shù)據(jù)和實(shí)例給監(jiān)聽者
// 編譯模板, 編譯完成之后, 返回DOM片段,才是展示給用戶看的頁面,
let fragment = createFragment(this.root, this);
// 重新放入到根元素中
this.root.appendChild(fragment);
}
function createFragment(root, vm) { // root: 根節(jié)點(diǎn), vm: 實(shí)例
// 先默認(rèn)指定第一個節(jié)點(diǎn)
let child = root.firstChild;
// 創(chuàng)建一個dom片段
let fragment = document.createDocumentFragment();
while (child) {
// 拿到每一個子節(jié)點(diǎn)沟沙,然后編譯一遍
compile(child, vm);
// 編譯完成之后放入DOM片段中
fragment.appendChild(child);
// 編譯完一個就重置一個
child = root.firstChild;
}
return fragment;
}
// 編譯解析函數(shù)
function compile(node, vm) {
// 判斷節(jié)點(diǎn)類型: 如果是1的話說明是元素培他,然后我們只考慮數(shù)據(jù)雙向綁定仪缸,所以采用v-model + 單行文本輸入框
if (node.nodeType === 1) {
let attrs = node.attributes;
// 獲取v-model上的變量名
let vModelVal = attrs['v-model'].value;
node.addEventListener('input', function (e) {
vm[vModelVal] = e.target.value;
})
node.value = vm[vModelVal];
new Watch(node, vModelVal, vm);
}
// 文本節(jié)點(diǎn)為3
if (node.nodeType === 3) {
// 獲取文本值
let textCon = node.textContent;
let reg = /\{\{(.*)\}\}/g;
if (reg.test(textCon)) {
new Watch(node, RegExp.$1, vm);
}
}
}
// 監(jiān)聽者
function observer(data, vm) {
Object.keys(data).forEach(k => {
// 每一個數(shù)據(jù)屬性都做劫持伴榔,并劫持在vm上
defineProps(data[k], k, vm);
});
}
// 劫持
function defineProps(val, k, vm) {
// 表面上: 是定義給了vm實(shí)例上屬性僵井,實(shí)際上是將值放在vm實(shí)例$data里面
let dep = new Dep();
Object.defineProperty(vm, k, {
// 做設(shè)置的劫持
set(newVal) {
if (val === newVal) return;
vm.$data[k] = newVal;
// 通知更新
dep.notify();
},
// 做讀取的劫持
get() {
// 將當(dāng)前的訂閱者添加到訂閱器數(shù)組中
if (Dep.target) {
dep.addSub(Dep.target);
}
return vm.$data[k];
}
});
}
// 訂閱器
function Dep() {
// 訂閱者數(shù)組中
this.subArr = [];
}
// 將訂閱者放入訂閱者數(shù)組中
Dep.prototype.addSub = function (tar) {
this.subArr.push(tar)
}
// 通知每一個訂閱者去更新
Dep.prototype.notify = function () {
this.subArr.forEach(sub => sub.update())
}
// 臨時存儲訂閱者
Dep.target = null;
// 訂閱者
function Watch(node, key,vm) {
Dep.target = this;
this.node = node;
this.key = key;
this.vm = vm;
this.update();
Dep.target = null;
}
Watch.prototype = {
constructor: Watch,
// 更新的方法
update() {
this.get();
if (this.node.nodeType === 1) {
this.node.value = this.value;
}
if (this.node.nodeType === 3) {
this.node.textContent = this.value;
}
},
// 獲取數(shù)據(jù)的方法
get() {
this.value = this.vm[this.key];
// this.value = 實(shí)例[message]
}
}
let newVm = new Mvvm({
el: '#app', // 根元素
$data: { // 數(shù)據(jù)對象
message: 'zrr'
}
});
setTimeout(() => {
newVm.message = 'zrrrrrrrrr';
}, 3000)
// Mvvm / Mvc => 概念性
</script>
</html>