vue响应式原理(mvvm)手写

// 观察者 (发布订阅) 将被观察者放到观察者中
class Dep {
constructor() {
this.subs = []
}
//订阅
addSub(watcher) {
this.subs.push(watcher)
}
//发布
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
//将 有v-开始的指令的dom节点,与 {{}} 语法的文本节点,加入观察者中
class Watcher {
//当我们数据一变,就执行对应表达式中的callback
constructor(vm, expr, callback) {
this.vm = vm
this.expr = expr
this.callback = callback
//默认先存放一个老值,互相比对,有变化再改
this.oldValue = this.get()
}
//根据表达式和实例,获取对应的值
get() {
//每当new watch 时,就把自己挂载在dep的target属性中
Dep.target = this
let value = CompileUtil.getVal(this.vm, this.expr) //然后去取值,取值旧调用observe的get方法
Dep.target = null
return value
}
//更新操作,数据变化后,会调用观察者中的update方法
update() {
//再拿一次newValue,判断新旧值是否相等,再决定是否更新
let newValue = CompileUtil.getVal(this.vm, this.expr)
if (this.oldValue !== newValue) {
this.callback(newValue)
}
}
}
// vm.$watch(vm,‘name’,(newValue)=>{

// })

//实现数据劫持
class Observe {
constructor(data) {
this.observe(data)
}
observe(data) {
//先判断是不是对象,是再劫持
if (data && data instanceof Object) {
for (let key in data) {
this.defineReactive(data, key, data[key])
}
}
}
defineReactive(obj, key, value) {
//这里的value有可能还是对象
this.observe(value)
let dep = new Dep() //给每一个属性都添加dep ,也就是具有发布订阅的能力
Object.defineProperty(obj, key, {
get() {
//dep.target 就是 watcher实例
Dep.target && dep.addSub(Dep.target)
return value
},
//这里需要箭头函数,this直接指向外面的this,普通函数的话,就会指向一个拥有get,set的对象,拿不到observe函数
set: (newValue) => {
//这里传入的新的值也有可能还是对象
if (value != newValue) {
this.observe(newValue)
value = newValue
//只要一改值,就通知Dep中的watcher进行更新
dep.notify()
}
}
})
}
}

class Compiler {
constructor(el, vm) {
//判断 el是不是 元素节点 ,因为 el既可以 ‘#app’ 也可以传 document.querySelector()
this.el = this.isElementNode(el) ? el : document.querySelector(el)
this.vm = vm
//将当前节点中的元素放在内存中,不然,每次重复修改dom元素,会频繁引起重绘和回流,性能不好
//也就是比对虚拟节点,不直接操作dom元素
/** 1.将当前节点中的元素放在内存中 /
let fragment = this.node2fragment(this.el)
/
* 2.把节点中的内容进行替换 /
/
* 2.1 编译模板,用数据进行编译 /
this.compile(fragment)
/
* 3.再把内容塞回到页面中 */
this.el.appendChild(fragment)
}
//判断dom属性是否以 v- 开头 ,也就是判断是不是指令
isDirective(attr) {
//startsWith判断字符串是否以 () 开头
return attr.startsWith(‘v-’)
}
//编译元素的
compileElement(node) {
//编译元素,就要看该元素有没有 v- 属性开头的
let attributes = node.attributes; //这样拿到该元素的属性,也是类数组
[…attributes].forEach(attr => {
//这里的attr就是对应的个个属性,比如 {class=‘att’} {v-model = ‘name’}
// console.log(attr instanceof Object); //true
let {
name,
value
} = attr
if (this.isDirective(name)) { // v-model v-html 等等
let [, directive] = name.split(’-’)
let [directiveName,eventName] = directive.split(’:’) //v-on:click
//调用compileUtil中不同的指令来进行处理
//我们需要怎么处理呢?即我们需要传入哪些参数
// 首先是需要编译的元素节点 node ,还有就是后面对应的表达式,也就是value,还有就是数据我们要从当前实例上拿,即 vm
CompileUtil[directiveName](node, value, this.vm,eventName)
}
})
}
//编译文本的 判断是否有 {{}} 语法
compileText(node) {
let content = node.textContent;
if (/{{(.+?)}}/.test(content)) {
//如果匹配到了,表明就是我们想要的内容
CompileUtil[‘text’](node, content, this.vm)
}
}
//用来将数据编译内存中的对应的dom节点
compile(node) {
//拿到node中的子节点
let childNodes = node.childNodes;
//此时的childNodes是类数组
[…childNodes].forEach(child => {
//如果是元素节点,就要看里面有没有v-开头的。并且都要检测有没有 {{}} 语法
if (this.isElementNode(child)) {
this.compileElement(child)
//如果是元素节点的话,还需要递归查找自己的子节点是不是元素节点或文本节点
this.compile(child)
} else {
this.compileText(child)
}
})
}
//将节点移动到内存中
node2fragment(node) {
//将传进来的node转化为虚拟节点
let fragment = document.createDocumentFragment(node)
let firstChild;
//将node中的所有节点,放入虚拟节点中
while (firstChild = node.firstChild) {
fragment.appendChild(firstChild)
}
return fragment
}
//用于判断是不是元素节点
isElementNode(node) {
return node.nodeType === 1
}
}
//compile工具类,用于处理指令相关的内容
CompileUtil = {
//根据表达式取到对应的数据
getVal(vm, expr) { //这里的expr有可能是 school.name.age.data
return expr.split(’.’).reduce((pre, cur) => { //返回的就是在实例中找到的data值,然后再将data对应的值返回
return pre[cur]
}, vm.$data)
},
model(node, expr, vm) {
// node是节点 ,expr 即存在data中的表达式 ,vm当前实例
// v-model 给输入框赋予 value属性 即node.value = xxx
let fn = this.updater[‘modelUpdater’]
//给 v-model 加观察者模式
new Watcher(vm, expr, (newValue) => {
fn(node, newValue)
})
let value = this.getVal(vm, expr)
//调用fn方法,也就是对有v-model的元素的值进行了更新
fn(node, value)
},
html() {

},
on(node,expr,vm,eventName) {
    //expr就是methods里面定义的方法
      node.addEventListener(eventName,(e)=>{
          vm[expr](e)
      })
},
getContentValue(vm, expr) {
    //遍历表达式,将内容重新替换成一个完整的内容
    return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
        return this.getVal(vm, args[1])
    })
},
text(node, expr, vm) {
    //这里的expr又可能是多个 {{}} {{}}
    let fn = this.updater['textUpdater']
    let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
        //args[1] 即为 {{ a }} 中的 a 
        //给有 {{}} 语法的每个表达式加入观察者,比如 这里不是给 {{a}},加观察者,而是给 a 加观察者,这里的expr即为arg[1] 
        //这里新传的newValue也是 {{}} 这种语法,所以我们还得用正则循环匹配一下newValue
        new Watcher(vm, args[1], () => {
            //   this.getContentValue(vm,expr) //拿到的就是新的值
            fn(node, this.getContentValue(vm, expr)) //再将新的值试图渲染出去
        })
        return this.getVal(vm, args[1])
    })
    fn(node, content)
},
//这里又划分一个对象,用于展示对各个指令的操作
updater: {
    //model中,拿到节点的value,用新的值覆盖
    modelUpdater(node, value) {
        node.value = value
    },
    textUpdater(node, value) {
        node.textContent = value
    }
}

}

