首頁  >  文章  >  web前端  >  不會vue沒事,但Proxy一定會!

不會vue沒事,但Proxy一定會!

藏色散人
藏色散人轉載
2023-04-14 17:23:301687瀏覽

在幾個月前就有寫這篇文章的想法了,但是想到這個知識點VueJs 用得比較多,我本身是一個React 開發者,如果讓我選另外一個框架的話我可能更傾向angular,但是最近在學NestJs,發現底層大量使用的Reflect,具體如下:

不會vue沒事,但Proxy一定會!

好了,到了這裡我就是我今天為什麼會寫這篇文章的原因了,本篇文章將會按照Object -> Proxy -> Reflect 的順序進行講解,趕緊學起來吧!

Object

ES5 中,我們可以定義一個物件,並且對其進行操作(添加或查找),如下程式碼所示:

const moment = {
  age: 18,
  address: "西安",
};

moment["hobby"] = "basketball";
moment.hight = 1.59;

console.log(moment.age); // 18

那如果我們要對該物件進行監聽呢,我們希望可以監聽到這個物件中的屬性被設定或取得的過程,我們可以透過屬性描述符中的儲存屬性來做到這個功能。

屬性描述子

ES5 中,所有的屬性都具備了屬性描述符,具體使用如下圖所示:

不會vue沒事,但Proxy一定會!

就像上圖所展示的一樣,這個普通的物件屬性對應的屬性描述子,可不僅僅是一個18,它還包含另外三個特性,它們分別是:

  • writable;
  • enumerable;
  • configurable;

在建立普通戶型時,屬性描述子會使用預設值,我們可以使用Object.defineProperty(...) 來新增一個新屬性或修改一個已有屬性(當該屬性Configurable 的值為

true

時)並對特性進行設定,具體如下程式碼所示:<pre class="brush:php;toolbar:false">const moment = { age: 18, address: &quot;西安&quot;, }; Object.defineProperty(moment, &quot;address&quot;, { value: &quot;肇庆&quot;, writable: true, configurable: true, enumerable: true, }); console.log(moment.address); // 肇庆</pre>此方法接收三個參數,它們分別為: 要定義屬性的物件;

要定義或修改的屬性的名稱或 

Symbol;

要定義或修改的屬性描述符;

不會vue沒事,但Proxy一定會!

Getter和Setter

前面說了這麼多基礎的東西,但是還沒有講解是怎麼接收到屬性的變化的,在這裡,屬性描述符

Object.defineProperty 提供了兩個屬性,它們分別是set

get

,兩個屬性的使用如下所示:

const moment = {
  age: 18,
  address: "西安",
};

Object.keys(moment).forEach((key) => {
  let value = moment[key];
  Object.defineProperty(moment, key, {
    get: function () {
      console.log(`监听到了对象的 ${key} 属性被访问了`);
      return value;
    },

    set: function (params) {
      console.log(`监听到了对象的 ${key} 属性被修改了`);
      value = params;
    },
  });
});

moment.age = 22;
const foo = moment.address;
當我們對moment.age 進行賦值的時候,會呼叫set

屬性上的方法,最終會輸出

監聽到了物件的age 屬性被修改了不會vue沒事,但Proxy一定會!

當我們對

moment.address

進行取值的時候,會呼叫

get

屬性上的方法,最終會輸出監聽到了物件的address 屬性被訪問了

雖然這兩個方法是能做到,但是如果我們想監聽更加豐富的操作的時候是做不到的,例如新增屬性,淡出屬性,使用

Object.defineProperty(. ..)

是做不到的

最終結果如上圖所示,終端機中的輸出是對應上面畫圈圈的程式碼執行。

那麼

Proxy 的出現就很好的解決了這一痛點。 ProxyES6 中,新增了一個Proxy 類別,這個類別從名字就可以看出來,是用來幫助我們創建一個代理的,它是一種由你創建的特殊對象,它封裝另一個普通對像或者說擋在這個普通對象的前面,例如你要修改一個對象,它主要有以下的流程圖: 它就像一個關卡對你的操作進行攔截。

###這個類別的基本使用如下所示:###
const moment = {
  age: 18,
  address: "西安",
};

const proxy = new Proxy(moment, {});

console.log(proxy); // { age: 18, address: &#39;西安&#39; }
###你會發現它回傳的是同一個物件,但是記憶體位址不同,所以透過嚴格相等比較傳回的值也就是## #false###。 ######Peoxy的13種捕獲器#########Peoxy### 總共有13個捕獲器,但是常用的就那麼幾個,這裡不低其全部講解,具體可以查看###MDN官網#########話不多說,直接上程式碼:###
const moment = {
  age: 18,
  address: "西安",
};

function foo(x, y) {
  return x + y;
}

