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类
说到对数组的侦听,因为重写的那几个方法都有可能会改变原数组的下标值,所以要监听最新的数组,以及可能会插进来的
新值