一、大致阐述一下Vue2响应式的原理
1.初始化Vue实例的时候,对data对象进行劫持
- 通过defineProperty对data对象进行数据劫持
- 遍历data对象中的数据,对数据进行劫持
2.在劫持数据过程中,对getter和setter进行改写
- getter:为该元素添加依赖收集,将当前的Watcher对象加到依赖列表中
- setter:进行派发更新,通知依赖该属性的Watcher进行更新操作
3.在编译模板的过程中,对数据的访问进行改写,加入依赖收集的逻辑
- 使用模板编译器将模板编译成render渲染函数
- 在渲染函数中对数据的访问进行改写,添加依赖收集的逻辑
4.当数据发生变化时,触发依赖该属性的Watcher对象进行更新操作
- 当数据发生变化时,会调用数据的setter方法
- setter方法会通知依赖该属性的Watcher进行更新
5.在更新过程中,对更新队列进行操作优化,避免重复更新和不必要的计算
- 将所有需要更新的Watcher对象添加到更新队列中
- 对更新队列进行排序,以确保组件的正确更新顺序
- 对更新队列进行去重
6.在更新过程中,对组件的生命周期进行管理,确保更新时期的正确性和性能优化
在组件更新前,调用beforeUpdate钩子函数 在组件更新后,调用updated钩子函数 在组件销毁后,调用beforeDestroy钩子函数,进行清理工作
二、大致阐述下如何在初始化Vue实例的时候,对data对象进行劫持
2.1 初始化Vue实例
当我们创建了一个Vue组件实例时,Vue会在组件实例初始化之前,created中调用initData函数,对函数进行初始化。
initData函数主要实现:
//获取options中的data数据赋值给data
let data = vm.$options.data
//判断data类型,如果是函数,则通过getData调用该函数获得纯对象,如果不是纯对象,则赋值{}
//纯对象:没有通过构造函数创建或者没有继承其他对象的属性或方法的对象,通常是通过对象字面量创建的。
data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm )
}
//代理实例上的data
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
//判断它是否已经在props或methods中定义过
if (methods && hasOwn(methods, key)) {......}
}
//判断它是否已经在props中定义过
if (props && hasOwn(props, key)) { ...... }
else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
2.2 通过observe实现响应式
初始化完成后,执行observe函数,这是实现响应式的关键。observe的作用是将一个普通对象转换成响应式对象,即对该对象的属性进行劫持,实现数据的双向绑定。
observe函数具体如下:
function oberseve(value:any,asRootData:?boolean) Observer|void {
//判断是不是一个对象
if(!isObject(value) || value instanceof VNode){
return
}
let ob:Observer | void;
//判断数据是否已经是响应式的
if(hasOwn(value,'__ob__') && value.__ob__ instanceof Observer){
ob = value.__ob__
}else if(
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) &&
!value._isVue
){
ob = new Observer(value)
}
// 如果是根数据对象,即 Vue 实例的数据对象,则将其标记为根响应式对象
if(asRootData && ob){
ob.vmCount++;
}
return ob
}
在observe函数中,首先判断value是否是对象或者不是虚拟节点(虚拟节点不需要进行劫持),再判断当前value中是否存在__ob__属性来判断是否需要new一个Observer实例,如果是根数据对象,即 Vue 实例的数据对象,则将其标记为根响应式对象。
2.2.1 Observer类
接下来讲讲Observer类,在Observer类中,分别实现了对数组和对象的响应式
export class Observer{
value:any;
dep:Dep;
vmCount:number;
constructor(value:any){
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value,'__ob__',this);
if(Array.isArray(value)){
//将该数组的原型链指向arrayMethods
protoAugment(value,arrayMethods);
this.obesrveAarray(value);
}else{
this.walk(value);
}
}
walk(obj:Object){
const keys = Object.keys(obj);
for(let i = 0;i < keys.length; i++){
//为对象中的元素实现响应式
defineReactive(obj,keys[i])
}
}
//为数组中的元素实现响应式
observeArray(item:Array<any>){
for(let i = 0; l = item.length; i < l; i++){
observe(item[i])
}
}
}
Observer类主要完成以下几个任务:
- 将传进来的value标记为响应式对象,并通过def为当前value添加一个值为Observer实例的属性__ob__。
- 判断value是否是数组,如果是数组,则需要另外处理,通过protoAugment函数将数组的原型指向修改为传入的arrayMethods。因为数组不能通过defineProperty来进行劫持(因为setter是需要通过=赋值的时候才会触发,当我们使用数组方法来改变数组的时候,数组长度是在内部进行改变的,不会涉及到setter,所以不能通过defineProperty来操作数组),所以需要对改变数组的7个方法进行重写,以此来保证数组在变化时,能够触发回调函数。
- 调用observeArray函数,遍历数组的每个元素,将其转换成响应式数据。
- 调用walk函数,对对象中的所有元素通过defineReactive函数(重写getter和setter)转换成响应式。
2.2.2 Dep类
我们注意到,在Observer类中,有一个dep属性,类型为Dep类,接下来简单分析一下Dep类具体作用,在后续Watcher类时,会详细说明。以下是一个简单Dep类的实现。
class Dep{
constructor(){
//依赖列表,用于存放Watcher
this.subs = [];
}
//添加Watcher实例
addSub(sub) this.subs.push(sub);
//删除Watcher实例
removeSub(sub){
const index = this.subs.indexOf(sub);
if(index != -1){
this.subs.splice(index,1)
}
}
//添加当前的Watcher实例
depend(){
if(Dep.target){
Dep.target.addDep(this)
}
}
notify(){
const subs = this.subs.slice();
for(let i = 0,i<subs.length;i++){
subs[i].update()
}
}
Dep.target = null;
const targetStack = [];
function pushTarget(target){
targetStack.push(target);
Dep.target = target;
}
funciton popTarget(){
targetStack.pop();
Dep.target = targetStack[targetStack.length - 1]
}
}
以上就是最近学习vue2响应式原理的一些总结,仅供参考,欢迎指正!