一、作为一个MVVM框架,应该实现什么:
数据响应式:监听数据变化并能及时在视图中更新(defineProperty,Proxy)
模板引擎:提供描述视图的模板语法(插值,指令)
渲染:将模板转化为html(vdom => dom)
二、定义响应式拦截
// 拦截定义
// 处理新增对象属性
function defineReactive (obj, key, val) {
// 处理初始化时的深层对象
observe(val)
Object.defineProperty(obj, key, {
get() {
console.log('get: ', key)
return val
},
set(newval) {
if(newval === val) return
// 处理深层对象的重新赋值
observe(val)
val = newval
console.log('set: ', key, newval)
}
})
}
function observe(obj) {
if(typeof obj !== "object" || obj === null) return obj
Object.keys(obj).forEach(k => defineReactive(obj, k, obj[k]))
}
let obj = { foo: { name: { deep: 'foo' } } }
observe(obj)
console.log('val: ', obj.foo.name.deep )
obj.foo.name.deep = 'foooooooooo'
console.log('val: ', obj.foo.name.deep )
三、简单实现一下
function defineReactive (obj, key, val) {
// 处理初始化时的深层对象 let a = { obj: {...} }
observe(val)
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
console.log('get', key, val)
if(Dep.target) dep.add(Dep.target)
return val
},
set(newval) {
if(newval === val) return
console.log('set', key, newval)
// 处理深层对象的重新赋值 a.obj = {...}
observe(val)
val = newval
dep.notify()
}
})
}
function observe(obj) {
if(typeof obj !== "object" || obj === null) return obj
new Oberver(obj)
}
function proxy(vm, key) {
Object.keys(vm[key]).forEach(datakey => {
Object.defineProperty(vm, datakey, {
get() {
return vm[key][datakey]
},
set(newval) {
vm[key][datakey] = newval
}
})
})
}
class Oberver{
constructor(obj) {
this.value = obj
// 拦截
this.walk(this.value)
}
walk(obj) {
Object.keys(obj).forEach(k => defineReactive(obj, k, obj[k]))
}
}
class vVue{
constructor(options) {
this.$options = options
this.$data = options.data
// $data 响应式
observe(this.$data)
// $data的值代理到新对象
proxy(this, '$data')
// 编译模板
new Compile(options.el, this)
}
}
class Compile{
constructor(el, vm) {
this.$el = document.querySelector(el)
this.$vm = vm
if(this.$el) {
this.compile(this.$el)
}
}
compile(el) {
// 判断元素类型
el.childNodes.forEach(node => {
if(node.nodeType === 1) {
// 节点元素
this.compileElement(node)
console.log('节点元素', node.nodeName)
} else if(this.isInner(node)) {
this.compileText(node)
console.log('模板元素', node.textContent, RegExp.$1.trim())
}
if(node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
isInner(node) {
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
compileText(node) {
this.update(node, RegExp.$1.trim(), 'text')
}
compileElement(node) {
const attrs = node.attributes
Array.from(attrs).forEach(attr => { // k-text = "count"
const attrName = attr.name // k-text
const attrValue = attr.value // count
if(this.isDirective(attrName)) {
const direct = attrName.substr(3)
this[direct] && this[direct](node, attrValue)
}
})
}
isDirective(name) {
return name.startsWith('vv-')
}
// 实现vv-text
text(node, exp) {
this.update(node, exp, 'text')
}
textUpdate(node, val) {
node.textContent = val
}
update(node, exp, dir) {
const fn = this[dir + 'Update']
fn && fn(node, this.$vm[exp])
new Watcher(this.$vm, exp, val => {
fn && fn(node, val)
})
}
}
class Watcher{
constructor(vm, exp, updatefn) {
this.vm = vm
this.exp = exp
this.updatefn = updatefn
Dep.target = this
this.vm[exp]
Dep.target = null
}
update() {
this.updatefn.call(this.vm, this.vm[this.exp])
}
}
class Dep {
constructor() {
this.watchers = []
}
add(watcher) {
this.watchers.push(watcher)
}
notify() {
this.watchers.forEach(w => w.update())
}
}
这里是流程解析:
new vVue() -> observe -> 为data里的数据做响应式 -> 每一个属性defineReactive的时候,会利用闭包保存一个Dep -> 定义get, set -> 把data里的数据映射到vVue实例上(proxy更优雅) -> 编译app节点 -> Compile(感觉改成函数更好理解一点)-> 判断节点类型 -> 递归处理直到遇到指令和文本节点(这里当一种情况了)-> 执行指令对应的方法 -> 执行update(初始化页面) -> 生成watcher,用来保存对应属性和对应的更新函数 -> 将自身保存到全局变量(Dep.target)里去,并主动调用对应属性的get方法 -> 在get方法中将Dep 和watcher 建立关系(其实就是dep 实例保存watcher) -> 下次data属性值更新会调用set方法,将dep中保存的watcher执行watcher里保存的更新方法