vue核心就是双向绑定我们都知道是用Object.defineProperty
但是我们在用vue得时候 可以通过this.xx访问修改属性, 不需要this.$data
还有如何实现数据变动dom就更新
如何解析dom上得指令和方法
下面代码将一步一步实现一个小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">
// 负责收集更新函数,属于发布订阅模式中的调度中心
class Dep {
constructor() {
this._listeners = []
}
add(obj) {
this._listeners.push(obj)
}
notify() { // 更新dom
this._listeners.forEach(item => item.update && item.update())
}
}
// 观察者,直接跟DOM,负责将更新函数传递给Dep
class Watcher {
constructor(options) {
this.update = options.update
Dep.target = this
this.val = options.allVal[options.key] // 触发get方法 将数据插入到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() // 创建虚拟元素,避免频繁操作真实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] // 防止死循环
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()
}
})
}
})
}
// 创建虚拟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 => {
// 解析节点
this._compileNode(node)
if (node.childNodes) this._compileElement(node)
})
}
_compileNode(node) {
// 1. 解析节点包含的指令、事件...
let res = this._checkHasBind(node)
// 2. 处理解析结果
this._resolveBind(node, res)
}
_checkHasBind(node) {
// 获取node节点上得属性
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有指令得节点 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交互。所以是发布订阅模式而不是观察者模式。
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>
网友评论