共计 4224 个字符,预计需要花费 11 分钟才能阅读完成。
实现:
/*
* Time: 2021/09/09
* Author: tony_cyt
* About: 简要实现 Vue
*/
class Vue {
/**
* @description: Vue 构造函数
* @param {*} options
* @return {*}
*/
constructor(options) {
// 赋值 el 和 data
const {el, data, computed} = options;
this.$el = el;
this.$data = data;
// 如果 $el 存在,就走编译流程
if (this.$el) {new Observer(this.$data);
for (const key in computed) {
Object.defineProperty(this.$data, key, {get: () => {return computed[key].call(this);
}
})
}
this.proxyData(this.$data);
new Compiler(this.$el, this);
}
}
proxyData(data) {for (const key in data) {
Object.defineProperty(this, key, {get: () => {return data[key];
}
})
}
}
}
/**
* @description: 自定义指令集
* @param {*}
* @return {*}
*/
CompilerUtil = {getVal(vm, expr) {return expr.split('.').reduce((data, current) => {return data[current];
}, vm.$data)
},
setVal(vm, expr, value) {expr.split('.').reduce((data, current, index, arr) => {if (index === arr.length - 1) {return data[current] = value;
}
return data[current];
}, vm.$data);
},
model(node, expr, vm) {const value = this.getVal(vm, expr);
const fn = this.updater['modeUpdater'];
new Watcher(vm, expr, newValue => {fn(node, newValue);
})
node.addEventListener('input', e => {this.setVal(vm, expr, e.target.value);
})
fn(node, value);
},
getContentValue(vm, expr) {return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {return this.getVal(vm, args[1]);
})
},
text(node, expr, vm) {const fn = this.updater['textUpdater'];
const 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);
},
html() {},
updater: {modeUpdater(node, value) {node.value = value;},
textUpdater(node, value) {node.textContent = value;},
htmlUpdater() {}
}
}
class Compiler {
/**
* @description: 编译器构造函数
* @param {*} el
* @param {*} vm
* @return {*}
*/
constructor(el, vm) {
this.vm = vm;
this.el = this.isElementNode(el) ? el : document.querySelector(el);
const fragment = this.nodeToFragment(this.el);
this.compile(fragment);
this.el.append(fragment);
}
/**
* @description: 判断是不是元素节点
* @param {*} node
* @return {*}
*/
isElementNode(node) {return node.nodeType === 1;}
/**
* @description: 生成文档片段
* @param {*} node
* @return {*}
*/
nodeToFragment(node) {const fragment = document.createDocumentFragment();
let firstChild;
while (firstChild = node.firstChild) {fragment.append(firstChild);
}
return fragment;
}
/**
* @description: 依据类型编译
* @param {*} node
* @return {*}
*/
compile(node) {[...node.childNodes].forEach(child => {if (this.isElementNode(child)) {this.compileElement(child);
// 递归节点遍历
this.compile(child);
} else {this.compileText(child);
}
})
}
/**
* @description: 编译文本节点
* @param {*} node
* @return {*}
*/
compileText(node) {const reg = /\{\{(.+?)\}\}/;
if (reg.test(node.textContent)) {CompilerUtil['text'](node, node.textContent, this.vm);
}
}
/**
* @description: 判断是否是指令
* @param {*} attrName
* @return {*}
*/
isDirective(attrName) {return attrName.startsWith('v-');
}
/**
* @description: 编译元素节点
* @param {*} node
* @return {*}
*/
compileElement(node) {[...node.attributes].forEach(attr => {const { name, value: expr} = attr;
if (this.isDirective(name)) {const [, directive] = name.split('-');
CompilerUtil[directive](node, expr, this.vm);
}
})
}
}
/**
* @description: 实现数据响应式
* @param {*}
* @return {*}
*/
class Observer {constructor(data) {this.observer(data);
}
observer(data) {if (data && typeof data === 'object') {for (const key in data) {this.defineReactive(data, key, data[key]);
}
}
}
defineReactive(obj, key, value) {this.observer(value);
const dep = new Dep();
Object.defineProperty(obj, key, {get() {Dep.target && dep.subs.push(Dep.target);
return value;
},
set: (newValue) => {if (newValue !== value) {this.observer(newValue);
value = newValue;
dep.notify();}
}
})
}
}
/**
* @description: 存储观察者
* @param {*}
* @return {*}
*/
class Dep {constructor() {this.subs = [];
}
addSub(watcher) {this.subs.push[watcher];
}
notify() {
this.subs.forEach(watcher => {watcher.update();
})
}
}
class Watcher {constructor(vm, expr, cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
this.oldValue = this.get();}
get() {
Dep.target = this;
const value = CompilerUtil.getVal(this.vm, this.expr);
Dep.target = null;
return value;
}
update() {const newValue = CompilerUtil.getVal(this.vm, this.expr);
if (newValue !== this.oldValue) {this.cb(newValue);
}
}
}
应用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MVVM</title>
</head>
<body>
<div id="app">
<input type="text" v-model='school.name'>
<div>{{school.name}}</div>
<div>{{school.age}}</div>
{{newName}}
<ul>
<li>1</li>
<li>2</li>
</ul>
</div>
<script src="VueMVVM.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
school: {
name: "qinghua",
age: 20
}
},
computed: {newName() {return this.school.name + 'new'}
}
})
</script>
</body>
</html>
成果:
正文完
发表至: javascript
2021-11-03