vue核心就是雙向綁定我們都知道是用Object.defineProperty
但是我們在用vue得時候 可以通過this.xx訪問修改屬性, 不需要this.$data
還有如何實現(xiàn)數(shù)據(jù)變動dom就更新
如何解析dom上得指令和方法
下面代碼將一步一步實現(xiàn)一個小vue
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="app">
<input type="text" v-model="name">
<div v-html="name"></div>
<button @click="changeName">
按鈕
</button>
</div>
</body>
<script type="text/javascript">
// 負(fù)責(zé)收集更新函數(shù),屬于發(fā)布訂閱模式中的調(diào)度中心
class Dep {
constructor() {
this._listeners = []
}
add(obj) {
this._listeners.push(obj)
}
notify() { // 更新dom
this._listeners.forEach(item => item.update && item.update())
}
}
// 觀察者,直接跟DOM,負(fù)責(zé)將更新函數(shù)傳遞給Dep
class Watcher {
constructor(options) {
this.update = options.update
Dep.target = this
this.val = options.allVal[options.key] // 觸發(fā)get方法 將數(shù)據(jù)插入到Dep
Dep.target = null
}
}
class Vue {
constructor(options) {
this.$options = options
this.$data = options.data()
// 劫持this, 始得vue實例得可以直接this.xxx訪問 this.$data得屬性
this.observerRoot() // 劫持根實例
this.observerData(this.$data) // 劫持$data 修改每一個值都知道
this.createFragment() // 創(chuàng)建虛擬元素,避免頻繁操作真實DOM
this.compile() // 編譯元素,解析指令、事件、方法
}
observerRoot() {
Object.keys(this.$data).forEach(item => {
Object.defineProperty(this, item, {
get: function () {
return this.$data[item]
},
set: function (newVal) {
this.$data[item] = newVal
}
})
})
}
// 劫持$data
observerData(obj) {
if (!obj || typeof obj != 'object') return
Object.keys(obj).forEach(item => {
let val = obj[item] // 防止死循環(huán)
if (typeof val === 'object') {
this.observerData(obj[item])
} else {
let dep = new Dep()
Object.defineProperty(obj, item, {
get: function () {
Dep.target && dep.add(Dep.target)
return val
},
set: function (newVal) {
val = newVal
dep.notify()
}
})
}
})
}
// 創(chuàng)建虛擬dom
createFragment() {
this.$el = document.querySelector(this.$options.el)
this.$fragment = document.createDocumentFragment()
while (this.$el.firstChild) {
this.$fragment.appendChild(this.$el.firstChild)
}
}
// 編譯
compile() {
// 1. 解析
this._compileElement(this.$fragment)
// 2. 重新append到根元素下
this.$el.appendChild(this.$fragment)
}
_compileElement(ele) {
Array.from(ele.childNodes).forEach(node => {
// 解析節(jié)點(diǎn)
this._compileNode(node)
if (node.childNodes) this._compileElement(node)
})
}
_compileNode(node) {
// 1. 解析節(jié)點(diǎn)包含的指令、事件...
let res = this._checkHasBind(node)
// 2. 處理解析結(jié)果
this._resolveBind(node, res)
}
_checkHasBind(node) {
// 獲取node節(jié)點(diǎn)上得屬性
let attributes = node.attributes
let dir_reg = /^v\-\w*$/
let event_reg = /^\@\w*/
let result = {
directives: [], // 指令
events: [] // 事件
}
if (attributes) Array.from(attributes).forEach(item => {
if (dir_reg.test(item.name)) result.directives.push({ name: item.name, value: item.value })
if (event_reg.test(item.name)) result.events.push({ name: item.name, value: item.value })
})
return result
}
_resolveBind(node, res) {
let _this = this
let data = this.$data
let { directives, events } = res
// 解析指令
directives.length && directives.forEach(item => {
// node有指令得節(jié)點(diǎn) item指令
let update = () => {
switch (item.name) {
case 'v-model':
node.oninput = (val) => {
_this[item.value] = node.value || ''
}
node.value = data[item.value]
break;
case 'v-html':
node.innerHTML = data[item.value]
break;
default: break;
}
}
update()
// 此處這個配置,沒有直接和dep交互。所以是發(fā)布訂閱模式而不是觀察者模式。
let watch_option = {
allVal: data,
key: item.value,
directive: item.name,
node: node,
update
}
new Watcher(watch_option)
})
// 解析綁定事件
events.length && events.forEach(item => {
let method_name = item.value
// 解析出來的事件,是以@為開頭,需要處理
let target_event = item.name.slice(1, item.name.length)
node.addEventListener(target_event, () => {
this.$options.methods[method_name].call(this)
})
})
}
}
let app = new Vue({
el: '#app',
data() {
return {
name: '二狗子'
}
},
methods: {
changeName() {
this.name = Math.floor(Math.random() * 10)
}
}
})
</script>
</html>