概括一下 mvvm 就是
发布(watcher)-订阅 (dep)+ defineProperty
通过 Observer类监听data中的数据,compile类的话解析html中的数据格式(input/{{}}),
dep起一个数组作用,收集订阅者,订阅者就是所谓的watcher负责订阅数据变化并且绑定更新数据
同时,如果页面中的数据发生改变,通过defineProperty数据劫持的set方法,通过dep订阅器调用watcher中的update方法实现数据更新
首先,我们来给定一下数据
// new Wue实例
let app = new Wue ({
el: '#app',
data: {
song: '发如雪',
album: {
name: '十一月的肖邦',
theme: '夜曲'
},
singer: '周杰伦'
}
})
<div id="app">
<h1>{{song}}</h1>
<p>《{{album.name}}》是{{singer}}2005年11月发行的专辑</p>
<p>主打歌为{{album.theme}}</p>
<p>作词人为{{singer}}等人。</p>
为你弹奏肖邦的{{album.theme}}
<input v-model="input" type="text">
</div>
我们先定义一下Wue类
wue类 就是用来生成wue实例哒
class Wue {
constructor (options) {
const vm = this
vm.$options = options
vm.$watcher = function(value, cb) {
new Watcher(vm, value, cb)
}
let data = vm._data = vm.$options.data
for (let key in vm._data) {
proxy(vm, '_data', key)
}
//监听数据
observer(vm._data)
new Compile(options.el, this)
}
}
如上图,就是挂载后的结果 vm即vue实例
Compile类
在定义compile解析类时,要先在Wue类中引入compile鸭
class Wue {
...
new Compile(options.el, this)
}
class Compile {
constructor (el, vm) {
// document.querySelector("#app")
vm.$el = document.querySelector(el) //这一步就是获取id为app的div
this.replace(vm.$el, vm)
}
replace(frag, vm) {
Array.from(frag.childNodes).forEach(node => {
let txt = node.textContent
let reg = /\{\{(.*?)\}\}/g
// 如果是元素属性中的文本内容 如<h1></h1> <p></p>
if (node.nodeType === 3 && reg.test(txt) {
let arr = RegExp.$1.split('.')
let val = vm
arr.forEach (key => {
//这里进行赋值 要对data进行代理处理
val = val[key] //如key为song val = Wue[song] = '发如雪'
})
//这一步实现了讲data的值显示在页面上
node.textContent = txt.replace(reg, val).trim();1
//使用watcher监听data中的属性
vm.$watch(RegExp.$1, function(newVal) {
txt.textContent = txt.replace(reg, newVal).trim()
})
}
// 当节点是元素时
if (node.nodeType === 1) {
let nodeAttr = node.attributes //获取dom上的所有属性,是个类数组
Array.from(nodeAttr).forEach(attr => {
// 如v-model type
let name = attr.name
// 如song text
let exp = attr.value
// 实现一下v-model
if (name.includes('v-')) {
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) => {
// 比如 如果是album.theme 咱们要取的肯定是最后theme嘛
if (i === arr.length - 1) {
val[key] = newVal
return
}
val = val[key]
})
})
})
}
// 如果还有子节点,就循环递归replace
if (node.childNodes && node.childNodes.length) {
this.replace(node, vm)
}
})
}
}
当 时,其dom上的所有属性如下
vm.$el.childNodes为
比如第一个 txt为{{song}} 则 arr为[song]
这里存在一个bug
一个txt中有两个{{}}待解析的话 会忽略另外一个
以上就实现了让data显示在页面上
对data进行代理 proxy
_data: {
song: '发如雪',
album: {
name: '十一月的肖邦',
theme: '夜曲'
},
singer: '周杰伦'
}
// key: song/ album/ theme
for (let key in vm._data) {
proxy(vm, '_data', key)
}
function proxy (target, sourceKey, key) {
Object.defineProperty(target, key, {
configurable: true,
get: function proxyGetter () {
//vm[_data][song]
return target[sourceKey][key]
},
set: function proxySetter (newVal) {
// vm[_data][song]
target[sourceKey][key] = newVal
}
})
}
我们把data的值显示到了页面上,如果data的值被人为改动了呢?会反应到页面上吗?**
当然是不行的拉!!!所以我们首先需要保证我们监听到了这个data中的属性。
那用什么监听呢?
我们需要定义一个watcher类,来订阅数据的变化
watcher类
class Watcher {
constructor (vm, expression, cb) {
this.vm = this.vm
this.cb = cb
this.expression = expression
this.value = this.getVal()
}
getVal () {
pushTarget(this)
let val = this.vm
this.expression.split('.').forEach((key) => {
// 获取val[key]值时,就会默认调用Observer下的get方法
val = val[key]
})
popTarget()
return val
}
addDep (dep) {
// 这里的话就要去把watcher添加进去 因为要把watcher添加, 所以要在watcher类下添加 这里的this指向watcher
dep.addSubs(this)
}
update () {
// 因为要改变的数据还是存在在vm下的,所以也要获取
let val = this.vm
this.expression.split('.').forEach((key) => {
val = val[key]
})
this.cb.call(this.vm, val).trim()
}
}
pushTarget 作用是 当前订阅者读取被订阅数据的最新更新后的值时,通知订阅者管理员收集当前订阅者
Dep.target = null
let targetStack = []
function pushTarget (_target) {
//Dep.target其实就是一个watcher对象
if (Dep.target) {
targetStack.push(Dep.target)
}
Dep.target = _target
}
function popTarget () {
Dep.target = targetStack.pop()
}
Observer
class Observer {
constructor (value) {
this.value = value
this.walk(value)
}
walk (obj) {
Object.keys(obj).forEach((key) => {
if (typeof obj[key] === 'object') {
this.walk(obj[key])
}
})
defineReactive(obj, key, obj[key])
}
defineReactive(obj, key, value) {
let dep = new Dep()
get () {
if (Dep.target) {
// 这里,因为你需要去获取wue下的data数据,那么就要把这个wather添加到dep消息订阅器中
dep.addDepend()
}
return value
}
set (newVal) {
if (newVal === value) {
return
}
value = newVal
// 调用更新数据方法
dep.notify()
}
}
}
Dep
class Dep {
constructor{
this.subs = [] //存放watcher订阅者
}
addDepend () {
// 调用watcher下的addDep方法
Dep.target.addDep(this) // this是dep实例本身
}
addSubs (sub) {
this.subs.push(sub)
}
notify () {
for(let sub of subs) {
sub.update()
}
}
}