实现:
/*
* 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>
效果:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。