搜尋
首頁web前端js教程ES6中Proxy的使用說明

ES6中Proxy的使用說明

Jun 13, 2018 am 10:57 AM
es6proxy

這篇文章主要介紹了ES6 Proxy使用場景介紹,現在分享給大家,也給大家做個參考。

ES6 中的箭頭函數、陣列解構、rest 參數等特性一實現就廣為流傳,但類似Proxy 這樣的特性卻很少見到有開發者在使用,一方面在於瀏覽器的相容性,另一方面也在於要發揮這些特性的優勢需要開發者深入理解其使用情境。就我個人而言是非常喜歡 ES6 的 Proxy,因為它讓我們以簡潔易懂的方式控制了外部對物件的存取。在下文中,首先我會介紹 Proxy 的使用方式,然後列舉具體實例解釋 Proxy 的使用情境。

Proxy,見名知意,其功能非常類似於設計模式中的代理模式,該模式常用於三個方面:

  1. 攔截和監視外部對物件的存取

  2. 降低函數或類別的複雜度

  3. #在複雜操作前對操作進行校驗或對所需資源進行管理

在支援Proxy 的瀏覽器環境中,Proxy 是一個全域對象,可以直接使用。 Proxy(target, handler) 是一個建構函數,target 是被代理的對象,handlder 是宣告了各類別代理操作的對象,最後傳回一個代理對象。外界每次透過代理對象存取 target 物件的屬性時,就會經過 handler 對象,從這個流程來看,代理對像很類似 middleware(中介軟體)。那麼 Proxy 可以攔截什麼操作呢?最常見的就是 get(讀取)、set(修改)物件屬性等操作,完整的可攔截操作清單請點這裡。此外,Proxy 物件還提供了一個 revoke 方法,可以隨時註銷所有的代理操作。在我們正式介紹 Proxy 之前,建議你對 Reflect 有一定的了解,它也是一個 ES6 新增的全域對象,詳細資訊請參考MDN Reflect。

Basic

const target = { 
  name: 'Billy Bob',
  age: 15
};

const handler = { 
  get(target, key, proxy) {
    const today = new Date();
    console.log(`GET request made for ${key} at ${today}`);

    return Reflect.get(target, key, proxy);
  }
};

const proxy = new Proxy(target, handler);
proxy.name;
// => "GET request made for name at Thu Jul 21 2016 15:26:20 GMT+0800 (CST)"
// => "Billy Bob"

在上面的程式碼中,我們先定義了一個被代理的目標對象target,然後宣告了包含所有代理操作的handler 對象,接下來使用Proxy(target, handler) 建立代理物件proxy,此後所有使用proxy 對target 屬性的存取都會經過handler 的處理。

1. 抽離校驗模組

讓我們從一個簡單的型別校驗開始做起,這個範例示範如何使用Proxy 保障資料型別的準確性:

let numericDataStore = { 
  count: 0,
  amount: 1234,
  total: 14
};

numericDataStore = new Proxy(numericDataStore, { 
  set(target, key, value, proxy) {
    if (typeof value !== 'number') {
      throw Error("Properties in numericDataStore can only be numbers");
    }
    return Reflect.set(target, key, value, proxy);
  }
});

// 抛出错误,因为 "foo" 不是数值
numericDataStore.count = "foo";

// 赋值成功
numericDataStore.count = 333;

如果要直接為物件的所有屬性開發一個校驗器可能很快就會讓程式碼結構變得臃腫,使用Proxy 則可以將校驗器從核心邏輯分離出來自成一體:

function createValidator(target, validator) { 
  return new Proxy(target, {
    _validator: validator,
    set(target, key, value, proxy) {
      if (target.hasOwnProperty(key)) {
        let validator = this._validator[key];
        if (!!validator(value)) {
          return Reflect.set(target, key, value, proxy);
        } else {
          throw Error(`Cannot set ${key} to ${value}. Invalid.`);
        }
      } else {
        throw Error(`${key} is not a valid property`)
      }
    }
  });
}

const personValidators = { 
  name(val) {
    return typeof val === 'string';
  },
  age(val) {
    return typeof age === 'number' && age > 18;
  }
}
class Person { 
  constructor(name, age) {
    this.name = name;
    this.age = age;
    return createValidator(this, personValidators);
  }
}

const bill = new Person('Bill', 25);

// 以下操作都会报错
bill.name = 0; 
bill.age = 'Bill'; 
bill.age = 15;