class Vue {
constructor(options) {
this. e l = o p t i o n s . e l t h i s . el = options.el this. el=options.elthis.data = options.data
let computed = options.computed
let methods = options.methods
//根据 e l 编 译 模 板 i f ( t h i s . el 编译模板 if (this. elif(this.el) {
//数据劫持,将数据全部用Object.defineProperty来定义
new Observe(this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …eProperty(this.data, key, {
get:()=> {
//computed中的this要指向Vue实例,直接computed[key]中的this则指向,computed这个对象
return computed[key].call(this)
}
})
}
for(let key in methods){
Object.defineProperty(this,key,{
get(){
return methods[key]
}
})
}
//目前获取数据要通过 vm. d a t a . n a m e 的 形 式 获 取 。 而 我 们 想 直 接 通 过 v m . n a m e 来 直 接 获 取 t h i s . p r o x y V m ( t h i s . data.name 的形式获取 。而我们想直接通过 vm.name 来直接获取 this.proxyVm(this. data.namevm.namethis.proxyVm(this.data)

        //通过需要渲染的 $el ,和渲染时的数据进行渲染,这里传入 this.$data 也可以 
        new Compiler(this.$el, this)
    }
}
proxyVm(data) {
    for (let key in data) {
        Object.defineProperty(this, key, {
            get() {
                return data[key]
            }
        })
    }
}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值