MyVue
一个简易的 Vue 框架:
1、用来解释 vue 的响应式原理
2、实现简单的模版指令{{}}、v-model 指令和事件指令
项目地址
https://github.com/userkang/MyVue
用法
1、 安装依赖
npm install
2、 启动本地服务
npm run dev
部分代码
index.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 = MyVue
observer.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
}
}