vue源码实现
- 模板解析
- 拿到模板内每一个元素,放入内存中(AST抽象语法树)
- 解析(深度遍历)每一个元素,及其属性
- 抽离数据渲染部分准备复用
- 双向绑定
- 数据劫持
- 创建被观察者(beauty)和观察者
- 在模板解析时,每次cmdHandler时为节点和属性创建观察者
- 在数据劫持时,为每个对象创建被观察者(beauty),在cmdHandler访问数据get时(观察者刚刚创建),为beauty添加观察者,在set时改变beauty状态
- computed 和 methods,主要注意this指向问题
2.2 被观察者和观察者比作老师和家长更合适。老师(被观察者)要记录所有家长(观察者),并在学校有事的时候主动通知他们。
// 被观察者,存储每个数据的老值,发生变化后notify所有观察者
class Beauty {
constructor (state, name) {
this.watchers = []
this.state = state
this.name = name
}
setState(newval) {
if (this.state == newval) return;
this.state = newval
this.notify()
}
addWatcher (observer) {
this.watchers.push(observer)
}
notify () {
// console.log('00000。', this.watchers)
this.watchers.forEach(o => {
o.update(this.state)
})
}
}
// 利用js单线程,存刚刚创建的watcher,给beauty用
let temp_target = null
// 观察者,存储每个节点上的第个命令,接到Beauty通知后,update node的上的属性(或文本)
class Watcher {
constructor (vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
// 创建watcher,马上访问数据,触发数据劫持的get
temp_target = this
this.oldval = vm._getdata(expr)
temp_target = null
}
update (newval) {
if (this.oldval != newval) this.cb(newval)
}
}
// 数据劫持
class DefineProperty {
constructor (data) {
this.observer(data)
}
observer (data) {
if (data && typeof data == 'object') {
for (let key in data) {
this.defineReactive(data, key, data[key])
}
}
}
defineReactive (data, key, value) {
this.observer(value)
let beauty = new Beauty(value, key) // 为每个数据创建被观察者
Object.defineProperty(data, key, {
get () {
// 有 temp_target 为刚刚创建的观察者,观察的就是当前数据
// 为beauty添加 刚刚创建的watcher
temp_target && beauty.addWatcher(temp_target)
return value
},
set: (newval) => {
if (value == newval) return;
this.observer(newval) // 防止普通值变为对象
value = newval
beauty.setState(newval) // 变更beauty状态
}
})
}
}
class Vue {
constructor (options) {
window.vm = this
// 数据劫持
this.data = options.data()
new DefineProperty(this.data)
// 拿到模板内每一个元素,放入内存中
this.$el = document.querySelector(options.el)
this.fragment = document.createDocumentFragment()
while (this.$el.firstChild) {
this.fragment.append(this.$el.firstChild)
}
// 渲染节点
this.fragment = this.render(this.fragment)
this.$el.append(this.fragment)
// this.methods = options.methods
}
render (fragment) {
fragment.childNodes.forEach(item => {
if (item.nodeType == 3) { // 文本节点
cmdHandler['text'](item, this)
} else if (item.nodeType != 8) { // 8是注释
[...item.attributes].forEach(attr => {
if (!attr.name.startsWith('v-')) return false;
let [cmd] = attr.name.substr(2).split(':')
cmdHandler[cmd] && cmdHandler[cmd](item, this, attr)
})
}
item.hasChildNodes() && this.render(item)
})
return fragment
}
_getdata (keystr) {
return keystr.split('.').reduce((data, key) => data[key], this.data)
}
}
let cmdHandler = {
model (node, vm, attr) {
new Watcher(vm, attr.value, newval => {
this.update.model(node, newval)
})
this.update.model(node, vm._getdata(attr.value), 'value')
node.removeAttribute(attr.name)
},
bind (node, vm, attr) {
let [, attr_truename] = attr.name.substr(2).split(':')
new Watcher(vm, attr.value, newval => {
this.update.bind(node, newval, attr_truename)
})
this.update.bind(node, vm._getdata(attr.value), attr_truename)
node.removeAttribute(attr.name)
},
text (node, vm) {
let expr = node.textContent
let newtext = this._getContext(vm, expr, item_expr => {
new Watcher(vm, item_expr, newval => {
this.update.text(node, this._getContext(vm, expr)) // 重新算一遍
})
})
this.update.text(node, newtext)
},
_getContext (vm, expr, cb) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
cb && cb(args[1])
return vm._getdata(args[1])
})
},
click () {
},
update: {
model (node, value) {
node.setAttribute('value', value)
},
bind (node, value, key) {
node.setAttribute(key, value)
},
text (node, value) {
node.textContent = value
}
}
}
vm.data.obj.age = 44后,页面上的节点对应也变成了44
下面完成v-model
class Vue {
...
_setvalue (keystr, val) {
let arr = keystr.split('.')
arr.reduce((data, key, i) => {
if (arr.length - 1 == i)
data[key] = val
return data[key]
}, this.data)
}
}
let cmdHandler = {
model (node, vm, attr) {
...
node.addEventListener('input', e => { vm._setvalue(attr.value, e.target.value) })
},
网友评论