重要代码的作用都写在代码注释中了。
核心点:
1、Object.defineProperty(),相当于给相关属性在取值和赋值时增加proxy,可以执行proxy中的功能,这里就是在get是进行双向绑定
2、Compiler的本质就是遍历html文档,把按照vue规范定的字段找出来做处理。比如{{deep.a}}取出来之后,从vm实例中拿出真实的值把{{deep.a}}替换掉。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<div>{{deep.a}}</div>
<input v-model="deep.a">
</div>
<script>
class Dep {
static target = null
static targetStack = []
static pushTarget(_target) {
if (Dep.target) {
Dep.targetStack.push(Dep.target)
}
Dep.target = _target
}
static popTarget() {
Dep.target = Dep.targetStack.pop()
}
constructor() {
this.subArr = []
}
addDepend() {
Dep.target.addDep(this) //this.addSub(watcher),此时的watcher就在target里
}
addSub(sub) {
this.subArr.push(sub)
}
notify() {
for (let sub of this.subArr) {
sub.update()
}
}
}
class Watcher {
//以expression为deep.a为例
constructor(vm, expression, cb) {
this.vm = vm
this.expression = expression
this.cb = cb
this.value = this.getVal()
}
getVal () {
Dep.pushTarget(this)
let val = this.vm
this.expression.split('.').forEach((key) => {
val = val[key]//这里会触发get方法,把这个watcher塞到Dep的sub里
})
Dep.popTarget()
return val //根据compiler里的分析这里val=1,也就是这个watcher的value=1
}
addDep(dep) {
dep.addSub(this)
}
update() {
let val = this.vm
this.expression.split('.').forEach((key) => {
val = val[key]
})
this.cb.call(this.vm, val, this.value)
}
}
class Observer {
constructor(obj) {
this.walk(obj)
}
walk(obj) {
Object.keys(obj).forEach(key=> {
if (typeof obj[key] === 'object') {
this.walk(obj[key])
}
this.defineReactive(obj, key, obj[key])
})
}
defineReactive(obj, key, value) {
let dep = new Dep()
Object.defineProperty(obj, key, {
set(newVal) {
console.log('set')
console.log(key + ': ' + value)
if (newVal === value) {
return
}
value = newVal
dep.notify()
},
get() {
if (Dep.target) {
dep.addDepend()
}
return value
},
})
}
}
class Compiler {
constructor (el, vm) {
vm.$el = document.querySelector(el)
let fragment = document.createDocumentFragment()
this.replace(vm.$el, vm)
}
replace (frag, vm) {
Array.from(frag.childNodes).forEach(node => {
let txt = node.textContent
let reg = /\{\{(.*?)\}\}/g // 正则匹配{{}}
if (node.nodeType === 3 && reg.test(txt)) { // 即是文本节点又有大括号的情况{{}}
//比如这里RegExp.$1为deep.a,reg.test(txt)去掉了大括号
let arr = RegExp.$1.split('.')//['deep', 'a']
let val = vm
arr.forEach(key => {
val = val[key]//循环两次,第一次val[deep] = {a:1, b:2},第二次val[a] = 1,最终val = 1
})
// 用trim方法去除一下首尾空格,这里{{deep.a}}就变成了1
node.textContent = txt.replace(reg, val).trim()
//传入'deep.a'做双向绑定
vm.$watch(RegExp.$1, function (newVal) {
node.textContent = txt.replace(reg, newVal).trim()
})//new Watcher(vm, 'deep.a', cb) cb就是上面第二个function参数
}
if (node.nodeType === 1) { // 元素节点
let nodeAttr = node.attributes // 获取dom上的所有属性,是个类数组
Array.from(nodeAttr).forEach(attr => {
let name = attr.name
let exp = attr.value
if (name.includes('v-')){
let val = vm
let arr = exp.split('.')
arr.forEach(key=> {
val = val[key]
})
node.value = val
// node.value = vm[exp]
}
// 监听变化
vm.$watch(exp, function(newVal) {
node.value = newVal
})
node.addEventListener('input', e => {
let newVal = e.target.value
let arr = exp.split('.')
let val = vm
arr.forEach((key, i)=> {
if (i === arr.length - 1) {
val[key] = newVal
return
}
val = val[key]
})
})
})
}
// 如果还有子节点,继续递归replace
if (node.childNodes && node.childNodes.length) {
this.replace(node, vm)
}
})
}
}
const LIFECYCLE_HOOKS = [
'created',
'mounted'
]
function callHook (vm, hook) {
const handlers = vm.$options[hook]
if (handlers) {
handlers.call(vm)
}
}
class Due {
constructor(options) {
let vm = this
vm.$options = options
vm.$watch = function (key, cb) {
new Watcher(vm, key, cb)
}
vm._data = vm.$options.data
this.observe(vm)
LIFECYCLE_HOOKS.forEach(hook => {
vm.$options[hook] = vm.$options[hook] || function () {}
})
for (let key in vm._data) {
this.proxy(vm, '_data', key)
}
callHook(vm, 'created')
new Compiler(vm.$options.el, vm)
callHook(vm, 'mounted')
}
proxy (target, sourceKey, key) {
Object.defineProperty(target, key, {
configurable: true,
get: function proxyGetter() {
return target[sourceKey][key]
},
set: function proxySetter(newVal) {
target[sourceKey][key] = newVal
}
})
}
observe(obj) {
if (!obj || typeof obj !== 'object') {
return
}
return new Observer(obj)
}
}
let app = new Due({
el: '#app',
data: {
msg: 'hello wue',
deep: {
a: 1,
b: 2
}
},
created() {
console.log('created')
},
mounted () {
console.log('mounted')
this.deep.a = 111
}
})
</script>
</body>
</html>