說一說我對于mvvm模型的理解吧
我第一次接觸mvvm也是在學習vue的時候,在我看來vue和react都是資料驅動視圖,但是vue屬于標準的mvvm模型,react是從組件化演變而來
不多廢話,直接粘圖

第一次使用mvvm的時候感覺特別的神奇,我只是修改了資料就可以驅動視圖的改變
- 學習mvvm模型的作用
一開始就是在學習vue的使用還有vuex等等很多,也能做一些小的網站,但就是沒有辦法提升自己的vue到一個更高的境界,后來就不斷的往深了學習
聽過網上的這么一句話
編程世界和武俠世界比較像,每一個入門的程式員,都幻想自己有朝一日,神功大成,青衣長劍,救民于水火之中,但是其實大部分的人一開始學習方式就錯了,導致一直無法進入高手的行列,就是過于看中招式,武器,而忽略了內功的修煉,所以任你慕容復有百家武學,還有被我喬峰一招制敵,所以這就是內功差距
原理就是內功修煉的捷徑
進入主題
- 原理
Object.defineProperty(obj,name,{get:function(),set:function()})
- 手寫
mvvm主要分為兩部
- kvue.js
- 獲取資料,先獲取options
- 把options.data的資料通過Object.key()決議
- 進入主題 Obejct.defineProprety() 進行雙向系結
- 接下來是兩個類 Dep 和 Watcher (關系可以看上面的圖片)
- compile.js
- 獲取dom宿主節點 options.el
- 把宿主節點拿出來遍歷,高效 createDocumentFragment()
- 編譯程序 判斷是否是文本節點,如果是文本節點就通過正則的分組獲取到{{}}插值運算式中間的值
- 更新函式 初始化更新函式,呼叫Watcher
第一次寫,怕說不明白,直接粘上代碼
先創建目錄結構

代碼
- index.html
<!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">
<!-- 插值系結 -->
<p>{{name}}</p>
<!-- 指令決議 -->
<p k-text="name"></p>
<p>{{age}}</p>
<p>
{{doubleAge}}
</p>
<!-- 雙向系結 -->
<input type="text" k-model="name" />
<!-- 事件處理 -->
<button @click="changeName">呵呵</button>
<!-- html內容決議 -->
<div k-html="html"></div>
</div>
<script src="https://www.cnblogs.com/sunhang32/p/compile.js"></script>
<script src="https://www.cnblogs.com/sunhang32/p/kvue.js"></script>
<script>
const kaikeba = new KVue({
el: "#app",
data: {
name: "I am test.",
age: 12,
html: "<button>這是一個按鈕</button>"
},
created() {
console.log("開始啦");
setTimeout(() => {
this.name = "我是測驗";
}, 1500);
},
methods: {
changeName() {
this.name = "殘夢a博客園";
this.age = 1;
}
}
});
</script>
</body>
</html>
- kvue.js
// new KVue({data:{...}})
class KVue {
constructor(options) {
this.$options = options;
// 資料回應化
this.$data = https://www.cnblogs.com/sunhang32/p/options.data;
this.observe(this.$data);
// 模擬一下watcher創建
// new Watcher();
// // 通過訪問test屬性觸發get函式,添加依賴
// this.$data.test;
// new Watcher();
// this.$data.foo.bar;
new Compile(options.el, this);
// created執行
if (options.created) {
options.created.call(this);
}
}
observe(value) {
if (!value || typeof value !=="object") {
return;
}
// 遍歷該物件
Object.keys(value).forEach(key => {
this.defineReactive(value, key, value[key]);
// 代理data中的屬性到vue實體上
this.proxyData(key);
});
}
// 資料回應化
defineReactive(obj, key, val) {
this.observe(val); // 遞回解決資料嵌套
const dep = new Dep();
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addDep(Dep.target);
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// console.log(`${key}屬性更新了:${val}`);
dep.notify();
}
});
}
proxyData(key) {
Object.defineProperty(this, key, {
get(){
return this.$data[key]
},
set(newVal){
this.$data[key] = newVal;
}
})
}
}
// Dep:用來管理Watcher
class Dep {
constructor() {
// 這里存放若干依賴(watcher)
this.deps = [];
}
addDep(dep) {
this.deps.push(dep);
}
notify() {
this.deps.forEach(dep => dep.update());
}
}
// Watcher
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
this.key = key;
this.cb = cb;
// 將當前watcher實體指定到Dep靜態屬性target
Dep.target = this;
this.vm[this.key]; // 觸發getter,添加依賴
Dep.target = null;
}
update() {
// console.log("屬性更新了");
this.cb.call(this.vm, this.vm[this.key]);
}
}
- complie.js
// 用法 new Compile(el, vm)
class Compile {
constructor(el, vm) {
// 要遍歷的宿主節點
this.$el = document.querySelector(el);
this.$vm = vm;
// 編譯
if (this.$el) {
// 轉換內部內容為片段Fragment
this.$fragment = this.node2Fragment(this.$el);
// 執行編譯
this.compile(this.$fragment);
// 將編譯完的html結果追加至$el
this.$el.appendChild(this.$fragment);
}
}
// 將宿主元素中代碼片段拿出來遍歷,這樣做比較高效
node2Fragment(el) {
const frag = document.createDocumentFragment();
// 將el中所有子元素搬家至frag中
let child;
while ((child = el.firstChild)) {
frag.appendChild(child);
}
return frag;
}
// 編譯程序
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
// 型別判斷
if (this.isElement(node)) {
// 元素
// console.log('編譯元素'+node.nodeName);
// 查找k-,@,:
const nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
const attrName = attr.name; //屬性名
const exp = attr.value; // 屬性值
if (this.isDirective(attrName)) {
// k-text
const dir = attrName.substring(2);
// 執行指令
this[dir] && this[dir](node, this.$vm, exp);
}
if (this.isEvent(attrName)) {
const dir = attrName.substring(1); // @click
this.eventHandler(node, this.$vm, exp, dir);
}
});
} else if (this.isInterpolation(node)) {
// 文本
// console.log('編譯文本'+node.textContent);
this.compileText(node);
}
// 遞回子節點
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node);
}
});
}
compileText(node) {
// console.log(RegExp.$1);
this.update(node, this.$vm, RegExp.$1, "text");
}
// 更新函式
update(node, vm, exp, dir) {
const updaterFn = this[dir + "Updater"];
// 初始化
updaterFn && updaterFn(node, vm[exp]);
// 依賴收集
new Watcher(vm, exp, function(value) {
updaterFn && updaterFn(node, value);
});
}
text(node, vm, exp) {
this.update(node, vm, exp, "text");
}
// 雙綁
model(node, vm, exp) {
// 指定input的value屬性
this.update(node, vm, exp, "model");
// 視圖對模型回應
node.addEventListener("input", e => {
vm[exp] = e.target.value;
});
}
modelUpdater(node, value) {
node.value = https://www.cnblogs.com/sunhang32/p/value;
}
html(node, vm, exp) {
this.update(node, vm, exp,"html");
}
htmlUpdater(node, value) {
node.innerHTML = value;
}
textUpdater(node, value) {
node.textContent = value;
}
// 事件處理器
eventHandler(node, vm, exp, dir) {
// @click="onClick"
let fn = vm.$options.methods && vm.$options.methods[exp];
if (dir && fn) {
node.addEventListener(dir, fn.bind(vm));
}
}
isDirective(attr) {
return attr.indexOf("k-") == 0;
}
isEvent(attr) {
return attr.indexOf("@") == 0;
}
isElement(node) {
return node.nodeType === 1;
}
// 插值文本
isInterpolation(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/176010.html
標籤:JavaScript
