实现:
/* * 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>
成果: