vue响应式的简单描述

66 阅读6分钟
Object.defineProperty(obj,'a',{ 
	get(){
		return val
		...
	},
	set(newValue){ 
	  
	}
})

数据劫持,每次获取对象属性都会触发getter,赋值触发setter

Object.defineProperty()需要周转变量。用于getter setter 
defineReactive -->闭包,避免临时周转变量

++递归侦测对象所有属性
  Object.create(...以这个对象为原型,创建对象)
  Object.setPrototypeof()

++++++  vue响应式与双向绑定原理 +++++++
    响应式,数据改变驱动视图改变,单向的
    双向绑定:双向的,视图反过来可以改变数据。响应式是双向绑定的一部分

    总结:a.所谓的响应式,指的是数据改变驱动视图改变。这里肯定会涉及到watcher的收集依赖(把模版中的插值表达式中的
    a.b.c获取到,并进行监听,设置回调
    ),这一步是在将data进行数据劫持之后的编译模板阶段进行的。再说一遍,响应式,关注的是视图。需要的是数据改变了之后,
    视图上的展示的值也改变
    b.所谓的双向绑定,需要的是不仅数据改变了更新视图,也需要让视图改变之后更新数据。(v-model),这就需要在模板编译
    的过程中,监听有v-model属性的input输入框的input事件,输入内容后改变绑定的数据值。

    对象响应式原理:数据劫持+发布订阅模式。
    Object与Array
    **如果一个对象是Object==>
    1.遍历所有的属性,对所有对象属性进行数据劫持
    2.这里的数据劫持:Object.defineProperty中的getter与setter函数对数据劫持,
      并且会在getter中收集依赖,setter中发布依赖
    **如果对象是Aarray,一个数组==>
    1.首先修改当前对象的原型,这里是vue通过继承、改写Array对象实例方法,
    重写数组Array方法:push pop shift unshift sort splice reverse
    为什么要重写数组:
       因为Object.defineProperty是根据key值,监听对应的数组。并不能对数组做到每一个值都进行监听,这样太耗费性能
       所以要重写数组的几项常用方法
    2.在重写的方法中:做到两点:
      2.1 如果有新的插入数据,进行监听
      2.2 因为在监听数据的时候,会将Dep实例赋值给当前对象的__ob__属性,所以此时需要调用当前数组对象身上的dep
      的notice方法,发布依赖,执行watcher的回调
      2.3 返回原Array的实例方法的返回值
    依赖的收集与发布依赖于Dep类与Watcher类
    a.Watcher类的构造函数中,会get这个数据的属性的值,在此时,还会将Dep.target赋值为当前的Watcher
