vue响应式
简单实现
my-vue/reactive.js
const obj = {}
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log('get', val)
return val
},
set(newVal) {
val = newVal
console.log('set', val)
}
})
}
defineReactive(obj, 'foo', 'foo')
obj.foo
obj.foo = 'fooooooooo'
综合视图实现
监听数据后,数据一更新,在set属性中通知视图更新 (my-vue/ractive.html)
<div id="app"></div>
<script>
const obj = {}
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
// console.log('get', val)
return val
},
set(newVal) {
if (newVal != val) {
val = newVal
// 通知视图更新
update()
}
}
})
}
defineReactive(obj, 'foo', '')
obj.foo = new Date().toLocaleTimeString()
function update() {
app.innerText = obj.foo
}
setInterval(() => {
obj.foo = new Date().toLocaleTimeString()
}, 1000)
</script>
对象嵌套不能监听数据
递归处理
// 数组处理
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice']
methods.forEach(method => {
arrayProto[method] = function() {
originalProto[method].apply(this, arguments)
console.log('执行了:' + method + '操作')
}
})
function defineReactive(obj, key, val) {
observe(val)
}
function observe(obj) {
// 递归判断
if(typeof obj != 'object' || obj == null) {
return
}
if (Array.isArray(obj)) {
obj.__proto__ = arrayProto
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
observe(obj[i])
}
} else {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
const obj = {foo: 'foo',bar: {b: {c: 2}},baz:{a: 1}, arr: [1, 2, 3]}
observe(obj)
obj.baz.a = 2
obj.bar.b.c = 3
obj.arr.push(4) // push操作
obj.arr // [1, 2, 3, 4]
往对象中添加新数据,无法监听该数据
重新监听对象新增的数据
function set(obj, key, val) {
defineReactive(obj, key, val)
}
set(obj, 'k', 3)
obj.k
测试用例
<meta charset="UTF-8">
<div id="app">
<p @click="add">{{counter}}</p>
<p my-html="desc"></p>
<input type="text" my-model="desc">
</div>
<script src="./my-vue.js"></script>
<script>
const app = new myVue({
el: '#app',
data: {
counter: 1,
desc: '<span>222</span>'
},
methods: {
add() {
this.counter++
}
}
})
</script>
框架构造:执行初始化
- 执⾏初始化,对data执⾏响应化处理
function observe(obj) {
if (typeof obj !== 'object' || obj == null) return
new Observer(obj)
}
function defineReactive(obj, key, val) {
}
class myVue {
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[counter] ——> this.counter
function proxy(vm) {
Object.keys(vm.$data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key]
},
set(newVal) {
vm.$data[key] = newVal
}
})
})
}
class myVue {
constructor(options) {
// 为$data做代理
proxy(this)
}
}
编译 - Compile
编译模板中vue模板特殊语法,初始化视图、更新视图,v-html、v-text、@event...
- 根据节点类型编译
class myVue {
constructor(options) {
// 编译
new Compile(options.el, this)
}
}
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.nodeType)
this.compileElement(node)
} else if (this.isInterpolation(node)) {
// console.log('编译插值文本' + node.nodeType)
this.compileText(node)
}
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
isElement(node) {
return node.nodeType == 1
}
isInterpolation(node) {
return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
compileText(node) {
// node.textContent = this.$vm[RegExp.$1]
this.update(node, RegExp.$1, 'text')
}
compileElement(node) {
let nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach(attr => {
// my-text="xxx"
// name = my-text,value = xxx
let attrName = attr.name
let exp = attr.value
if (this.isDirective(attrName)) {
let dir = attrName.substring(3)
this[dir] && this[dir](node, exp)
}
// 事件@
if (this.isEvent(attrName) {
let dir = attrName.sunstring(1)
this.eventHandler(node, exp, dir)
}
})
}
isDirective(attr) {
return attr.indexOf('my-') == 0
}
isEvent(attr) {
return attr.startsWith('@')
}
eventHandler(node, exp, dir) {
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp]
node.addEventListener(dir, fn.bind(this.$vm))
}
text(node, exp) {
// node.textContent = this.$vm[exp]
this.update(node, exp, 'text')
}
html(node, exp) {
// node.innerHTML = this.$vm[exp]
this.update(node, exp, 'html')
}
model(node, exp) {
this.update(node, exp, 'model')
// 事件监听
node.addEventListener('input', event => {
this.$vm[exp] = event.target.value
})
}
textUpdater(node, val) {
node.textContent = val
}
htmlUpdater(node, val) {
node.innerHTML = val
}
modelUpdater(node, val) {
node.value = val
}
update(node, exp, dir) {
// 1.init
const fn = this[dir + 'Updater']
fn && fn(node, this.$vm[exp])
// 2.update
new Watcher(this.$vm, exp, val => {
fn && fn(node, val)
})
}
}
依赖收集
- 创建Watcher
const watchers = [] // 临时保存watcher
// 监听器:负责更新视图
class Watcher {
constructor(vm, key, updateFn) {
// myVue实例
this.vm = vm
// 依赖key值
this.key = key
// 更新函数
this.updateFn = updateFn
// 临时放入watcher数组
watchers.push(this)
}
// 更新
update(){
this.updateFn.call(this.vm, this.vm[this.key])
}
}
- 声明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
}
// 更新
update() {
this.updateFn.call(this.vm, this.vm[this.key])
}
}
- 依赖收集,创建Dep实例
function defineReactive(obj, key, val) {
observe(val)
// val值的唯一性,所以一个dep对应多个watcher
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addDep(Dep.target)
return val
},
set(newVal) {
if (newVal == val) return
val = newVal
observe(newVal)
dep.notify()
}
})
}
总结
- new Vue()首先执行初始化,对data执行响应式处理,这个过程发生在Observer中
- 同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compiler中
- 同时定义一个更新函数和Watcher,将来对应数据变化时Watcher会调用更新函数
- 由于data的某个key值在一个视图中可能多次出现所以每个key都需要一个管家Dep来管理多个Watcher
- 将来data中数据一旦发生变化,会首先找到对应的Dep,通知所有Watcher执行更新函数