目录结构
此次我们在上篇文章juejin.cn/post/700028… (一)的基础上新增了$nextTick异步更新策略 没有修改的文件就不展示了,只展示修改和新增的文件
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>
<script type="module">
import MVVM from './MVVM.js'
const vm = new MVVM({
el: '#app',
data: {
name: 'wenhao',
age: 21,
intro: ''
},
mounted() {
this.name = 1111
this.name = 2222
this.name = 3333
this.name = 5555
this.name = 666
this.name = 777
this.name = 'name'
this.$nextTick(() => {
console.log(document.getElementById('name').textContent);
})
},
methods: {
},
// watch: {
// name(newValue, oldValue) {
// console.log('watch name');
// }
// },
// computed: {
// intro() {
// console.log('computed intro');
// return this.name + this.age
// }
// }
})
</script>
</head>
<body>
<div id="app">
<p>
我叫1<data id="name">name</data>
</p>
<p>
我的年龄2 <data>age</data>
</p>
<p>
我的年龄 3<data>age</data>
</p>
<p>
我的年龄 3<data>age</data>
</p>
<p>
intro:<data>intro</data>
</p>
</div>
</body>
</html>
dep.js
import queue from "./queue.js"
export default class Dep { // 观察者模式
constructor() {
this.subs = []
}
depend() {
if (Dep.target && !this.subs.includes(Dep.target)) {
this.subs.push(Dep.target)
}
}
notify() {
queue.collect(this.subs) // 改动:我们把所有的要执行的watcher丢到队列中
}
}
queue.js (异步缓冲队列)
export default {
_buffer: [],
_pending: false,
_nextTickCbs: [],
collect(data) {
if (Array.isArray(data)) {
this._buffer = this._buffer.concat(data)
} else {
this._buffer.push(data)
}
if (!this._pending) {
this._pending = true
Promise.resolve().then(() => {
this.release()
this._pending = false
})
}
},
release() {
this._buffer = [...new Set(this._buffer)]
console.log(this._buffer);
while (this._buffer.length > 0) {
this._buffer.shift().update()
}
while (this._nextTickCbs.length > 0) {
this._nextTickCbs.shift()()
}
},
nextTick(cb) {
this._nextTickCbs.push(cb)
}
}
mvvm.js
import Watcher from "./Watcher.js"
import observe from "./observe.js"
import queue from "./queue.js"
export default class MVVM {
constructor({ el, data, mounted, methods, watch, computed }) {
this.el = document.querySelector(el)
this._data = data
this.methods = methods
this._watch = watch
this._computed = computed
this.$initData(this._data)
this.$compile(this.el)
this.$handleWatch()
this.$handleComputed()
this.__proto__.__proto__ = this._data // 改动:把data数据放到后面的原型链中不然会覆盖nextTick方法
Object.assign(this, this.methods)
mounted && mounted.call(this)
}
$initData(data) {
observe(data) // 数据劫持
}
$compile(el) { // 模板编译
if (el) {
if (el.tagName == 'DATA') {
new Watcher(this._data, el.textContent.trim(), (newVal, oldVal) => {
el.textContent = newVal
}).update()
return
}
const attrs = el.getAttributeNames && el.getAttributeNames()
if (attrs && attrs.includes(':value')) {
new Watcher(this._data, el.getAttribute(':value'), (newVal, oldVal) => { // 在数据劫持的基础上,加入回调
el.value = newVal
}).update()
}
if (attrs && attrs.includes('@input')) {
console.log(el);
const prop = el.getAttribute('@input')
el.addEventListener('input', this.methods[prop].bind(this))
}
if (el.nodeType === 1 && el.tagName !== 'DATA') {
el.childNodes.forEach(element => {
this.$compile(element)
});
}
}
}
$handleWatch() {
if (this._watch)
Object.keys(this._watch).forEach(n => {
new Watcher(this._data, n, this._watch[n].bind(this._data))
})
}
$handleComputed() {
if (this._computed)
Object.keys(this._computed).forEach(n => {
new Watcher(this._data, n, this._computed[n].bind(this._data), {
computed: true
})
})
}
$nextTick(cb) { // 改动:增加nextTick方法
queue.nextTick(cb.bind(this))
}
}