簡陋版 MVVM 原理實現
筆者也是第一次學習這個原理,聽不懂的東西還挺多的,所以程式碼能用的就用,視訊文章尾部自取。
視訊中所講的程式碼,大致如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="school.name">
<div class="">{{school.name}}</div>
<div class="">{{school.age}}</div>
<!-- 如果資料不變化,檢視就不會重新整理 -->
{{getNewName}}
<!-- -->
<ul>
<li>1</li>
<li>1</li>
</ul>
<button v-on:click="change">update</button>
<div class="" v-html="message"></div>
</div>
<script src="./mvvm.js"></script>
<script>
let vm = new Vue({
el: '#app',
data: {
school: {
name: 'vue',
age: 10
},
message: '<h1>JS</h1>'
},
computed: {
getNewName() {
return this.school.name + '3.0'
}
},
methods: {
change() {
this.school.name = 'react'
}
},
});
</script>
</body>
</html>
mvvm.js
// 觀察者(釋出訂閱)
class Dep {
constructor() {
// 存放所有的 watcher
this.subs = []
}
// 訂閱
addSub(watcher) {
// 新增 watcher
this.subs.push(watcher)
}
// 釋出
notify() {
this.subs.forEach(watcher => watcher.update())
}
}
// new Watcher
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
// 預設存放舊值
this.oldValue = this.get()
}
get() {
Dep.target = this // 先把自己放在 this 上
// 取值 把這個觀察者和 資料關聯起來
let value = CompileUtil.getVal(this.vm, this.expr)
Dep.target = null
return value
}
// 更新操作 資料變化後,會呼叫觀察者的 update 方法
update() {
let newVal = CompileUtil.getVal(this.vm, this.expr)
if (newVal !== this.oldValue) {
this.cb(newVal)
}
}
}
// vm.$watch(vm, 'school.name', (newVal) => {
// })
// 實現資料劫持功能
class Observer{
constructor(data) {
this.observer(data)
}
observer(data) {
// 如果是物件,才進行觀察
if (data && typeof data === 'object') {
// 如果是物件
for (let key in data) {
this.defineReactive(data, key, data[key])
}
}
}
defineReactive(obj, key, value) {
this.observer(value)
let dep = new Dep(value) // 給每一個屬性,都加上一個具有釋出訂閱的功能
Object.defineProperty(obj, key, {
get() {
// 建立 watcher時,會取到相對應的內容,並且把 watcher 放到全域性上
Dep.target && dep.addSub(Dep.target)
return value
},
set:(newVal) => {
if (newVal != value) {
this.observer(newVal)
value = newVal
dep.notify()
}
}
})
}
}
// 基類 排程
class Compiler {
constructor(el, vm) {
// 判斷 el 屬性, 是不是一個元素, 如果不是元素, 進行獲取
this.el = this.isElementNode(el) ? el : document.querySelector(el)
// 將當前節點的元素,獲取之後存放到記憶體中
this.vm = vm
let fragment = this.node2fragment(this.el)
// 將節點中的內容進行替換
// 編譯模板 用資料編譯
this.compile(fragment)
// 把頁面重新渲染到頁面中
this.el.appendChild(fragment)
}
isDirective(attrName) {
return attrName.startsWith('v-')
}
// 編譯元素
compileElement(node) {
let attributes = node.attributes;
[...attributes].forEach(attr => {
// console.log(attr)
let { name, value: expr } = attr
// console.log(name, value)
if (this.isDirective(name)) {
// console.log(node, 'element')
let [, directive] = name.split('-')
let [directiveName, eventName] = directive.split(':')
// 需要呼叫不同的指令來處理
CompileUtil[directiveName](node, expr, this.vm, eventName)
}
})
}
// 編譯文字 包含 {{}}
compileText(node) {
let content = node.textContent
// console.log(content, 'text')
if (/\{\{(.+?)\}\}/.test(content)) {
console.log(node, 'text')
// 找到所有文字
CompileUtil['text'](node, content, this.vm)
}
}
// 編譯記憶體中的 dom 節點
compile(node) {
let childNodes = node.childNodes;
// 類陣列,轉為陣列
[...childNodes].forEach(child => {
if (this.isElementNode(child)) {
this.compileElement(child)
// 如果是元素的話,繼續遍歷子節點
// 遞迴呼叫
this.compile(child)
} else {
this.compileText(child)
}
})
// console.log(childNodes)
}
node2fragment(node) {
// 建立一個文件碎片
let fragment = document.createDocumentFragment()
let firstChild
while (firstChild = node.firstChild) {
// console.log(firstChild)
// appChild 具有移動性。從頁面中獲取,存放到記憶體中,頁面的元素就會少一個
fragment.appendChild(firstChild)
}
return fragment
}
// 判斷是否是元素節點
isElementNode(node) {
return node.nodeType === 1
}
}
CompileUtil = {
// 根據表示式取到對應的資料
getVal(vm, expr) {
// vm.$data
return expr.split('.').reduce((data, current) => {
return data[current]
}, vm.$data)
},
setValue(vm, expr, value) {
expr.split('.').reduce((data, current, index, arr) => {
if (index === arr.length - 1) {
return data[current] = value
}
return data[current]
}, vm.$data)
},
// 解析 v-model 指令
model(node, expr, vm) {
// node 是節點 expr 是表示式 vm 是當前例項,
let fn = this.updater['modelUpdater']
new Watcher(vm, expr, (newVal) => {
// 給輸入框加一個觀察者,如果資料更新了,會觸發此方法
// 會各輸入框賦予心智
fn(node, newVal)
})
node.addEventListener('input', (e) => {
let value = e.target.value // 獲取使用者輸入的內容
this.setValue(vm, expr, value)
})
let value = this.getVal(vm, expr)
fn(node, value)
},
html(node, expr, vm) {
let fn = this.updater['htmlUpdater']
new Watcher(vm, expr, (newVal) => {
fn(node, newVal)
})
let value = this.getVal(vm, expr)
fn(node, value)
},
getContentValue(vm, expr) {
// 遍歷表示式,將內容重新替換成完整的內容,返還回去
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(vm, args[1])
})
},
on(node, expr, vm, eventName) {
// v-on:click="change"
node.addEventListener(eventName, (e) => {
vm[expr].call(vm, e) // this change
})
},
text(node, expr, vm) {
// expr => {{a}} {{b}} {{c}}
let fn = this.updater['textUpdater']
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
// 給表示式 {{}} 都加上觀察者
new Watcher(vm, args[1], () => {
fn(node, this.getContentValue(vm, expr)) // 返回一個全的字串
})
return this.getVal(vm, args[1])
})
fn(node, content)
},
updater: {
// 把資料插入到節點中
modelUpdater(node, value) {
node.value = value
},
htmlUpdater(node, value) {
node.innerHTML = value
},
textUpdater(node, value) {
node.textContent = value
}
}
}
class Vue {
constructor(optinos) {
// this.$el $data $options
this.$el = optinos.el
this.$data = optinos.data
let computed = optinos.computed
let methods = optinos.methods
// 如果根元素 存在 進行編譯模板
if (this.$el) {
// 把資料全部轉化為 Object.defineProperty 來定義
new Observer(this.$data)
// 把資料獲取操作 vm 上的取值操作都代理到 vm.$data
//
for(let key in computed) {
Object.defineProperty(this.$data, key, {
get:() => {
return computed[key].call(this)
}
})
}
for(let key in methods) {
Object.defineProperty(this, key, {
get() {
return methods[key]
}
})
}
this.proxyVm(this.$data)
console.log(this.$data)
new Compiler(this.$el, this)
}
}
proxyVm(data) {
for(let key in data) {
// 取值可以通過 vm 取到相對應的值
Object.defineProperty(this, key, {
get() {
// 進行轉化操作
return data[key]
},
set(newVal) {
data[key] = newVal
}
})
}
}
}
連結:https://pan.baidu.com/s/1tHs97yC21DaObE58PwtJcg
提取碼:50mn
相關文章
- vue--實現MVVM原理VueMVVM
- 10分鐘瞭解MVVM,實現簡易MVVMMVVM
- 前端MVC、MVVM的簡單實現前端MVCMVVM
- MVVM模式和在WPF中的實現(一)MVVM模式簡介MVVM模式
- 剖析Vue原理&實現雙向繫結MVVMVueMVVM
- 基於Vue的簡易MVVM實現VueMVVM
- 使用 Proxy 實現簡單的 MVVM 模型MVVM模型
- MVVM模式到底是什麼?實現原理剖析MVVM模式
- 實現一個簡單的MVVM(Compile)MVVMCompile
- mvvm-simple雙向繫結簡單實現MVVM
- 試著用Proxy 實現一個簡單mvvmMVVM
- Flutter mvvm簡單實戰FlutterMVVM
- Flutter MVVM 簡單實踐FlutterMVVM
- 簡單實現vuex原理Vue
- vue 實現原理及簡單示例實現Vue
- DI 原理解析 並實現一個簡易版 DI 容器
- MVVM原始碼 - 如何實現一個MVVM框架MVVM原始碼框架
- 精簡版 koa 簡單實現
- 簡單實現 VUE 中 MVVM – step4 – 優化Watcher實現VueMVVM優化
- JDBC JdbcUtils( 本博多次出現的簡陋工具類)JDBC
- Redux 原理和簡單實現Redux
- MapReduce原理及簡單實現
- 簡述RPC原理實現RPC
- 簡單的實現vue原理Vue
- 簡單的實現React原理React
- MVVM雙向繫結機制的原理和程式碼實現MVVM
- 簡單版Promise實現Promise
- 簡易版 vue實現Vue
- Proxy實現vue MVVM實踐VueMVVM
- 基於ES5`defineProperty` 實現簡單的 Mvvm框架MVVM框架
- 簡單介紹numpy實現RNN原理實現RNN
- MVVM原理,你看了也會vue MVVMMVVMVue
- Laravel API 文件太簡陋了LaravelAPI
- Flutter 中的 MVVM 實現FlutterMVVM
- 是的,你沒看錯!這才是MVVM原理實現的正規軍MVVM
- 防抖原理以及簡單實現
- 簡單、好懂的Svelte實現原理
- 一次簡陋的爬蟲爬蟲