Vue中的数据响应化
目标代码
kvue.html
<div id="app">
<p>{{counter}}</p>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
counter: 1
},
})
setInterval(() => {
app.counter++
}, 1000)
</script>
原理分析
1、new Vue()首先执行初始化,对data执行响应化处理,这个过程发生在Observer中
2、同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compile中
3、同时定义一个更新函数和watcher,将来对应数据变化时watcher会调用更新函数
4、由于data的某个key在一个视图中可能出现多次,所以每个key都需要一个管家Dep来管理多个watcher
5、将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有watcher执行更新函数
涉及类型介绍
- KVue: 框架构造函数
- Observer: 执行数据响应化(分辨数据是对象还是数组)
- Compile: 编译模板,初始化视图,收集以来(更新函数、watcher创建)
- Watcher: 执行更新函数(更新dom)
- Dep: 管理多个Watcher,批量更新
KVue
框架构造函数:执行初始化
- 执行初始化,对data执行响应化处理,kvue.js
function observer(obj) {
if(typeof obj !== 'object' || obj == null) {
return
}
new Observer(obj)
}
function defineReactive(obj, key, val) {}
class KVue {
constructor(options) {
this.$options = options;
this.$data = options.data
observe(this.$data)
}
}
class Observer {
constructor(value) {
this.value = value
this.walk(value)
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
- 为$data做代理
class KVue {
constructor(options) {
// ...
proxy(this, '$data')
}
}
function proxy(vm) {
Object.keys(vm.$data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key]
},
set(newVal) {
vm.$data[key] = newVal
}
})
})
}
编译-Compile
编译模板中vue模板特殊语法,初始化视图,更新视图
初始化视图
根据节点类型编译,compile.js
class Compile {
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el);
if(this.$el) {
this.compile(this.$el)
}
}
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if(this.isElement(node)) {
console.log('编译元素' + node.nodeName)
} else if(this.isInterpolation(node)) {
console.log('编译插值文本' + node.textContent)
}
if(node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
isElement(node) {
return node.nodeType == 1
}
isInterpolation(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.text(node.textContent)
}
}
编译插值,compile.js
compile(el) {
// ...
} else if(this.isInterpolation(node)) {
// console.log("编译插值文本" + node.textContent);
this.compileText(node)
}
}
compileText(node) {
console.log(RegExp.$1)
node.textContent = this.$vm[RegExp.$1]
}
编译元素
compile(el) {
// ...
if(this.isElement(node)) {
// console.log('编译元素' + node.nodeName)
this.compileElement(node)
}
}
compileElement(node) {
let nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr => {
let attrName = attr.name;
let exp = attr.value;
if(this.isDirective(attrName)) {
let dir = attrName.substring(2)
this[dir] && this[dir](node, exp)
}
})
}
isDirective(attr) {
return attr.indexOf("k-") == 0;
}
text(node, exp) {
node.textContent = this.$vm[exp]
}
k-html
html(node, exp) {
node.innerHTML = this.$vm[exp]
}
依赖收集
视图中会用到data中某key,这称为依赖,同一个key可能出现多次,每次都需要收集出来用一个Watcher来维护他们,此过程称为依赖收集。
多个watcher需要一个Dep来管理,需要更新时由Dep统一通知。
看下面案例,理出思路:
new Vue({
template:
`<div>
<p>{{name1}}</p>
<p>{{name2}}</p>
<p>{{name1}}</p>
</div>`,
data: {
name1: 'name1',
name2: 'name2'
}
})
实现思路
1、defineReactive时为每一个key创建一个Dep实例
2、初始化视图时读取某个key,例如name1,创建一个watcher1
3、由于触发name1的getter方法,便将watcher1添加到name1对应的Dep中
4、当name1更新,setter触发时,便可通过对应Dep通知其管理所有watcher更新
创建Watcher,kvue.js
const watchers = [] // 临时用于保存watcher测试用
// 监听器:负责更新视图
class Watcher {
constructor(vm, key, updateFn) {
// kvue实例
this.vm = vm;
// 依赖key
this.key = key
// 更新函数
this.updateFn = updateFn
// 临时放入watchers数组
watchers.push(this)
}
// 更新
update() {
this.updateFn.call(this.vm, this.vm[this.key])
}
}
编写更新函数、创建watcher
// 调用update函数给插值文本赋值
compileText(node) {
// console.log(RegExp.$1)
// node.textContent = this.$vm[RegExp.$1]
this.update(node, RegExp.$1, 'text')
}
text(node, exp) {
this.update(node, exp, 'text')
}
html(node, exp) {
this.update(node, exp, 'html')
}
update(node, exp, dir) {
const fn = this[dir + 'updater']
fn && fn(node, this.$vm[exp])
new Watcher(this.$vm, exp, function(val) {
fn && fn(node, val)
})
}
textUpdater(node, val) {
node.textContent = val;
}
htmlUpdater(node, val) {
node.innerHTML = val
}
声明Dep
class Dep {
constructor() {
this.deps = []
}
addDep(dep) {
this.deps.push(dep)
}
notify() {
this.deps.forEach(dep => dep.update())
}
}
创建watcher时触发getter
class Watcher {
constructor(vm, key, updateFn) {
Dep.target = this;
this.vm[this.key]
Dep.target = null
}
}
依赖收集,创建Dep实例
defineReactive(obj, key, val) {
this.observe(val);
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
if(newVal === val) return
dep.notify()
}
})
}