vue实现双向绑定和依赖收集遇到的坑
在掘金上买了一个关于解读vue源码的小册,因为是付费的,所以还比较放心
在小册里看到了关于vue双向绑定和依赖收集的部分,总感觉有些怪怪的,然后就自己跟着敲了一遍。敲完后,发现完全无法运行, 坑啊, 写书人完全没有测试过。
然后自己完善代码,越写越发现坑,问题有些大。。。。。。
最后自己重新实现了一遍,代码较多。用到观察订阅者模式实现依赖收集,Object.defineProperty()实现双向绑定
/*
自己写的代码,实现vue的双向绑定和依赖收集
场景:多个子组件用到父组件data中的数据,当父组件data中的此数据发生改变时,
所有依赖它的子组件全部更新
通常子组件的从父组件中拿取的数据不允许发生改变
*/
//订阅者Dep
//一个订阅者只管理一个数据
classDep{
constructor(){
this.subs=[]//存放vue组件
}
addSubs(sub){
this.subs.push(sub)
console.log('addwatcher:',sub._name)
}
notify(){
this.subs.forEach(sub=>{//通知vue组件更新
sub.update()
})
}
}
//监听者
//一个vue实例包含一个Watcher实例
classWatcher{
//在实例化Watcher时,将Dep的target指向此实例,在依赖收集中使用
//因为依赖收集是在组件初始化时触发的,而数据变更后视图相应变更是在初始化后
//所以让Dep.target指向此实例,当此vue实例初始化完成后,再指向下一个正在初始化的vue实例完成依赖收集
constructor(name){
Dep.target=this
this._name=name
}
update(){
//这里模拟视图更新
//其实还应该让子组件的props相应值与父组件更新的数据同步
console.log("子组件视图更新了..."+this._name)
}
}
//对data中的数据设置读写监听,并且创建订阅者,用于收集子组件的依赖和发布
functiondefineReactive(obj,key,value){
//对vue实例中data对象的每一个属性都设置一个订阅者Dep
letdep=newDep()
//第二个vue实例的监听覆盖了第一个vue实例的监听,因为引用的obj是同一个
Object.defineProperty(obj,key,{
configurable:true,
enumerable:true,
get(){
//在读此属性时,将当前watcher对象收集到此属性的dep对象中
//在实例化vue时将Dep.target指向当前Watcher
//get()依赖收集的时候是vue组件初始化的时候,set()是在初始化后
if(dep.subs.indexOf(Dep.target)===-1){
dep.addSubs(Dep.target)
}
//returnobj[key]此写法报错提示栈溢出原因是无限调用get()
returnvalue
},
set(newVal){//此属性改变时,通知所有视图更新
if(newVal!==value){
value=newVal
dep.notify()
}
}
})
}
//接收一个对象作为参数,将该对象的所有属性调用defineReactive设置读写监听
functionobserver(obj){
if(!obj||(typeofobj!=='object')){
return
}
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
}
//构造函数,监听配置options中的data()方法返回的对象的所有属性的读写
classVue{
constructor(options){
this._name=options.name
this._data=options.data
//每个vue组件都是一个vue实例,在一个页面中有多个vue实例
//在初始化该vue实例时,new一个Watcher对象,使Dep.target指向此实例
newWatcher(options.name)
//给data中的数据挂载读写监听
observer(this._data)
//模拟vue解析template过程,获取从父组件传递过来的props
//在这里进行依赖收集
this._props=options.props?getProps():{}
//实例化该组件的子组件
this._children=options.render?(options.render()||{}):{}
}
}
//父组件数据
letdata={
first:"hello",
second:'world',
third:['啦啦啦']
}
lettimes=0
//第一次调用返回的是第一个子组件的从父组件继承的数据(vue中props属性的值)
//第二次调用返回的是第二个子组件的从父组件继承的数据(vue中props属性的值)
functiongetProps(){
times++
if(times==1){
letobj={first:"",second:""}
Object.keys(obj).forEach(key=>{
//如果是对象,则进行深拷贝
//这里使用到了父组件的数据,触发依赖收集
if(data[key]instanceofObject){
obj[key]=JSON.parse(JSON.stringify(data[key]))
}else{
obj[key]=data[key]
}
})
returnobj
}elseif(times==2){
letobj={first:"",third:""}
Object.keys(obj).forEach(key=>{
if(data[key]instanceofObject){
obj[key]=JSON.parse(JSON.stringify(data[key]))
}else{
obj[key]=data[key]
}
})
returnobj
}
}
letvue_root=newVue({
name:'vue_root',
data,
//模拟编译template和实例化vue的过程
//在编译父组件并且传递参数给子组件时,将子组件的watcher添加进父组件的dep
render(){
letvue_1=newVue({
name:'vue_1',
data:{},
props:true,
render(){}
})
letvue_2=newVue({
name:'vue_2',
data:{},
props:true,
render(){}
})
return{
vue_1,
vue_2
}
}
})
console.log(vue_root)
vue_root._data.first='hellohello'//vue_1和Vue_2都依赖此数据,都更新
vue_root._data.third="aaa"//只有vue_2依赖到了此数据,更新
总结
以上所述是小编给大家介绍的vue的双向绑定和依赖收集遇到的坑,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!