const proxy = new Proxy(moment, {
  has: function (target, prop) {
    console.log(`使用 in 访问了 ${prop} 是否存在于 moment 对象`);
  },
  get: function (target, property, receiver) {
    console.log(`通过读取 moment 对象中的 ${property} 属性`);
  },
  set: function (target, property, value, receiver) {
    console.log(`通过设置 moment 对象中的 ${property} 属性为 ${value}`);
  },
});

const fProxy = new Proxy(foo, {
  apply: function (target, _, params) {
    return target(...params) * 10;
  },
  construct: function (target, argumentsList, newTarget) {
    console.log(target); // [Function: foo]
    console.log(argumentsList); // [ 1, 2 ]
    console.log(newTarget); // [Function: foo]
    return {};
  },
});

"age" in proxy; // 使用 in 访问了 age 是否存在于 moment 对象
proxy.age; // 通过读取 moment 对象中的 age 属性
proxy.address = "肇庆"; // 通过设置 moment 对象中的 address 属性为 肇庆

console.log(foo(1, 2)); // 3
console.log(fProxy(1, 2)); // 30

new fProxy(1, 2);
###在上面的程式碼中,###target === moment### 和## #receiver === proxy### 都回傳###true###,也就是說###target### 為對應要修改的物件,而###receiver### 為###Proxy# ## 或繼承###Proxy### 的物件。 ###

操作 Proxy 的同时会修改 moment 对象。

可取消代理

普通对象总是陷入到目标对象,并且在创建之后不能改变,只要还保持着对这个代理的引用,代理的机制就将维持下去。

但是可能会存在这样的情况,比如你想要创建一个在你想要停止它作为代理时便可被停用的代理,解决的方案是创建可取消代理,具体代码如下所示:

const moment = {
  age: 18,
  address: "西安",
};

const { proxy, revoke } = Proxy.revocable(moment, {
  get: function (target, key, receiver) {
    console.log("get 捕获器");
  },
});

proxy.address;
revoke();
proxy.address;

最终的输出如下图所示:

不會vue沒事,但Proxy一定會!

一旦可取消代理被取消,任何对他的访问都会抛出 TypeError 错误。

Proxy的问题与不足

尽管现在 Proxy 已经做得很好了,但是在某些情况下,代理也不能与现在的 ECMAScript 机制很好地协同。

Proxy中的this

Peoxy 潜在的一个问题来源是 this 值,我们知道方法中的 this 通常执行调用这个方法的对象,具体代码如下所示:

const target = {
  moment() {
    return this === proxy;
  },
};

const proxy = new Proxy(target, {});

console.log(target.moment()); // false
console.log(proxy.moment()); // true

按照正常的理解这是没有问题的调用 Proxy 上的任何方法,而这个方法进而又会调用另一个方法,实际上都会调用 Proxy 内部的方法,这是符合预期的行为,但是,如果目标对象依赖于对象表示,那就可能碰到意料之外的问题。

举个例子:

const wm = new WeakMap();

class User {
  constructor(userId) {
    wm.set(this, userId);
  }

  set id(userId) {
    wm.set(this, userId);
  }
  get id() {
    return wm.get(this);
  }
}

由于这个实现依赖 User 实例的对象标识,在这个实例被代理的情况下就会出现问题:

const user = new User(1);
console.log(user.id); // 1

const proxy = new Proxy(user, {});
console.log(proxy.id); // undefined

这是因为 User 实例一开始使用目标对象作为 WeakMap 的键,代理对象却尝试从自身取得这个实例,要解决这个问题,就需要重新设置代理,把代理 User 实例改为代理 User 类本身,之后再创建代理的实例就会创建代理实例作为 WeakMap 的键了:

const UserProcess = new Proxy(User, {});
const proxy = new UserProcess(1);
console.log(proxy.id); // 1

Proxy与内部槽位

代理与内置引用类型的实例通常可以很好地协同,但有些 ECMAScript 内置类型可能会依赖代理无法控制的机制,结果导致在代理上调用某些方法会出错,具体代码如下所示:

const target = new Date();

const proxy = new Proxy(target, {});

console.log(proxy instanceof Date); // true
proxy.getDate(); // TypeError: this is not a Date object.

在上面的代码中,根据 ECMAScript 规范,Date 类型方法的执行依赖 this 值上的内部曹伟 [[NumberDate]],代理对象上不存在这个内部槽位,而且这个内部槽位的值也不能通过普通的 get()set() 操作访问到,于是代理拦截后本应该转发给目标对象的方法会抛出 TypeRrror 的错误。

这篇文章到此结束,下一篇将会详细讲解 Reflect,敬请期待。

以上是不會vue沒事,但Proxy一定會!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.im。如有侵權,請聯絡admin@php.cn刪除