一个简易的 Vue 框架:
1、用来解释 vue 的响应式原理 2、实现简单的模版指令{{}}、v-model 指令和事件指令
https://github.com/userkang/MyVue
1、 安装依赖
npm install2、 启动本地服务
npm run devindex.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>myVue</title> </head> <body> <div id="app"> <input type="text" v-model="text"> <h1>{{text}}</h1> <h2>{{text}}</h2> <button v-on:click="clickToChange">reset</button> </div> </body> <script src="./dist/myvue.js"></script> <script> document.addEventListener('DOMContentLoaded', () => { new MyVue({ el: '#app', data: { text: '', }, methods: { clickToChange() { this.text = 'myVue' } }, mounted() { this.text = 'myVue' } }) }) </script> </html>index.js:
import { observe } from './observer.js' import { Compile } from './compile.js' class MyVue { constructor(options) { this.data = options.data this.methods = options.methods // 添加属性代理 Object.keys(this.data).forEach(key => { this.proxyKeys(key) }) // 注册监听 observe(this.data) // 解析和初始化模版,并注册订阅者 new Compile(options.el, this) // 执行 mounted 函数 options.mounted.call(this) } proxyKeys(key) { Object.defineProperty(this, key, { enumerabel: true, configurable: true, get() { return this.data[key] }, set(newVal) { this.data[key] = newVal } }) } } window.MyVue = MyVueobserver.js:
/** * 监听者 */ class Observer { constructor(data) { this.data = data this.walk(data) } walk(data) { Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]) }) } defineReactive(data, key, val) { const dep = new Dep() observe(val) Object.defineProperty(data, key, { enumerable: true, configurable: true, get() { // 如果当前有缓存订阅者,就添加一个新订阅者 if (Dep.target) { // 添加一个订阅者 dep.addSub(Dep.target) } return val }, set(newVal) { if (newVal === val) { return } val = newVal // 如果数据有变化,通知所有订阅者 dep.notify() } }) } } // 消息订阅器 export class Dep { constructor() { this.subs = [] } addSub(sub) { this.subs.push(sub) } notify() { this.subs.forEach(sub => { // 调用订阅者的 update 方法 sub.update() }) } } Dep.target = null export const observe = value => { if (!value || typeof value !== 'object') { return } return new Observer(value) }watcher.js:
import { Dep } from './observer.js' // 订阅者 export class Watcher { constructor(vm, exp, cb) { this.cb = cb this.vm = vm this.exp = exp this.value = this.get() } update() { this.run() } run() { let value = this.vm.data[this.exp] let oldVal = this.value if (value !== oldVal) { this.value = value this.cb.call(this.vm, value, oldVal) } } get() { // 缓存订阅者自己 Dep.target = this // 强制执行监听器里的 get 函数,这里的执行顺序不能变 let value = this.vm.data[this.exp] // 清除自己 Dep.target = null return value } }compile.js:
import { Watcher } from './watcher.js' /** * 解析器 * 1、解析模版指令,并替换模版数据,初始化视图 * 2、将模版指令对应的节点绑定对应的更新函数,初始化相应的订阅器 */ export class Compile { constructor(el, vm) { this.vm = vm this.el = document.querySelector(el) this.fragment = null this.init() } init() { if (this.el) { this.fragment = this.nodeToFragment(this.el) this.compileElement(this.fragment) this.el.appendChild(this.fragment) } else { console.log('Dom 元素不存在') } } // 将节点转为文档片段 nodeToFragment(el) { let fragment = document.createDocumentFragment() let child = el.firstChild while (child) { fragment.appendChild(child) child = el.firstChild } return fragment } // 编译节点 compileElement(el) { const childNodes = el.childNodes Array.prototype.slice.call(childNodes).forEach(node => { const reg = /\{\{(.*)\}\}/ const text = node.textContent if (this.isElementNode(node)) { this.compile(node) } else if (this.isTextNode(node) && reg.test(text)) { this.compileText(node, reg.exec(text)[1]) } // 如果有子节点,递归编译 if (node.childNodes && node.childNodes.length) { this.compileElement(node) } }) } // 编译指令 compile(node) { const nodeAttrs = node.attributes Array.prototype.forEach.call(nodeAttrs, attr => { let attrName = attr.name // 是否是指令属性 if (this.isDirective(attrName)) { const exp = attr.value const dir = attrName.substring(2) // 是否是事件指令属性 if (this.isEventDirective(dir)) { this.compileEvent(node, exp, dir) } else { // v-model 指令 this.compileModel(node, exp) } node.removeAttribute(attrName) } }) } // 编译 {{}} 文本模版语法 compileText(node, exp) { const initText = this.vm[exp] this.updateText(node, initText) new Watcher(this.vm, exp, value => { this.updateText(node, value) }) } // 编译 v-on 事件指令 compileEvent(node, exp, dir) { const eventType = dir.split(':')[1] const cb = this.vm.methods && this.vm.methods[exp] if (eventType && cb) { node.addEventListener(eventType, cb.bind(this.vm)) } } // 编译 v-model 指令 compileModel(node, exp) { let val = this.vm[exp] this.modelUpdater(node, val) new Watcher(this.vm, exp, value => { this.modelUpdater(node, value) }) node.addEventListener('input', e => { const newValue = e.target.value if (val === newValue) { return } this.vm[exp] = newValue val = newValue }) } // 更新文本数据 updateText(node, value) { node.textContent = typeof value === 'undefined' ? '' : value } // 更新 model 数据 modelUpdater(node, value) { node.value = typeof value === 'undefined' ? '' : value } // 判断是否是指令 isDirective(attr) { return attr.indexOf('v-') === 0 } // 判断是否是事件指令 isEventDirective(dir) { return dir.indexOf('on:') === 0 } // 判断是否是元素节点 isElementNode(node) { return node.nodeType === 1 } // 判断是否是文本节点 isTextNode(node) { return node.nodeType === 3 } }