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的双向绑定和依赖收集遇到的坑,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!