mvvm框架最火的是react和vue,鉴于工作接触最多的是vue,所以,手动实现了一个简单版vue类,可以双向绑定数据,做到数据劫持,观察和更新数据
建个src,放个index.html
,js/vue.js
。
let vm = new Vue({
el:'#app',
data:{
msg:'hi'
},
method:{
clickFn(){
console.log('click')
}
}
})
首先,vue是个类,需要传一个对象。
class Vue{
constructor(opt={}){
this.$el = opt.el
this.$data = opt.data
this.$methods = opt.methods
}
}
// 在控制台打印下vm,哎嗨,有那么点意思了啊
vue是在el的范围内解析的。将其中的相应的指令解析成正确的内容。 这里的大逻辑是,将el所有子节点移到fragment,framment解析完成之后,重新添加到el。这样做是减少重绘。
// 不想多次重绘,所以用fragment媒介
let fragment = document.createDocumentFragment()
// 首先把el的节点都移到fragment下,解析完成之后,再放回来。
let childNodes = this.el.childNodes
this.toArray(childNodes).forEach(node => {
fragment.appendChild(node)
})
// 编译
this.compile(fragment)
// 编译完成塞回来
this.el.appendChild(fragment)
<div id="app">
<p>{{msg}}</p>
<p v-text="msg" >{{msg}}</p>
<p v-html="msg"></p>
<input type="text" v-model="msg">
<button v-on:click="clickFn">点我</button>
</div>
遇到标签解析指令,遇到文本解析{{}}
。
听着高大上,也就是跟踪属性,读取属性或设置属性的时候,都知道并且可以控制返回值。是这货,Object.defineProperty
把data的每个属性都getter和setter。每个自然就是遍历。其次是考虑到属性值也是对象的情况,递归。
观察者模式啊。首先,getter用于收集订阅者(也就是compile那边的watcher),setter用于触发动作。
定义一个dep依赖收集的类。
watcher主要是,值变化的时候,执行回调函数来重新编译。每个{{}}
(指令一样啦,这里的只是一个代表)都是一个watcher。compile那边编译的时候就新建watcher。
dep的作用是,每个值有很多的{{}}
,每个这样的都需要在,值变化的时候,执行回调函数来重新编译。所有收集所有的watcher,值变化的时候一起通知。
总结:每个属性,有很多{{}}
,每个{{}}
就是一个watcher,而每个属性就是一个dep。值变化,就会触发所有订阅者的动作,也就是watcher的渲染动作。每新建一个watcher的时候,就记得添加到相应的Dep里。每一个compile的时候就新建一个watcher.
这里好难理解。没事多琢磨。说自己的。
vm.$data[expr] => vm[expr]