透過校驗器和主邏輯的分離,你可以無限擴展personValidators 校驗器的內容,而不會對相關的類別或函數造成直接破壞。更複雜一點,我們也可以使用Proxy 模擬類型檢查,檢查函數是否接收了類型和數量都正確的參數:

let obj = { 
  pickyMethodOne: function(obj, str, num) { /* ... */ },
  pickyMethodTwo: function(num, obj) { /*... */ }
};

const argTypes = { 
  pickyMethodOne: ["object", "string", "number"],
  pickyMethodTwo: ["number", "object"]
};

obj = new Proxy(obj, { 
  get: function(target, key, proxy) {
    var value = target[key];
    return function(...args) {
      var checkArgs = argChecker(key, args, argTypes[key]);
      return Reflect.apply(value, target, args);
    };
  }
});

function argChecker(name, args, checkers) { 
  for (var idx = 0; idx < args.length; idx++) {
    var arg = args[idx];
    var type = checkers[idx];
    if (!arg || typeof arg !== type) {
      console.warn(`You are incorrectly implementing the signature of ${name}. Check param ${idx + 1}`);
    }
  }
}

obj.pickyMethodOne(); 
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 1
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 2
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 3

obj.pickyMethodTwo("wopdopadoo", {}); 
// > You are incorrectly implementing the signature of pickyMethodTwo. Check param 1

// No warnings logged
obj.pickyMethodOne({}, "a little string", 123); 
obj.pickyMethodOne(123, {});

2. 私有屬性

##在JavaScript 或其他語言中,大家會約定俗成地在變數名稱之前加上底線_ 來表示這是一個私有屬性(並不是真正的私有),但我們無法保證真的沒人會去存取或修改它。在下面的程式碼中,我們聲明了一個私有的apiKey,便於api 這個物件內部的方法調用,但不希望從外部也能夠存取api._apiKey:

var api = { 
  _apiKey: &#39;123abc456def&#39;,
  /* mock methods that use this._apiKey */
  getUsers: function(){}, 
  getUser: function(userId){}, 
  setUser: function(userId, config){}
};

// logs &#39;123abc456def&#39;;
console.log("An apiKey we want to keep private", api._apiKey);

// get and mutate _apiKeys as desired
var apiKey = api._apiKey; 
api._apiKey = &#39;987654321&#39;;

很顯然,約定俗成是沒有束縛力的。使用 ES6 Proxy 我們就可以實現真實的私有變數了,以下針對不同的讀取方式示範兩個不同的私有化方法。第一種方法是使用set / get 攔截讀寫請求並返回undefined:

let api = { 
  _apiKey: &#39;123abc456def&#39;,
  getUsers: function(){ }, 
  getUser: function(userId){ }, 
  setUser: function(userId, config){ }
};

