Vue更新视图的思想
Vue的响应式的核心是defineProperty,通过defineProperty来设置响应式的变量,当变量的值改变时就触发对应的setter方法,从而调用视图更新的方法,更新视图。那么Vue中究竟如何渲染视图的呢?
Vue的思想:
- 实例化vue时初始化数据,将data中的变量设置成响应式;
- 渲染模版初始化页面时,进行依赖收集,也就是会将有读取data中的变量的dom对象存在Watcher对象中;
- 修改数据,触发setter,触发对应的watch对象,调用渲染函数,重新渲染被收集的dom,更新视图。
模拟vue实现视图更新
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="app">
<div>
{{message}}
</div>
</div>
</body>
<script>
document.addEventListener('DOMContentLoaded', function () {
let app = {
el: '#app',
data: {
message: '页面加载于 ' + new Date().toLocaleString()
}
}
let vm = new miniVue(app)
setTimeout(() => {
app.data.message = '加载完成!!'
}, 2000);
})
class miniVue {
constructor(opt) {
this.opt = opt
this.observe(opt.data)
let root = document.querySelector(opt.el)
this.compile(root)
console.log(this)
}
// 为响应式对象 data 里的每一个 key 绑定一个观察者对象
observe(data) {
Object.keys(data).forEach(key => {
let obv = new Observer()
data["_" + key] = data[key]
// 通过 getter setter 暴露 for 循环中作用域下的 obv,闭包产生
Object.defineProperty(data, key, {
get() {
Observer.target && obv.addSubNode(Observer.target);
return data['_' + key]
},
set(newVal) {
obv.update(newVal)
data['_' + key] = newVal
}
})
})
}
// 初始化页面,遍历 DOM,收集每一个key变化时,随之调整的位置,以观察者方法存放起来
compile(node) {
[].forEach.call(node.childNodes, child => {
if (!child.firstElementChild && /\{\{(.*)\}\}/.test(child.innerHTML)) {
let key = RegExp.$1.trim()
child.innerHTML = child.innerHTML.replace(new RegExp('\\{\\{\\s*' + key + '\\s*\\}\\}', 'gm'), this.opt
.data[key])
Observer.target = child
this.opt.data[key]
Observer.target = null
} else if (child.firstElementChild)
this.compile(child)
})
}
}
// 常规观察者类
class Observer {
constructor() {
this.subNode = []
}
addSubNode(node) {
this.subNode.push(node)
}
update(newVal) {
this.subNode.forEach(node => {
node.innerHTML = newVal
})
}
}
</script>
</html>
注意以上demo:
巧妙的使用了闭包
在obsever方法中的Object.keys(data).forEach(key => {})中使用到了闭包,data中的每个变量对应的都有一个new Observer()观察者对象,这个对象一直被setter方法引用着。(可以试着把遍历data属性的方法改成for循环遍历,这时候就没有了闭包,new Observer()不会被保存在setter方法中,数据改变时触发setter方法读取不到new Observer()对象,最终无法更新视图)
闭包的判断依据:函数内部的函数一直对该函数的作用域保持着引用,这个引用就是闭包。闭包的好处就是当函数执行完,作用域不会被释放,还可以读到其内部的数据。
观察者模式解耦代码技巧
编译渲染的时候,在Observe类上定义了属性target,作为全部变量用来标记依赖,同时在渲染方法中又读取了一次变量,用来触发obsever方法中的变量对应的getter方法来存放依赖。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。