vue简单指令编译和双向绑定的原理
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{msg}}</h3>
<div v-text="msg"></div>
<div v-html = 'htmlStr'></div>
<input type="text" v-model = 'msg'>
<button v-on:click="handleClick">onclick</button>
<button @click="handleClick">@click</button>
</div>
<!-- <script src="./vue.js"></script> -->
<script src="./MVue.js"></script>
<script src="./Observer.js"></script>
<script>
let vm = new MVue({
el: "#app",
data: {
person: {
name: '小马哥',
age: '18',
fav: '书法'
},
msg: '学习vue的简单实现原理',
htmlStr: '<h1>787887</h1>'
},
methods: {
handleClick () {
this.person.name='1212'
}
}
})
</script>
</body>
</html>
MVue.js
compileUtil = {
getVal (expr, vm) {
return expr.split('.').reduce((data, currentVal) => {
return data[currentVal]
}, vm.$data)
},
setVal (expr, vm, inputVal) {
return expr.split('.').reduce((data, currentVal) => {
return data[currentVal] = inputVal
}, vm.$data)
},
getContentVal (expr, vm) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args)=> {
return this.getVal(args[1], vm)
})
},
text (node, expr, vm) {
let value
if (expr.indexOf('{{') !== -1) {
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
new Watcher(vm, args[1], (newVal) => {
this.updater.textUpdate(node, this.getContentVal(expr, vm))
})
return this.getVal(args[1], vm)
})
} else {
value = this.getVal(expr, vm)
}
this.updater.textUpdate(node, value)
},
html (node, expr, vm) {
const value = this.getVal(expr, vm)
new Watcher(vm, expr, (newVal) => {
this.updater.htmlUpdater(node, newVal)
})
this.updater.htmlUpdater(node, value)
},
model (node, expr, vm) {
const value = this.getVal(expr, vm)
new Watcher(vm, expr, (newVal) => {
this.updater.modelUpdater(node, newVal)
})
node.addEventListener('input', (e)=> {
this.setVal(expr, vm, e.target.value)
})
this.updater.modelUpdater(node, value)
},
on (node, expr, vm, eventName) {
let fn = vm.$options.methods && vm.$options.methods[expr]
node.addEventListener(eventName, fn.bind(vm), false)
},
updater: {
modelUpdater (node, value) {
node.value = value
},
htmlUpdater (node, value) {
node.innerHTML = value
},
textUpdate (node, value) {
node.textContent = value
}
}
}
class Compile{
constructor(el,vm){
this.el = this.isElementNode(el) ? el : document.querySelector(el)
this.vm = vm
// 1.获取文档碎片对象 放入内存中减少页面的回流和重绘
const fragment = this.node2Fragment(this.el)
// 2.编辑模板
this.compile(fragment)
// 3.追加子元素到根元素
this.el.appendChild(fragment)
}
compile (fragment) {
const childNodes = fragment.childNodes;
[...childNodes].forEach((child) =>{
if (this.isElementNode(child)) {
this.compileElement(child)
} else {
this.compileText(child)
}
if (child.childNodes && child.childNodes.length) {
this.compile(child)
}
})
}
compileElement (node) {
const attributes = node.attributes;
[...attributes].forEach(attr => {
const {name, value} = attr
if (this.isDirective(name)) {
const [, directive] = name.split('-')
const [dirName, eventName] = directive.split(':')
compileUtil[dirName](node, value, this.vm, eventName)
node.removeAttribute('v-' + directive)
} else if (this.isEventName(name)) {
let [,eventName] = name.split('@')
compileUtil['on'](node, value, this.vm, eventName)
}
})
}
compileText (node) {
const content = node.textContent;
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtil['text'](node, content, this.vm)
}
}
isEventName (arrtName) {
return arrtName.startsWith('@')
}
isDirective (arrtName) {
return arrtName.startsWith('v-')
}
node2Fragment(el) {
// 创建文档碎片
const f = document.createDocumentFragment()
let firstChild
while (firstChild = el.firstChild) {
f.appendChild(firstChild)
}
return f
}
isElementNode (node) {
return node.nodeType === 1
}
}
class MVue{
constructor(options) {
this.$el = options.el
this.$data = options.data
this.$options = options
if (this.$el) {
// 1.实现一个数据观察者
new Observer(this.$data)
// 2.实现一个指令解析器
new Compile(this.$el, this)
// 代理
this.proxyDara(this.$data)
}
}
proxyDara (data) {
for (const key in data) {
Object.defineProperty(this, key, {
get () {
return data[key]
},
set (newValue) {
data[key] = newValue
}
})
}
}
}
Observer.js
class Watcher{
constructor (vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
this.oldVal = this.getOldVal()
}
getOldVal () {
Dep.target = this
const oldVal = compileUtil.getVal(this.expr, this.vm)
Dep.target = null
return oldVal
}
update () {
const newVal = compileUtil.getVal(this.expr, this.vm)
if (newVal !== this.oldVal) {
this.cb(newVal)
}
}
}
class Dep{
constructor() {
this.subs = []
}
addSub (watcher) {
this.subs.push(watcher)
}
notify () {
this.subs.forEach(w => {
w.update()
})
}
}
class Observer{
constructor(data) {
this.observer(data)
}
observer (data) {
if (data && typeof data === 'object') {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
}
defineReactive (obj, key, value) {
this.observer(value)
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get () {
Dep.target && dep.addSub(Dep.target)
return value
},
set: (newVal)=> {
this.observer(newVal)
if (newVal !== value) {
value = newVal
}
dep.notify()
}
})
}
}