在上篇文章的基础上,Vue核心,数据劫持。创建observer.js文件,将上篇文章的代码复制过来,文件结构如下
文件结构.png
在Vue类中将数据data转化为object.definProperty来定义
class Vue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
//判断根元素是否存在,存在编译模板
if (this.$el) {
//将数据全部转化为object.defineProperty来定义
new Observer(this.$data) //新增
new Compiler(this.$el, this);//编译
}
}
}
数据劫持类
class Observer {
constructor(data) {
this.observer(data)
}
observer(data) {
if (data && typeof data == 'object') {
//判断data合法性,如果是对象才观察,如果是对象
for (let key in data) {
this.defineReactive(data, key, data[key])
}
}
}
defineReactive(obj, key, value) {
this.observer(value) //如果值也是对象,递归劫持
Object.defineProperty(obj, key, {
get() {
return value
},
set:(newValue)=>{
if (newValue != value) {
this.observer(newValue) //赋值的也是对象的话 ,也要劫持
value = newValue
}
}
})
}
}
数据劫持之后,进行改变更新视图,采用发布订阅,首先写一个观察者
class Watcher {
constructor(vm, expr, cb) {
this.vm = vm
this.expr = expr
this.cb = cb
//默认存放一个老值,当值变化时,对比,变化则更新
this.oldValue = this.get()
}
get() {
let value = CompilerUtil.getValue(this.vm, this.expr)
return value
}
update() { //更新操作
let newValue = CompilerUtil.getValue(this.vm, this.expr)
if (newValue != this.oldValue) {
this.cb(newValue) //回调函数
}
}
}
写发布订阅类
//订阅发布
class Dep {
constructor() {
this.subs = [] //存放所有的观察者
}
//订阅
addSub(watcher) {//添加所有的watcher
this.subs.push(watcher)
}
//发布
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
在编译元素和文本时候加入观察者,在编译工具中的 model 和 text内加入,示例代码,具体请移步git
getContentValue(vm,expr){
//遍历表达式,将内容重新替换成一个完整的内容
return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getValue(vm,args[1])
})
},
model(node, expr, vm) {
let value = this.getValue(vm, expr)
let fn = this.updater['modelUpdater']
new Watcher(vm,expr,(newValue)=>{ //给输入框加上观察者,数据更新触发这个方法,给输入框赋新值,新增
fn(node,newValue)
})
fn(node, value)
},
text(node, expr, vm) {
let fn = this.updater['textUpdater']
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
//给表达式的每个变量都加上观察者
new Watcher(vm,args[1],()=>{
fn(node,this.getContentValue(vm,expr)) //返回全的字符串
})
return this.getValue(vm, args[1])
})
fn(node, content)
},
最最最重点,new Watcher之后,我们如何把它放进订阅的subs数组。js是单线程,
每次new Watcher,都会调用获取数据,我们的数据用Observer类劫持,所以在劫持的时候,加入发布订阅功能
defineReactive(obj, key, value) {
this.observer(value) //如果值也是对象,递归劫持
//重点
let dep = new Dep() //给每个属性都加入发布订阅的功能
Object.defineProperty(obj, key, {
get() {
return value
},
set: (newValue) => {
if (newValue != value) {
this.observer(newValue) //赋值的也是对象的话 ,也要劫持
value = newValue
}
}
})
}
此时watcher方法如何和发布订阅联系起来,全局有Dep类,new Watcher是get方法内,将new Watcher放在全局的Dep上,Watcher内的get方法如下
get() {
Dep.target = this //watcher先把自己放在this上
//取值,把观察者和数据结合起来
let value = CompilerUtil.getValue(this.vm, this.expr)
return value
}
Dep.target上有了Watcher,我们在数据劫持时,将new Watcher放入subs数组
defineReactive(obj, key, value) {
this.observer(value) //如果值也是对象,递归劫持
let dep = new Dep() //给每个属性都加入发布订阅的功能
Object.defineProperty(obj, key, {
get() {
//创建watcher时取到对应的内容,并且把watcher放到全局上。
Dep.target && dep.addSub(Dep.target)
return value
},
set: (newValue) => {
if (newValue != value) {
this.observer(newValue) //赋值的也是对象的话 ,也要劫持
value = newValue
}
}
})
}
将Dep.target清空
在Watcher的get方法上加入
get() {
Dep.target = this //watcher先把自己放在this上
//取值,把观察者和数据结合起来
let value = CompilerUtil.getValue(this.vm, this.expr)
//此时加入
Dep.target = null
return value
}
最后,在数据更改的时候调用发布,即数据劫持的set方法
defineReactive(obj, key, value) {
this.observer(value) //如果值也是对象,递归劫持
let dep = new Dep() //给每个属性都加入发布订阅的功能
Object.defineProperty(obj, key, {
get() {
//创建watcher时取到对应的内容,并且把watcher放到全局上。
Dep.target && dep.subs.push(Dep.target)
return value
},
set: (newValue) => {
if (newValue != value) {
this.observer(newValue) //赋值的也是对象的话 ,也要劫持
value = newValue
//此时加入
dep.notify()
}
}
})
}
完成发布订阅,此时在浏览器控制台输入 vm.$data.author.name = "wmm",即可看到变化
v-model现在无法实现输入数据改变视图,CompilerUtil 编译类加入
setValue(vm, expr, val) { //输入值,试图更新
expr.split('.').reduce((data, current, index, arr) => {
if (index == arr.length - 1) {
data[current] = val
}
return data[current]
}, vm.$data)
},
// 解析v-model
model(node, expr, vm) {
let value = this.getValue(vm, expr)
let fn = this.updater['modelUpdater']
new Watcher(vm, expr, (newValue) => { //给输入框加上观察者,数据更新触发这个方法,给输入框赋新值
fn(node, newValue)
})
node.addEventListener('input', (e) => {
let val = e.target.value
this.setValue(vm, expr,val)
})
fn(node, value)
},
此时取值方式为vm.$data.xx,期望的方式应该为vm.xxx,加上代理
在Vue类上加入代理
class Vue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
//判断根元素是否存在,存在编译模板
if (this.$el) {
//将数据全部转化为object.defineProperty来定义
new Observer(this.$data) //新增
//把数据获取操作,vm上的取值操作,都代理到vm.$data
this.proxyVm(this.$data) //代理 此时加入
new Compiler(this.$el, this);//编译
}
}
proxyVm(data){
for(let key in data){
Object.defineProperty(this,key,{
get(){
return data[key]//进行转化
},
set(newValue){
data[key] = newValue
}
})
}
}
}
此时只需要用vm.xxx即可获取数据
git地址:https://github.com/wangxunahe1996/Vue-mvvm
网友评论