Vue源码探索之知识小储备 ——Object.defineProperty VS proxy

                    前端基础知识储备

1.Vue源码探索之知识小储备 ——Object.defineProperty VS proxy

响应式数据/双向绑定原理

Vue数据双向绑定主要指的是:数据变化影响视图。视图变化影响数据。其中视图影响数据通过监听用户操作来得到变化得数据。所以 Vue数据双向绑定的工作主要是如何根据Data变化更新View。 简而言之, Vue要实现数据双向绑定,需要做三件事情:

  1. 检测到Data变化(Observer)
  2. 追踪收集依赖,通知变更(Dep)
  3. 更新View视图(Watcher)

本文先来讲解vue实现数据双向绑定的第一步:如何检测到Data的变化。

假设我们有如下这样的一个data对象

let obj = {

    a: 1, 

    b: 2,

    c: ['排序', '递归']};

我们修改obj.a = 3, 那怎样才可以监听到这个改变呢?我们很容易想到大名鼎鼎的Object.defineProperty()。Object.defineProperty() 方法会直接在一个对象上定义一个新属性。或者修改一个对象的现有属性并返回此对象

1.Object.defineProperty()

Object.defineProperty(obj,prop,descriptor)参数:【obj】是要定义属性的对象,【prop】是要定义或修改的属性的名称或Symbol.【descriptor】:要定义或修改属性的描述符

对象里目前存在的属性描述符号有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值是可写,也可以是不可写的。存取描述符是由getter函数和setter函数所描述的属性。一个描述符只能是这两者其中之一;不能同时两者

1.1数据描述符

数据描述符是一个具有值的属性,该值可能是可写的,也可能是不可写的。

它具有以下可选的键值:

configurable:表示该属性能否通过delete删除,能否修改属性的特性或者能否修改访问器属性,默认为false。

enumerable:表示该属性是否可以枚举,即可否通过for..in访问属性。默认为false。

value:表示该属性的值。可以是任何有效的Javascript 值(数值,对象。函数等)。默认为undefined。

writable:表示该属性的值是否可写,默认为false。

【栗子】:

console.log(Object.getOwnPropertyDescriptor(obj, 'a'));

    result: { value: 2, writable: true, enumerable: true, configurable: true }

1.2存取描述符

存取描述符还具有以下可选键值:

get:  

属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。  

执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。  

该函数的返回值会被用作属性的值。

set:  

属性的 setter 函数,如果没有 setter,则为 undefined。  

当属性值被修改时,会调用此函数。  

该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。

显然,我们只需要利用对象属性描述符里面的存取描述符,就能够检测到对象的改变。

然,我们只需要利用对象属性描述符里面的存取描述符,就能够检测到对象的改变。

显然,我们只需要利用对象属性描述符里面的存取描述符,就能够检测到对象的改变。

【栗子】:

let _value = obj.a;Object.defineProperty(obj, 'a', {

   get() {

       console.log('you get a number');

       return _value;

   },

   set(newVal){

       console.log('you set a number');

       _value = newVal;

   }});

console.log(obj.a);obj.a = 2;console.log(obj.a);

// 打印结果you get a number1you set a numberyou get a number2

我们成功检测到了obj对象中a的改变,vue2.x正式基于这一特性实现了数据双向绑定的Observer部分。趁热打铁,再多试一次,享受Object.defineProperty的乐趣吧~

【Do it again】:

let _value = obj.c;Object.defineProperty(obj, 'c', {

   get() {

       console.log('you get the array');

       return _value;

   },

   set(newVal){

       console.log('you change the array');

       _value = newVal;

   }});

console.log(obj.c);obj.c.push('二分查找');console.log(obj.c);

// 打印结果you get the array[ '排序', '递归' ]you get the arrayyou get the array[ '排序', '递归', '二分查找' ]

OMG!怎么没有打印set里的日志,可是数组确实追加进去了。难道Object.defineProperty不能监听数组的变化?没错,确实是这样,因此vue2.x在实现数组的响应式时,它使用了一些hack,把无法监听数组的情况通过 重写数组 的部分方法(push,pop, shif, unshift, splice, sort, reverse)来实现响应式,这也只限制在数组的这七个方法,其他数组方法及数组的使用则无法检测到,例如如下两种使用方式:

data.c[index] = newValue;

data.c.length--;

2.数组hack

  1. 复制一份数组的原型方法,防止污染

const oldArrayProperty = Array.prototype;const arrayProto = Object.create(oldArrayProperty);

  1. 遍历,将arrayProto对象上的方法转换为观察者对象

const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];  methodsToPatch.forEach(methodName => {

    // 往arrayProto对象中加入methodsToPatch中的属性,并执行原生的methodsToPatch对应的方法    Object.defineProperty(arrayProto, methodName, {

        value () {

            //监听数组变更            console.log('you change the array');

            oldArrayProperty[methodName].apply(this, arguments)

        }

    })})

  1. 让数组继承arrayProto中的方法

obj.c.__proto__ = arrayProto;

好啦,见证奇迹的时刻:

console.log(obj.c);obj.c.push('二叉分法');console.log(obj.c);

结果:[ '排序', '递归' ]you change the array[ '排序', '递归', '二叉分法' ]

一个简单的数组hack就实现了,vue2.x也正是基于这种数组方法重写的原理,监听数组变更,从而实现数组类型数据的双向绑定的。

vue2.x在实现数据响应式时,需要区别对待数组和对象,因此在vue3.0,尤大大和他的团队弃用了这种方式,选择使用es6的新特性 Proxy 和 Reflect 来实现双向绑定。

3.Proxy

vue3.0基于Proxy来做数据劫持代理,可以支持原生数组的响应式。还可以直接支持新增和删除属性,使用起来十分简洁明了。

var obProxy = new Proxy(ob, {

    get(target, key, receiver) {

        console.log('you get:' + key);

        return target[key];

    },

    set(target, key, value, receiver){

        console.log('you set:' + key);

        // target[key] = value;        // Reflect.set(target, key, value)等同于上面的target[key] = value;        return Reflect.set(target, key, value);

    }});

我们来分别打印一下

console.log(obj.a);obj.a = 666;console.log(obj.a);

结果:1666不进入上面的get和set方法

console.log(obProxy.a);obProxy.a = 2;console.log(obProxy.a);

结果:you get:a1you set:ayou get:a2

结果印证:proxy是去创建一个新的代理对象,所以在改变原对象obj时,上述中的get与set中的console均未打印。

4. 对比:为什么vue3改用proxy实现数据响应式?

1.Proxy可以直接监听对象而非属性

2.Proxy 可以直接监听数组的变化

3.Proxy有多达13种拦截方法,不限于apply,ownKeys ,deleteProerty,has等等是Object.defineProperty不具备的

4.Object.defineProperty只能遍历对象属性实现监听,而Proxy返回的是一个新对象,我们可以直接操作新的对象达到目的,大大减少了遍历带来的性能开销

5.Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是新标准的性能红利

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值