Vue-如何实现响应式
写在前面:总算在今天仔仔细细的看了一遍双向数据绑定的帖子,其实之前也看了很多,都是自己理解能力不太够,再一个静不下心(哭)。看完之后进行一下总结。如有理解不到位的欢迎指正,谢谢。
Vue的响应式:其实就是通过数据的改变去驱动DOM视图的变化。这里是Vue最核心的内容。初始化时通过Object.defineProperty进行绑定,设置通知的机制。当编译生成的渲染函数被实际渲染的时候,就会触发getter进行依赖收集,在数据变化的时候,触发setter进行更新。
首先一个小小入门例子。假设<p>标签的内容会随着obj中数据的改变而变化.。
1 <div id="app"> 2 <p id="name"></p> 3 </div> 4 <script> 5 var obj={} 6 obj.name="软软"//操作Object.defineProperty来使得name数据的改变影响p标签 7 </script>
通过Object.defineProperty(obj, prop, descriptor)来实现。 obj:要在其上定义属性的对象。prop:要定义或修改的属性的名称。descriptor:将被定义或修改的属性描述符。
1 <div id="app"> 2 <p id="name"></p> 3 </div> 4 <script> 5 var obj={}; 6 Object.defineProperty(obj,'name',{ 7 get(){ 8 return document.querySelector('#name').innerHTML; 9 }, 10 set(newVal){ 11 document.querySelector('#name').innerHTML=newVal 12 } 13 }) 14 obj.name="软软"//操作Object.defineProperty来使得name数据的改变影响p标签 15 </script>
接下来创建一个新的newVue.js。通过使用new NewVue(.....)来实现vue的响应式
1 class NewVue{ 2 constructor(options){//接收所想要配置的对象就像是new Vue({data:{...}}) 3 this.$options=options;//先缓存一下options一会其他的类要用到 4 //数据响应化 5 this.$data=options.data;//拿出{data:{}}的数据 6 this.observe(this.$data);//对data中的数据进行观察. 7 } 8 observe(value){ 9 //为了语句健壮性,先判断是否存在这个value,不存在就返回了 10 if(!value||typeof value !='object'){ 11 return; 12 } 13 //遍历该对象 14 Object.keys(value).forEach(key=>{ 15 16 this.defineReactive(value,key,value[key]) 17 }) 18 } 19 //定义数据响应式函数 20 defineReactive(obj,key,val){ 21 this.observe(val)//为了递归实现类似{data:{foo.bar:"xxx"}}中的foo.bar这种数据嵌套的问题 22 Object.defineProperty(obj,key,{ 23 get(){ 24 return val; 25 }, 26 set(newVal){ 27 if(newVal==val){ 28 return; 29 } 30 val=newVal; 31 console.log(`${key}属性更新了:${val}`) 32 } 33 }) 34 } 35 }
创建一个index.html实现这个响应式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <script src="newVue.js"></script> <script> const app=new NewVue({ data:{ test:"fjs是粑粑", foo:{ bar:"bar" } } }); app.$data.test="hello fjs"; app.$data.foo.bar="oh bar" </script> </body> </html>
最终实现的结果:
依赖收集
1 class NewVue{ 2 constructor(options){ 3 this.$options=options; 4 this.$data=options.data; 5 this.observe(this.$data); 6 //模拟一下watcher创建 7 new Watcher(); 8 this.$data.test; 9 new Watcher(); 10 this.$data.foo.bar; 11 } 12 observe(value){ 13 if(!value||typeof value !='object'){ 14 return; 15 } 16 Object.keys(value).forEach(key=>{ 17 this.defineReactive(value,key,value[key]) 18 }) 19 } 20 21 defineReactive(obj,key,val){ 22 this.observe(val) 23 const dep=new Dep() 24 Object.defineProperty(obj,key,{ 25 get(){ 26 Dep.target&&dep.addDep(Dep.target) 27 return val; 28 }, 29 set(newVal){ 30 if(newVal==val){ 31 return; 32 } 33 val=newVal; 34 dep.notify();//通知所有的watcher进行更新 35 } 36 }) 37 } 38 } 39 40 class Dep{//订阅器 41 constructor(){ 42 this.deps=[];//在deps中存放若干依赖,也就是watcher(订阅者).这个依赖其实就是属性值发生改变的属性 43 44 } 45 addDep(dep){//增加依赖 46 this.deps.push(dep) 47 } 48 notify(){//通知所有的依赖(watcher)去做更新 49 this.deps.forEach(dep=>dep.update()) 50 } 51 } 52 53 class Watcher{//订阅者其实就是用来做具体更新的那个对象 54 constructor(){ 55 //将当前这个Watcher实例指定到Dep的静态属性target 56 Dep.target=this;//这个target只有一个,当有第二个watcher出现时就会覆盖成第二个watcher 57 } 58 update(){ 59 console.log('属性更新了!!!') 60 } 61 }