Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

一文搞懂Vue2和Vue3的Proxy #11

Open
dravenww opened this issue Feb 18, 2021 · 0 comments
Open

一文搞懂Vue2和Vue3的Proxy #11

dravenww opened this issue Feb 18, 2021 · 0 comments

Comments

@dravenww
Copy link
Owner

dravenww commented Feb 18, 2021

问个好

hello,大家好,我是德莱问,又和大家见面了。

当在初六抱怨假期为何如此短暂的时候,已然来到了初七。

预祝大家,初七快乐,开工大吉!!!

广告时间:

正文在此开始。

Vue2的proxy实现

众所周知,Vue2中实现代理的方式是通过数据劫持来实现的,也就是使用的Object.defineProperty;简单举例如下:

var obj = {};
var initValue = 20;

Object.defineProperty(obj, "age", {
  get: function () {
    console.log('get')
    return initValue;
  },
  set: function (value) {
    console.log('set')
    initValue = value;
  }
});

console.log(obj.age);
obj.age = 22;
console.log(obj.age);

如上代码,控制台会输出:

在Vue2中其实就是这么来实现的数据劫持,其中get里面会收集依赖--depend,set里面会触发依赖--notify;vue-defineReactive源码直达

当然还有数组的处理,因为数组是个比较特殊的数据类型,Vue2中对数组的方法进行了重新封装,改变原始数组数据的方法都被重新封装了,如下:push、pop、shift、unshift、splice、sort、reversevue数组重新封装源码直达

关于Vue2中的observe具体是如何实现的,在这里不做过多解读,可以看下这篇文章Vue2源码解读-Observe

Vue2的proxy存在的问题

在Vue2中,即便是对数组进行了重新封装,还是会存在问题,如下:

var list = ['tom', 'jack', 'draven', 'ifil']
// 直接改变数组的长度,Vue2是监听不到的,
list.length = 3;
// 直接改变数组中的某个元素,Vue2中也是监听不到的,
list[2] = 'luckyDraven';

虽然Vue2中对上面代码中对数组的修改方式提供了Vue.$set方法去弥补,但是对于开发人员来说,也是增加了额外的工作量嘛。关于这部分内容Vue2的官方文档也进行了说明。关于数组

Vue3的Proxy

Vue3抛弃了数据劫持,转而使用的是Proxy+Reflect来实现的数据代理。

什么是Proxy

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。Proxy 的构造函数语法为:

const p = new Proxy(target, handler)
  • target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

handler可以包含的方法(也叫捕捉器)如下:

// Object.getPrototypeOf 方法的捕捉器。
handler.getPrototypeOf()

// Object.setPrototypeOf 方法的捕捉器。
handler.setPrototypeOf()

// Object.isExtensible 方法的捕捉器。
handler.isExtensible()

// Object.preventExtensions 方法的捕捉器。
handler.preventExtensions()

// Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.getOwnPropertyDescriptor()

// Object.defineProperty 方法的捕捉器。
handler.defineProperty()

// in 操作符的捕捉器。
handler.has()

// 属性读取操作的捕捉器。
handler.get()

// 属性设置操作的捕捉器。
handler.set()

// delete 操作符的捕捉器。
handler.deleteProperty()

// Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.ownKeys()

// 函数调用操作的捕捉器。
handler.apply()

// new 操作符的捕捉器。
handler.construct()

相关的参数说明如下:

  • target:要使用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为。

举个官方例子:

const handler = {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    }
};

const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37

在上面简单的例子中,当对象中不存在属性名时,默认返回值为 37。上面的代码以此展示了 get handler 的使用场景。详细描述可以移步官网Proxy

什么是Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers 的方法相同。Reflect 不是一个函数对象,因此它是不可构造的。

与大多数全局对象不同Reflect并非一个构造函数,所以不能通过new运算符对其进行调用,或者将Reflect对象作为一个函数来调用。Reflect的所有属性和方法都是静态的(就像Math对象)。

Reflect 对象提供了以下静态方法,这些方法与proxy handler methods的命名相同.

语法:


// 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。
// 和 Function.prototype.apply() 功能类似。
Reflect.apply(target, thisArgument, argumentsList)

// 对构造函数进行 new 操作,相当于执行 new target(...args)。
Reflect.construct(target, argumentsList[, newTarget])

// 和 Object.defineProperty() 类似。如果设置成功就会返回 true
Reflect.defineProperty(target, propertyKey, attributes)

// 作为函数的delete操作符,相当于执行 delete target[name]。
Reflect.deleteProperty(target, propertyKey)

// 获取对象身上某个属性的值,类似于 target[name]。
Reflect.get(target, propertyKey[, receiver])

// 类似于 Object.getOwnPropertyDescriptor()。
// 如果对象中存在该属性,则返回对应的属性描述符,  否则返回 undefined.
Reflect.getOwnPropertyDescriptor(target, propertyKey)

// 类似于 Object.getPrototypeOf()。
Reflect.getPrototypeOf(target)

// 判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。
Reflect.has(target, propertyKey)

// 类似于 Object.isExtensible().
Reflect.isExtensible(target)

// 返回一个包含所有自身属性(不包含继承属性)的数组。
// (类似于 Object.keys(), 但不会受enumerable影响).
Reflect.ownKeys(target)

// 类似于 Object.preventExtensions()。返回一个Boolean。
Reflect.preventExtensions(target)

// 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。
Reflect.set(target, propertyKey, value[, receiver])

// 设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。
Reflect.setPrototypeOf(target, prototype)

举个例子:

  • 检测一个对象是否存在特定属性

    const duck = {
      name: 'Maurice',
      color: 'white',
      greeting: function() {
        console.log(`Quaaaack! My name is ${this.name}`);
      }
    }
    
    Reflect.has(duck, 'color');
    // true
    Reflect.has(duck, 'haircut');
    // false
    
  • 检测一个对象是否存在特定属性

    const duck = {
      name: 'Maurice',
      color: 'white',
      greeting: function() {
        console.log(`Quaaaack! My name is ${this.name}`);
      }
    }
    Reflect.set(duck, 'eyes', 'black'); // returns "true" if successful
    
    console.log(Reflect.ownKeys(duck)); // ["name", "color", "greeting", "eyes"]
    

实现部分

阅读到这里,应该可以看到Proxy中handler部分和Reflect中所支持的静态方法是一一对应的。

Vue3中通过Proxy结合Reflect来彻底代理实现了数据代理。关于源码分析部分可以查看这篇文章Vue3源码解读-createReactiveObject

Vue3中通过不同的api来调用不同的handler实现数据代理。贴一下源码解读中的图吧:

Vue2和Vue3的Proxy对比

  • 通过Vue2中Object.defineProperty是不支持对数组的监听的,只支持对对象的监听;
  • 来看个Vue3中使用Proxy+Reflect的例子
    var list = ["tom", "jack"];
    
    var proxy = new Proxy(list, {
      set: function (target, key, value) {
        Reflect.set(target, key, value);
      }
    });
    
    proxy.length = 3;
    
    console.log(list);  // ["tom", "jack", undefined]
    
    proxy[2] = "draven";
    
    console.log(list); // ["tom", "jack", "draven"]
    

可以看到通过Proxy+Reflect实现了Vue2中length和直接赋值监听不到的问题。

the last

当然了Proxy的优势不止上面数组的部分,还有一些其他的优势,可以去官网进行深度阅读。

大家都喜欢新鲜的东西,其实重要的不是结果,而是探索的过程~

感谢阅读,祝大家开工大吉~

觉得不错的话,点个star再走哇~

@dravenww dravenww reopened this Mar 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant