Vue2 和 Vue3响应式原理的区别

一、vue2的实现原理:

  • 对象类型:通过Object.defineProperty()对属性的读取,修改进行拦截(数据劫持)。
  • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
    Object.defineProperty(data, 'count', {
        get() {},
        set() {}
    })

举例:

let person= {
   name: '张三',
   age: 18
}
let p = {}
Object.defineProperty(p, 'name', {
   configurable: true,
   get() {
     return person.name
   },
   set(value) {
     console.log('有人修改了name属性,更新界面!')
     person.name = value
   }
})
Object.defineProperty(p, 'age', {
   configrable: true,
   get() {
      return person.age
   },
   set(value) {
      console.log('有人修改了age属性,更新界面!')
      person.age = value
   }
})

 此时在控制台输入person和p都可以得到name和age的信息,且都是双向数据绑定。

                     

        接下来做修改属性的操作:p.name = '李四' 

              结果:

                          

                 我们可以看到结果中,有触发到 '有人修改了name属性,更新界面!',

                 因此,证明目前修改数据仍然是双向绑定!

          修改p.age同理...

          但是!!!当我们添加一个属性时:p.sex = '男' ;

                会发现添加的sex属性是没有set和get方法的,是个死数据。

               

                

          当我们删除一个属性时:delete p.name,回车后返回结果是true。再查看p时,里面也没有了name属性;

           同样的,操作这两个方法时,都没有提示‘有人修改了xxxx属性,更新界面!’的日志

                

        因此证明了:这两个操作不是双向绑定的。

 结论:

           对属性读取能捕获到,对属性修改能捕获到,

           但是添加一个属性捕获不到,删除一个已有的数据也捕获不到。

       所以,存在两个问题:

              a. 新增属性、删除属性,界面不会更新。($set 或 $delete)

              b. 直接通过下标修改属性,界面不会自动更新($set、或数组变更方法)

二、vue3的实现原理:

Proxy(window中内置的构造函数): window.Proxy

    new Proxy() 需要传递两个参数

           const p = new Proxy(person, {})  --->  p: 代理数据/代理对象  person:源数据/源对象

           注意:第二个参数必传,即使不传内容,也要用{}空对象进行占位。

    proxy的作用:可以让p去映射对person的操作

    例:   

let person1 = {
   name: '张三',
   age: 18
}
const p1 = new Proxy(person1, {})
console.log(person1);
console.log(p1)

控制台输入person 后可以看到返回值:{name: '张三', age: 18}, 展开可以看到有原型对象

控制台再输入p后可以看到返回值:Proxy {name: '张三', age: 18}, 展开可以看到[[handler]],[[targer]],原型对象

 

接下来对属性进行操作:

     修改:控制台输入p.name = '李四'  ===>  '李四'

               再输入person.name ===> 也变成了'李四'

     添加:p.sex = '男' ===> '男'

                再输入person.sex ===> '男';  再输入p ===> Proxy {name: '李四', age: 18, sex: '男'}

     删除:delete p.age  ===> true

                再输入person ===> {name: '李四', sex: '男'};

目前完成的只是代理,并不是响应式,想要实现响应式,需要借助Proxy中get和set,来捕获到数据的修改

    举例:

let person = {
    name: '张三',
    age: 18
}
const p = new Proxy(person, {
	get(target, prop) {
		console.log('有人读取了p身上的某个属性')
		// 如果get()中没有传参,那么:
		// 在控制台测试一下 :p.name ===> '有人读取了p身上的某个属性' undefined 
		// 有undefined,是因为getter没有return信息
        // return 100
		// 再在控制台测试一下 :p.name ===> '有人读取了p身上的某个属性' 100 
		// 这样虽然有返回值了,但是修改任何一个属性,返回的都是100,所以需要有参数!
		// get(target, prop)的两个参数:
		// 		第一个参数:new Proxy()时传入的参数-person,将这个参数称为源对象或源数据!
        //                 用target来表示。
		// 		第二个参数:用传入的想要修改的值,去源对象中匹配,匹配到的属性的值!
        //                 用prop来表示。
		//		例:控制台输入p.name修改数据后,prop就是用想要修改的name去person中找name;
        //          找到后,返回name对应的值 即'张三'
		// return  target[prop]
		// 验证:控制台输入p.name ===> '张三'
        //       虽然成功了,但是return target[prop]是在操作源数据
		// 所以需要用到 Reflect.get(target, prop)  !!!!!Reflect详解在下面!!!!!!
		Reflect.get(target, prop)
	},
	set(target, prop, value) {
		// 注意!!!!在修改和增加时,都能调用!!!!
		// 前两个参数同get(), 但是比get()多一个参数:value,即,修改的那个值
		console.log(`修改了${prop}属性,改为${value}`)
		target[prop] = value
		// 控制台输入 p.name = '李四' ===> 修改了name属性,改为'李四'
        //        同理,虽然成功了,但是target[prop] = value是在操作源数据
		// 所以需要用到 Reflect.set(target, prop, value)
		Reflect.set(target, prop, value)
	},
	// 拦截删除属性
	deleteProperty (target, prop) {
	    // 参数及内容同get和set
		return Reflect.deleteProperty(target, prop)
	}
})

 Reflect:反射

let obj = {
    a: 1,
    b: 2
}
// 想要读取obj中的a或b属性,除了可以使用obj.a或obj.b方法外,还可以通过window.Reflect方法
Reflect.get(obj, 'a'); // 1
// 如果想要改数据
Reflect.set(obj, 'a', 666); // true
console.log(obj); // {a: 666, b: 2}
// 如果想要删除数据:
Reflect.deleteProperty(obj, 'a'); // true
console.log(obj); // {b: 2}

Reflect的作用:

      为什么使用Reflect?

        ECMA正在尝试将Object中很多有用的API,移植到Reflect身上。

        例如:Object身上有的Object.defineProperty(),Reflect身上也有

      Object.defineProperty()与 Reflect()区别:

         1. 想要通过Object.defineProperty()去追加属性的时候,如果重名,整个代码全部挂掉;

              例如:

// 先写
Object.defineProperty(obj, c, {
     get() {
        return 3
     }
})
// 再写
Object.defineProperty(obj, c, {
     get() {
        return 4
     }
})
// 则会报错

用Reflect.defineProperty()去追加属性的时候,即使重名,也不会报错;但是只有第一次追加的值才有效;且Reflect.defineProperty是有返回值的

// 先写
Reflect.defineProperty(obj, c, {
     get() {
        return 4
     }
})
// 有返回值
// true

// 再写
Reflect.defineProperty(obj, c, {
     get() {
        return 4
     }
})
// 不会报错,但是只有第一次追加的值才有效
// 有返回值
// false

        2. 相比Object.defineProperty()来说好分析错误;

                 Object.defineProperty() 如果有问题要借助try {} catch() {};

                 Reflect.defineProperty() 直接通过判断即可。

if(b) {
    console.log('某某某操作成功')
} else {
    console.log('某某某操作失败')
}

       通过Proxy(代理),拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。

       通过Reflect(反射):对被代理对象的属性进行操作。

new Proxy(data, {
	// 拦截读取属性值
	get (target, prop) {
		return Reflect.get(target, prop)
	},
	// 拦截设置属性值或添加新属性
	set (target, prop, value) {
		return Reflect.set(target, prop, value)
	},
	// 拦截删除属性
	deleteProperty (target, prop) {
		return Reflect.deleteProperty(target, prop)
	}
})

MDN文档中描述的Proxy与Reflect:

        Proxy:https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

        Reflect:https://developer.mozilla.org/zhCN/docs/Web/JavaScript/Reference/Global_Objects/Reflect 

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值