const RESTRICTED = [&#39;_apiKey&#39;];
api = new Proxy(api, { 
  get(target, key, proxy) {
    if(RESTRICTED.indexOf(key) > -1) {
      throw Error(`${key} is restricted. Please see api documentation for further info.`);
    }
    return Reflect.get(target, key, proxy);
  },
  set(target, key, value, proxy) {
    if(RESTRICTED.indexOf(key) > -1) {
      throw Error(`${key} is restricted. Please see api documentation for further info.`);
    }
    return Reflect.get(target, key, value, proxy);
  }
});

// 以下操作都会抛出错误
console.log(api._apiKey);
api._apiKey = &#39;987654321&#39;;

第二種方法是使用has 攔截in 操作:

var api = { 
  _apiKey: &#39;123abc456def&#39;,
  getUsers: function(){ }, 
  getUser: function(userId){ }, 
  setUser: function(userId, config){ }
};

const RESTRICTED = [&#39;_apiKey&#39;];
api = new Proxy(api, { 
  has(target, key) {
    return (RESTRICTED.indexOf(key) > -1) ?
      false :
      Reflect.has(target, key);
  }
});

// these log false, and `for in` iterators will ignore _apiKey
console.log("_apiKey" in api);

for (var key in api) { 
  if (api.hasOwnProperty(key) && key === "_apiKey") {
    console.log("This will never be logged because the proxy obscures _apiKey...")
  }
}

3. 存取日誌

對於那些呼叫頻繁、運行緩慢或占用執行環境資源較多的屬性或接口,開發者會希望記錄它們的使用情況或性能表現,這個時候就可以使用Proxy 充當中間件的角色,輕易實現日誌功能:

let api = { 
  _apiKey: &#39;123abc456def&#39;,
  getUsers: function() { /* ... */ },
  getUser: function(userId) { /* ... */ },
  setUser: function(userId, config) { /* ... */ }
};

function logMethodAsync(timestamp, method) { 
  setTimeout(function() {
    console.log(`${timestamp} - Logging ${method} request asynchronously.`);
  }, 0)
}

api = new Proxy(api, { 
  get: function(target, key, proxy) {
    var value = target[key];
    return function(...arguments) {
      logMethodAsync(new Date(), key);
      return Reflect.apply(value, target, arguments);
    };
  }
});

api.getUsers();

4. 預警與攔截

假設你不想讓其他開發者刪除noDelete 屬性,也想讓呼叫oldMethod 的開發者了解到這個方法已經被廢棄了,或者告訴開發者不要修改doNotChange 屬性,那麼就可以使用Proxy 來實現:

let dataStore = { 
  noDelete: 1235,
  oldMethod: function() {/*...*/ },
  doNotChange: "tried and true"
};

const NODELETE = [&#39;noDelete&#39;]; 
const NOCHANGE = [&#39;doNotChange&#39;];
const DEPRECATED = [&#39;oldMethod&#39;]; 

dataStore = new Proxy(dataStore, { 
  set(target, key, value, proxy) {
    if (NOCHANGE.includes(key)) {
      throw Error(`Error! ${key} is immutable.`);
    }
    return Reflect.set(target, key, value, proxy);
  },
  deleteProperty(target, key) {
    if (NODELETE.includes(key)) {
      throw Error(`Error! ${key} cannot be deleted.`);
    }
    return Reflect.deleteProperty(target, key);

  },
  get(target, key, proxy) {
    if (DEPRECATED.includes(key)) {
      console.warn(`Warning! ${key} is deprecated.`);
    }
    var val = target[key];

    return typeof val === &#39;function&#39; ?
      function(...args) {
        Reflect.apply(target[key], target, args);
      } :
      val;
  }
});

// these will throw errors or log warnings, respectively
dataStore.doNotChange = "foo"; 
delete dataStore.noDelete; 
dataStore.oldMethod();

5. 過濾操作

#某有些操作會非常佔用資源,例如傳輸大文件,這個時候如果文件已經在分塊發送了,就不需要在對新的請求作出相應(非絕對),這個時候就可以使用Proxy 對當請求進行特徵檢測,並根據特徵過濾出哪些是不需要回應的,哪些是需要回應的。下面的程式碼簡單示範了過濾特徵的方式,並不是完整程式碼,相信大家會理解其中的妙處:

let obj = { 
  getGiantFile: function(fileId) {/*...*/ }
};

obj = new Proxy(obj, { 
  get(target, key, proxy) {
    return function(...args) {
      const id = args[0];
      let isEnroute = checkEnroute(id);
      let isDownloading = checkStatus(id);   
      let cached = getCached(id);

      if (isEnroute || isDownloading) {
        return false;
      }
      if (cached) {
        return cached;
      }
      return Reflect.apply(target[key], target, args);
    }
  }
});

6. 中斷代理

Proxy 支持隨時取消對target 的代理,此操作常用於完全封閉對資料或介面的存取。在下面的範例中,我們使用了 Proxy.revocable 方法建立了可撤銷代理程式的代理物件:

let sensitiveData = { username: &#39;devbryce&#39; };
const {sensitiveData, revokeAccess} = Proxy.revocable(sensitiveData, handler);
function handleSuspectedHack(){ 
  revokeAccess();
}

// logs &#39;devbryce&#39;
console.log(sensitiveData.username);
handleSuspectedHack();
// TypeError: Revoked
console.log(sensitiveData.username);

Decorator

ES7 中實作的 Decorator,相當於設計模式中的裝飾模式。如果簡單地區分 Proxy 和 Decorator 的使用場景,可以概括為:Proxy 的核心作用是控制外界對被代理者內部的訪問,Decorator 的核心作用是增強被裝飾者的功能。只要在它們核心的使用場景上做好區別,那麼像是訪問日誌這樣的功能,雖然本文使用了Proxy 實現,但也可以使用Decorator 實現,開發者可以根據專案的需求、團隊的規範、自己的偏好自由選擇。

上面是我整理給大家的,希望今後對大家有幫助。

相關文章:

透過vue如何引入公共css檔案

在Vue中有關使用ajax方法有哪些?

在vue.js中如何實作資料分發slot

#

以上是ES6中Proxy的使用說明的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

VSCode Windows 64位元 下載

VSCode Windows 64位元 下載

微軟推出的免費、功能強大的一款IDE編輯器

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具