实现简单的MVVM架构
Opened this issue · 0 comments
junhey commented
话不多少,直接look代码
let currentWatcher = null //设置一个全局唯一的一个,data中属性的观测者
let id = 0 //每个主题有唯一的ID
class mvvm{
constructor(opts){
this.init(opts)//初始化
observer(this.$data)//把所有要观测的数据,推入observer中进行数据拦截
new Compile(this)//解析模板同时让对应的属性添加watcher
}
init(opts){
this.$el = document.querySelector(opts.el)
this.$data = opts.data || {}
this.$methods = opts.methods || {}
this.observers = []
//数据代理,让vm实例可以直接获取到data数据
for(let key in this.$data){
Object.defineProperty(this,key,{
configurable:true,
enumerable:true,
//箭头函数让this指向vm实例
get:()=>{
return this.$data[key]
},
set:(newVal)=>{
this.$data[key] = newVal
}
})
}
//让methods里的this指向vm实例,和Vue里的methods保持一致
for(let key in this.$methods){
this.$methods[key] = this.$methods[key].bind(this)//这里的this是vm实例
}
}
}
function observer(data){ //让所有的VM的$data进行数据监听
if(typeof data !== 'object' || !data ) return
let keys = Object.keys(data)
keys.forEach((key)=>{
let val = data[key] //局部作用域 保持val值
let subject = new Subject()
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){
if(currentWatcher){
console.log('has Watcher')
currentWatcher.subscribeTo(subject)
}
return val
},
set(newVal){
console.log(`data change ${val}=>${newVal}`)
val = newVal
//通知所有观测者进行更新
subject.notify()
}
})
if(typeof val === 'object'){//递归
observer(val)
}
})
}
//解析模板
class Compile{
constructor(vm){//vm为watcher实例
this.vm = vm
this.node = vm.$el
this.compile()
}
compile(){
//遍历节点
this.traverse(this.node)
}
traverse(node){
//如果为1说明有子节点
if(node.nodeType === 1 ){
//处理节点上的v-model,v-on指令
this.compileNode(node)
//继续向下遍历,childNodes是类数组nodeList,但是有forEach方法
node.childNodes.forEach(child=>{
this.traverse(child)
})//为3,说明为文本节点,解析文本
}else if(node.nodeType === 3){
this.compileText(node)
}
}
compileText(node){
//创建正则规则: 全局匹配花括号内的分组内容用非贪婪模式
let reg = /{{(.+?)}}/
let matches
//reg.exec 返回匹配到的数组,循环调用直到没有对应结果返回null
while (matches = reg.exec(node.nodeValue)) {
//reg匹配到的全部字符
let raw = matches[0]
//分组内容里匹配的字符
let key = matches[1].trim()
//将节点文本替换为实例对象属性对应的值
node.nodeValue = node.nodeValue.replace(raw,this.vm.$data[key])
//给对应的主题(data内的属性)添加watcher观测者,同时给update时添加一个回调函数更新View
new Watcher(this.vm, key, function(newVal,oldVal){
node.nodeValue = node.nodeValue.replace(oldVal,newVal)
})
}
}
//解析指令
compileNode(node){
//类数组转换为真数组
let attrs = [...node.attributes]
attrs.forEach(attr=>{
//attr为这个节点上的每个属性的键值对象,key是属性名,value是属性值
if(this.isModelDirective(attr.name)){
this.bindModel(node,attr)
}else if(this.isEventDirective(attr.name)){
this.bindEventHandle(node,attr)
}
})
}
bindModel(node,attr){
let key = attr.value
//node一般为input
node.value = this.vm[key]
//为属性添加watcher
new Watcher(this.vm,key,function(newVal){
node.value = newVal
})
//绑定事件
node.oninput = (e)=>{//箭头函数this绑定声明上下文作用域this
this.vm[key] = e.target.value
}
}
bindEventHandle(node,attr){//事件绑定
console.log(attr)
let eventType = attr.name.substr(5) //索引为5开始到结束的字符串 e.g:"v-on:click"
let methodName = attr.value
node.addEventListener(eventType,this.vm.$methods[methodName])
}
isModelDirective(attrName){//判断指令
return attrName === 'v-model'
}
isEventDirective(attrName){//判断指令
return attrName.indexOf('v-on') === 0
}
}
//发布订阅主题
class Subject {
constructor(){
this.observers= []
this.id = id++
}
addOberserver(observer){
this.observers.push(observer)
}
removeOberserver(observer){
let index = this.observers.indexOf(observer)
if(index > 0){
this.observers.splice(index,1)
}
}
notify(){
this.observers.forEach((item)=>{
item.updata()
})
}
}
//观察者
class Watcher{//每个属性的观测者.观测数据的变化然后通知View进行更新
constructor(vm,key,callback){
this.subjects={}
this.key = key
this.vm = vm//当前的ViewModel实例
this.callback = callback
this.value = this.getValue()
}
updata(){
let oldVal = this.value
let value = this.getValue() //如果触发了update事件,那么此时的get值已经被set更新到最新
//如果发生了更新那么就要通知 Vm实例的view发生变化,同时传递参数,调用replace替换新值
if(oldVal !== value){
this.value = value
this.callback.bind(this.vm)(value,oldVal)
}
}
subscribeTo(subject){
//当观测者的还没有订阅相对应的主题(data中的属性)时就添加一个,否则不执行逻辑
if(!this.subjects[subject.id]){
console.log(`has subscribeto${subject}`)
subject.addOberserver(this)
//让观测者有了订阅主题
this.subjects[subject.id] = subject
}
}
getValue(){
//让全局唯一的观测者变成当前的实例对象
currentWatcher = this
//触发Observer的get函数,让把当前对象(watcher观测者)添加到主题(data的属性就是一个主题)中
let value = this.vm.$data[this.key]
//添加进主题后把全局唯一观测者变回null
currentWatcher = null
return value
}
}