vue2与vue3的响应式原理

vue2

1、对象类型

原理:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
1、定义数据:

const vm = new Vue({
el: '#root',
data: {
	person:{
		name: '张三',
		age: 18
	}
}
})

2、加工数据:为了实现响应式,vue会给data中的每一组key:value都形成了get和set的写法
只要data中的属性发生改变,就会调用set方法,然后重新解析模板,生成虚拟DOM,新旧虚拟DOM对比,更新页面
vue2与vue3的响应式原理_第1张图片
3、模拟数据监视:

<script type="text/javascript" >
    let data = {
        name:'张三',
        age:18
    }
    //创建一个监视的实例对象,用于监视data中属性的变化
    const obs = new Observer(data)		
    //准备一个vm实例对象
    let vm = {}
    vm._data = data = obs
    function Observer(obj){
        //汇总对象中所有的属性形成一个数组
        const keys = Object.keys(obj)
        //遍历
        keys.forEach((k)=>{
            Object.defineProperty(this,k,{
                get(){
                    return obj[k]
                },
                set(val){
                    obj[k] = val
                }
            })
        })
    }
</script>

这里只能实现一层监视,如果data中还有嵌套的对象,则无效。vue底层做了无限的递归,能够监视data中所有层次的数据。
4、数据代理:

vm._data = data

5、data中已经写好的属性都匹配会匹配get和set(影响页面),之后通过vm._data.person.sex = '男'这种方法添加的属性,通过delete this.person.name删除属性是没有响应式的,界面不会更新
vue2与vue3的响应式原理_第2张图片
6、实现后添加的数据有响应式
语法:Vue.set(target,key,val)

Vue.set(vm._data.person,sex,'男')
// 数据代理 
Vue.set(vm.person,sex,'男')
// 或者通过这种方式
vm.$set(vm.person,sex,'男')

注意:
target不能是Vue实例或者实例的根数据对象

// 报错
Vue.set(vm._data,'leader','领导')
Vue.set(vm,'leader','领导')

2、数组类型

原理:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
1、定义数据:

const vm = new Vue({
	el: '#root',
	data: {
		hobby:['抽烟','喝酒','烫头']
	}
})

2、vue没有为数组里面的某一个元素匹配get、set,如果通过索引去改变值时,数据能改掉,但是Vue监测不到,不会引起页面的更新
vue2与vue3的响应式原理_第3张图片
3、通过vue封装过了的修改数组方法:push pop shift unshift splice sort reverse可以实现响应式
这些数组的方法被Vue所管理,里面的这些方法不再是正常Array原型对象上的了,它除了可以正常调用Array原型对象上的这些方法外,还能够重新解析模板等一系列操作。
4、Vue.set(vm.hobby,1,'打台球')也能实现响应式数组修改

vue3

vue3的响应式要通过函数是通过函数来实现的

1、ref函数

1、作用: 定义一个响应式的数据
2、语法: const xxx = ref(initValue)

  • 创建一个包含响应式数据的引用对象
  • JS中操作数据: xxx.value
  • 模板中读取数据: 不需要.value,直接:
    {{xxx}}
  • 备注:接收的数据可以是:基本类型、也可以是对象类型。

2、 reactive函数

1、作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
2、语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)

  • reactive定义的响应式数据是“深层次的”。
  • 如果接收的是数组,通过索引改变的数据也是响应式的

实现原理

1、对于基本类型的数据,响应式依然是靠Object.defineProperty()getset完成的。
2、对于对象类型的数据

  • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
  • 通过Reflect(反射): 对源对象的属性进行操作。
let person = {
    name:'张三',
    age:18
}
const p = new Proxy(person, {
	// 拦截读取属性值
    get (target, propName) {
        console.log(`有人读取了p身上的${propName}属性`)
    	// return target[propName]
        return Reflect.get(target, propName)
    },
    // 拦截设置属性值或添加新属性
    set (target, propName, value) {
        console.log(`有人修改了p身上的${propName}属性`)
    	// target[propName] = value
        Reflect.set(target,propName,value)
    },
    // 拦截删除属性
    deleteProperty (target, propName) {
        console.log(`有人删除了p身上的${propName}属性`)
    	// return delete target[propName]
        return Reflect.deleteProperty(target, propName)
    }
})

3、Object.defineProperty(target,propName,value) 如果对对象追加一个属性重名了,会直接报错(单线程后面的不执行)

Object.defineProperty(obj,'c',{
    get() {
        return 3
    }
})
Object.defineProperty(obj,'c',{
    get() {
        return 4
    }
})

4、Reflect.defineProperty(target,propName,value) 如果对对象追加一个属性重名了,不会报错。对框架封装比较友好,可以用if…else

const x1 = Reflect.defineProperty(obj,'c',{
	get() {
        return 3
    }
})
console.log(x1) // true
const x2 = Reflect.defineProperty(obj,'c',{
	get() {
        return 4
    }
})
console.log(x2) // false

你可能感兴趣的:(vue,vue.js)