vue2响应式
- 在
init
数据初始化的时候,对象内部通过defineReactive
方法,使用Object.defineProperty
将属性进行劫持(这时候只会劫持已经存在的属性)。如果数据是数组类型, Vue2中是通过重写数组方法来实现。多层对象是通过递归来实现劫持的。 - 在初始化流程中的编译阶段,当
render function
被渲染的时候,会读取Vue实例中和视图相关的响应式数据,此时会触发getter
函数进行 依赖收集(将观察者Watcher
对象存放到当前闭包的订阅者Dep
的subs
中),此时的数据劫持功能和观察者模式就实现了一个MVVM模式中的Binder,之后就是正常的渲染和更新流程。 - 当数据发生变化或者视图导致的数据发生变化时,会触发数据劫持的
setter
函数,setter
会通知初始化依赖收集中的Dep
中和视图相应的Watcher
,告知需要重新渲染视图,Watcher
就会再次通过update
方法来更新视图。
vue的组件通信
props
/$emit
$emit
/$on
(eventbus)- vuex (单向数据流、state存放数据,mutation修改更新数据,action提供异步)
$attrs
/$listeners
($attrs
里存放的是父组件中绑定的非 Props 属性(传递下来没被props接收的参数),$listeners
里存放的是父组件中绑定的非原生事件。)- provide/inject(祖先组件向其后所有子孙注入一个依赖都可调用)
遇到父子传数组或者对象时,通过加default实现。
1
2
3
4
5
6
7 props: {
content: {
type: Array,
// default: function () { return [] }
default: () => []
},
}
Vue.$set()
如果在实例创建之后添加新的属性到实例上,它不会触发视图更新
- 当你把一个普通的 JavaScript 对象传入 Vue 实例作为
data
选项,Vue 将遍历此对象所有的属性,并使用Object.defineProperty
把这些属性全部转为 getter/setter。- 由于 Vue 会在初始化实例时对属性执行 getter/setter 转化过程,所以属性必须在 data 对象上存在才能让 Vue 转换它,这样才能让它是响应的。
- Vue 不允许在已经创建的实例上动态添加新的根级响应式属性 (就是说就算是空也要提前在data中写好了)。
- 它可以使用 Vue.set(object, key, value) 方法将响应属性添加到嵌套的对象上
this.$set(this.obj,'e',02)
get
vs. defineProperty
当使用 get
关键字时,它和Object.defineProperty(对象,属性名,对应的方法或属性)
有类似的效果,在classes
中使用时,二者有细微的差别。
当使用 get
关键字时,属性将被定义在实例的原型上,当使用Object.defineProperty()
时,属性将被定义在实例自身上。
defineProperty其实并不是核心的为一个对象做数据双向绑定,而是去给对象做属性标签,只不过属性里的get和set实现了响应式
Vue.set()和this.$set()
vm.$set( target, propertyName/index, value )
this.$set(this.items, 1, 'two')
将数组第二位更新为’two’
区别在于 Vue.set( ) 是将 set 函数绑定在 Vue 构造函数上,this.$set() 是将 set 函数绑定在 Vue原型上。
1.判断目标值是否为有效值,不是有效值直接停止
2.判断是否为数组,并且key值是否为有效的key值
- 如果是数组,就选择数组的长度和key值取较大值作为数组的新的length值,并且替换目标值
- splice方法,重写了,所以执行splice,会双向数据绑定
3.判断目标值是否为响应式的
- 如果是vue实例,直接不行
- 如果不是响应式的数据,就是普通的修改对象操作
- 如果是响应式数据,那就通过Object.defineProperty进行数据劫持
4.通知dom更新
Vue的mvvm原理
不只是object.defineProperty();从数据改变到视图改变,通过observe、dep、watcher、compile四个类和CpompileUtil辅助类实现的。
observe
会遍历检测每一个属性,如果不是对象则返回
1 | function observe(target) { |
Watcher
观察者的目的就是给需要变化的那个元素增加一个观察者,用新值和老值进行比对,如果数据变化就执行对应的方法
这个类主要是用来观察方法/表达式
中引用到的数据(数据需要是 reative 的,即 data 或者 props)变更,当变更后做出相应处理。
在compile的时候,给需要重新编译的DOM增加watcher
Dep
依赖收集器,如何将视图和数据关联起来呢?就是将每个数据和对应的watcher关联起来。当数据变化时让对应的watcher执行update方法即可!再想想在哪做操作呢?就是我们的set和get!
1 | class Dep{ |
Compile
交给virtualDOM完成。
数据发生改变,发布订阅
当data中的某一项数据发生改变时,将调用这个属性的Dep的notify方法,通知订阅了该属性的Watcher调用自身的update方法改变DOM
1 | // watcher中的get方法 |
总流程
首先会使用的类:
- Observer类(数据劫持将对象所有属性改成get、set方法)
- 深度递归劫持对象中的其他对象
- defineReactive
- 定义响应式,其中使用的基础是defineProperty
- 每个变化的数据都会对应一个数组在dep中
- 就是把对象
obj
里的属性key
变成一个getter/setter形式的响应式的属性。同时在getter的时候收集依赖,并在setter的时候触发依赖。
- Watcher类
- 获取值的方法get,getVal,然后保存在dep中
- 更新值的方法,如果两值不一致就更新
- 在compile时,当数据变化的时候调用Watcher判断更新
关于发布订阅流程
- dep(一个依赖收集通知器)
vue的双向绑定
vue2.x
object.defindProperty()
定义对象的属性,get和set才是实现响应式的
defineReactive 对依赖收集的总结
在初次渲染时,会触发一次get函数,为了提高效率,节省资源,采用依赖收集,这里以之前手写的为例,get部分,我们就会对this.$data
里的每一个属性(即key值)进行收集,看在哪些组件里进行了调用,以此提高效率。
而在set部分,就会更新我们收集到的依赖
vue3
相对于vue2
1 | var obj={ |
diff
1 | //diff算法匹配机制 |
关于vue的diff优化
- 在update时旧node和新node头尾都有两个头尾变量,会先对他们的两个变量相互比较,都没匹配成功就会去比较key了
- key不要用index。
Vue3的diff优化
在模板编译的时候会在动态标签末尾加上/Text/PatchFlag,(也就是在生成VNode的时候,同时打上标记,在这个基础上再进行核心的diff算法),
- 当 patchFlag 的值「大于」 0 时,代表所对应的元素在 patchVNode 时或 render 时是可以被优化生成或更新的。
- 当 patchFlag 的值「小于」 0 时,代表所对应的元素在 patchVNode 时,是需要被 full diff,即进行递归遍历 VNode tree 的比较更新过程。
「Vue3.0对于不参与更新的元素,做静态标记并提示,只会被创建一次,在渲染时直接复用。」
VUEX和eventbus
- 不是复杂的组件交互绑定的时候可以用eventbus
- 有比较深层结构复杂,容易经常交互的组件就要用vuex更好一些,eventbus的性能开销更大而且会有很多绑定操作。
- 全局状态管理可以适用于大的项目和交互复杂的项目
vuex的更新状态
- vuex内部实例化了一个vue实例, 并且将store的state,挂载在vue实例data中,$$state属性。在vuex源码可以看看相关的代码。
- 因为挂载在data中, 因此对vue对state也进行了setter和getter的劫持。更改store中的state实质上更改的vue实例data中的数据。
vuex本质就是一个没有template的vue组件
v-text 和 v-html 区别
v-text就是{{ }}
,其中的东西都不会进行html解析
v-html会对其中的东西进行html解析
NextTick原理
- 把回调函数放入callbacks等待执行
- 将执行函数放到微任务或者宏任务中
- 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调
Vue
在更新DOM
时是异步执行的。当数据发生变化,Vue
将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新根据环境放入不同任务中:
Promise.then
、MutationObserver
、setImmediate
、setTimeout