在getter函数中访问属性的值的时候,将Dep.target push到依赖数组中,达成收集依赖的效果。
    b.在改变数据时,会调用setter函数,在这个函数中,除了给当前Value赋新值外,还会调用Dep的notice,发布订阅.
    notice方法中,会将当前dep实例中的收集依赖的数组的每一个watcher执行update函数,新值不等于旧值或者新值是
    对象时候,执行watcher的回调

    双向绑定原理:
    在原来的响应式原理的基础上,嵌入模版解析的相关逻辑
    首先在observe对象之后,开始解析模版
    nodeType:
    1 元素节点
    2 属性节点
    3 文本节点
    解析模版的逻辑包括:
       v-model双向绑定(视图上改变驱动数据也改变)、插值表达式替换(数据改变驱动视图更新)
       通过el获取根元素,document.createDocumentFragment创建fragment,fragment.appendChild(firstChild)
       ,通过这样将页面的元素节点全部放到碎片节点中
       **v-model  双向绑定:双向的数据绑定:data数据绑定到input的值,input输入值也改变data数据
       当nodeType是1,且node.nodeName是“INPUT”时,
         a.获取当前node的所有属性,Array.from(node.attributes),遍历所有的属性,当属性是‘v-model’时,
               点表达式,获取当前绑定的data的值
               const value=item.nodeValue.split('.').reduce((total,current)=>total=total[current],vm.$data)
               node.value=value
               这样就实现了将data的值赋给input的value属性
               并且这里需要监听v-model绑定的点语法属性,当绑定的属性的值发生变化时,执行回调
               Watcher的构造函数会去获取点表达式对应的值,这个时候就走到了上面的数据响应式原理那里
               new Watcher(vm,item.nodeValue,(newValue)=>{
                       node.value=newValue
               })
               //这里的item是循环的每一个属性,通过nodeValue获取v-model绑定的点语法属性
         b.给当前元素绑定input事件,实现输入框输入的值改变data中的值
               通过上面的reduce那样
               const arr1=node.nodeValue.split('.');
               const arr2=arr1.slice(0,arr1.length-1);
               const final=arr2.reduce((total,current)=>total[current],vm.$data);
               //这里为什么只取值到arr1的倒数第二个。是因为arr2.reduce的这个方法如果输出一个值,然后去改变它没有
               任何意义,也不会走到setter函数中,
               //所以返回到最终节点的上一层,然后修改其属性值,就能到达setter函数中
               final[arr1[arr1.length-1]]=e.target.value
       **插值表达式  单向的数据绑定,将data绑定到模板上
           首先就是匹配文本节点
               node.nodeType=3,匹配到所有的文本节点
               利用正则匹配插值表达式
               let reg=/\{\{\s*(\S+)\s*\}\}/  //匹配形如{{a.b.c}}
               //这里将nodeValue存一下是因为后来的逻辑中会改变nodeValue,导致result不会匹配到插值表达式
               let nodevalue=node.nodeValue;
       result = reg.exec(node.nodeValue)
               if(result){ 
                 let arr=reg[1].split('.')//获得属性.的数组
                     //此处的value是插值表达式中的值
                     let value=arr.reduce((total,current)=>total[current],vm.$data) ;//获取最终的
                     a.b.c属性值
                     node.nodeValue=nodevalue.replace(reg,value)
                     //在这里添加一个watcher ,当这里的v-model绑定的属性的值再发生变化,怎样处理回调
                     new Watcher(vm,result[1],(newValue){ 
                            node.nodeValue=nodevalue.replace(reg,newValue)
                     }) 
                }  
			
看了vue源码之后对自己写代码时的影响

1.展示类用的数据,比如文章详情,用Object.freeze(value)冻结对象,这样不会遍历对象的属性做响应式处理,既省性能,又能
提高页面加载速度
2.data函数为什么要返回对象: 因为数据响应式处理,只能接收对象做处理,所以为了监听每一个属性值的变化,data函数必须
返回对象
3.data对象的嵌套层级尽可能的少一点 属性尽可能的也少一点。层架嵌套的越深,越需要多一次遍历属性,会消耗更多的性能


vue口述数据响应式原理--未完成
	1. 首先在observe.js判断要侦听的数据是不是对象,不是对象不做侦听。是对象判断是否已经被侦听,没有的话走到
        Observe类中
	2. Observe的class只有两个作用,
        1.循环.将侦听的对象的每一层级,为的是把每个属性都改为响应式侦听 
        2.对数组做特殊处理,改写数组的原型链
	3. 要改为响应式的侦听属性,需要用到另一个方法defineReactive。
	   a.这里最外层是一个外包,内层用的是Object.defineProperty。为的是解决defineProperty方法带来的需要中间变量
           的问题
	   b.defineProperty内部:
	     主要的就是利用getter拦截数据并收集依赖、setter拦截数据,监听到数据被重新赋值,发布订阅的事件
		 依赖的收集与发布订阅都依赖于watcher
                     首先用observe.js侦听数据之后,watcher观察数据
		 收集依赖:watcher观察数据时候,会在构造器中首先获取数据的值,在这一步将Dep.target设置为当前的watcher
                 实例。
                     然后就是获取观察的数据的值,并赋值给当前的watcher实例的value属性。这里的获取数据的值,就会用
                 到defineReactive中拦截数据的getter。且此时的Dep.target不为空是当前的watcher实例。判断不为空之后,
                 就是添加依赖了。
		 dep.depend()-->将当前的watcher添加到当前Dep实例subs数组中,相当于添加了一个订阅。
		 发布订阅:更改数据时,直接调用当前dep的notify方法,此时,将当前中的订阅全部执行watcher中的回调处理
                 函数	 
	4.Dep类
	5.Watcher类
	说到对数组的侦听,因为重写的那几个方法都有可能会改变原数组的下标值,所以要监听最新的数组,以及可能会插进来的
        新值