查看全部 13 个 JavaScript 代理陷阱

代理程序是一个非常酷的JavaScript功能. 如果你喜欢元程式编程,你可能已经熟悉了它们. 在本文中,我们不会进入编程设计模式或获得元程式,甚至理解代理程序是如何工作的。

通常有关陷阱的文章总是有相同的例子来设置私人属性与代理. 这是一个很好的例子. 然而,在这里,我们将看看你可以使用的所有陷阱. 这些例子不是真正的使用案例,目标是帮助你了解代理陷阱是如何工作的。

什么是陷阱?它听起来已经是可怕的

我到处都读到这个词来自操作系统领域(即使Brendan Eich在JSConfEU 2010中提到它)。

陷阱是内部方法检测工具. 每次你与一个对象交互时,你都会调用 一个必不可少的内部方法

当你跑步时:

1const profile = {};
2profile.firstName = 'Jack';

您正在告诉您的 JavaScript 引擎调用 [[SET]]内部方法,因此设置陷阱将调用一个函数执行,然后将profile.firstName设置为Jack

1const kickOutJacksHandler = {
2  set: function (target, prop, val) {
3    if (prop === 'firstName' && val === 'Jack') {
4      return false;
5    }
6    target[prop] = val;
7    return true;
8  }
9}

在这里,我们的设置陷阱将拒绝任何试图创建名为Jack的个人资料的程序。

1const noJackProfile  = new Proxy ({}, kickOutJacksHandler);
2noJackProfile.firstName = 'Charles';
3// console will show {} 'firstName' 'Charles'
4// noJackProfile.firstName === 'Charles'
5//This won't work because we don't allow firstName to equal Jack
6
7newProfileProxy.firstName = 'Jack';
8// console will show {firstName: 'Charles'} 'firstName' 'Charles'
9// noJackProfile.firstName === 'Charles'

我可以做什么Proxy?

任何令人满意的事情:

1typeof MyThing === 'object'

这意味着数组、函数、对象甚至...

1console.log(typeof new Proxy({},{}) === 'object')
2// logs 'TRUE' well actually just true... I got a bit excited...

代理!如果您的浏览器不支持它,您只不能代理任何东西,因为没有完全功能的多功能或转载选项(更多在另一个帖子中)。

所有 Proxy 陷阱

在JavaScript中有13个陷阱!我选择不分类它们,我将从我认为最有用的到最不有用的(类型)列出它们。

在我们开始之前,这是一个小小的骗局表 从ECMAScript规格中获取:

Internal MethodHandler Method
[[Get]]get
[[Delete]]deleteProperty
[[OwnPropertyKeys]]ownKeys
[[HasProperty]]has
[[Call]]apply
[[DefineOwnProperty]]defineProperty
[[GetPrototypeOf]]getPrototypeOf
[[SetPrototypeOf]]setPrototypeOf
[[IsExtensible]]isExtensible
[[PreventExtensions]]preventExtensions
[[GetOwnProperty]]getOwnPropertyDescriptor
[[Enumerate]]enumerate
[[Construct]]construct

获取、设置和删除:超级基本

我们已经看到设置,让我们看看获取删除。侧注:当您使用设置删除时,您必须返回真实,以告诉JavaScript引擎是否应该更改密钥。

 1const logger = []
 2
 3const loggerHandler = {
 4  get: function (target, prop) {
 5    logger.push(`Someone accessed '${prop}' on object ${target.name} at ${new Date()}`);
 6    return target[prop] || target.getItem(prop) || undefined;
 7  },
 8}
 9
10const secretProtectorHandler = {
11  deleteProperty: function (target, prop) {
12    // If the key we try to delete contains to substring 'secret' we don't allow the user to delete it
13    if (prop.includes('secret')){
14      return false;
15    }
16    return true;
17  }
18};
19
20const sensitiveDataProxy = new Proxy (
21  {name:'Secret JS Object', secretOne: 'I like weird JavaScript Patterns'},
22  {...loggerHandler, ...secretProtectorHandler}
23);
24
25const {secretOne} = sensitiveDataProxy;
26//logger = ['Someone tried to accessed 'secretOne' on object Secret JS Object at Mon Dec 09 2019 23:18:54 GMT+0900 (Japan Standard Time)']
27
28delete sensitiveDataProxy.secretOne;
29// returns false it can't be deleted!
30
31// sensitiveDataProxy equals  {name: 'Secret JS Object', secretOne: 'I like weird JavaScript Patterns'}

与钥匙玩耍

假設我們有一個網頁伺服器,將一些應用程式資料傳送到我們的路線上,我們想要將這些資料儲存在我們的控制器中,但也許我們想要確保它不會被濫用。

 1const createProxiedParameters  = (reqBody, allowed) => {
 2  return new Proxy (reqBody, {
 3    ownKeys: function (target) {
 4      return Object.keys(target).filter(key => allowed.includes(key))
 5    }
 6  });
 7};
 8
 9const allowedKeys = ['firstName', 'lastName', 'password'];
10
11const reqBody = {lastName:'Misteli', firstName:'Jack', password:'pwd', nefariousCode:'MWUHAHAHAHA'};
12
13const proxiedParameters = createProxiedParameters(reqBody, allowedKeys);
14
15const parametersKeys =  Object.keys(proxiedParameters)
16// parametersKeys equals ["lastName", "firstName", "password"]
17const parametersValues = parametersKeys.map(key => reqBody[key]);
18// parameterValues equals ['Misteli', 'Jack', 'pwd']
19
20for (let key in proxiedParameters) {
21  console.log(key, proxiedParameters[key]);
22}
23// logs:
24// lastName Misteli
25// firstName Jack
26// password pwd
27
28// The trap will also work with these functions
29Object.getOwnPropertyNames(proxiedParameters);
30// returns ['lastName', 'firstName', 'password']
31Object.getOwnPropertySymbols(proxiedParameters);
32// returns []

在一个真正的应用程序中,你不应该这样清理你的参数,但是,你可以建立一个基于代理的更复杂的系统。

在 Arrays 中超载

你是否一直梦想使用操作器与数组,但总是太害羞地问如何?

 1function createInArray(arr) {
 2  return new Proxy(arr, {
 3    has: function (target, prop) {
 4      return target.includes(prop);
 5    }
 6  });
 7};
 8
 9const myCoolArray  =  createInArray(['cool', 'stuff']);
10console.log('cool' in myCoolArray);
11// logs true
12console.log('not cool' in myCoolArray);
13// logs false

ha 陷阱拦截方法,试图通过in 运算符检查对象中是否存在一个属性。

控制函数 调用率与应用

应用用于拦截函数调用,在这里我们将看看一个非常简单的缓存代理。

createCachedFunction采用一个func参数。cachedFunction有一个apply陷阱,每次我们运行cachedFunction(arg)时都会被称为陷阱。我们的处理器也有一个cache属性,存储用于调用函数和函数的结果的参数。在[Call]]/apply陷阱中,我们检查函数是否已经被调用了该参数。

这不是一个完整的解决方案. 有很多陷阱. 我试图使其简短,以便更容易理解. 我们的假设是函数输入和输出是单个数字或字符串,并且代理函数总是返回给定的输入相同的输出。

 1const createCachedFunction = (func) => {
 2  const handler = {
 3    // cache where we store the arguments we already called and their result
 4    cache : {},
 5    // applu is the [[Call]] trap
 6    apply: function (target, that, args) {
 7      // we are assuming the function only takes one argument
 8      const argument = args[0];
 9      // we check if the function was already called with this argument
10      if (this.cache.hasOwnProperty(argument)) {
11        console.log('function already called with this argument!');
12        return this.cache[argument];
13      }
14      // if the function was never called we call it and store the result in our cache
15      this.cache[argument] = target(...args);
16      return this.cache[argument];
17    }
18  }
19  return new Proxy(func, handler);
20};
21
22// awesomeSlowFunction returns an awesome version of your argument
23// awesomeSlowFunction resolves after 3 seconds
24const awesomeSlowFunction = (arg) => {
25  const promise = new Promise(function(resolve, reject) {
26    window.setTimeout(()=>{
27      console.log('Slow function called');
28      resolve('awesome ' + arg);
29      }, 3000);
30    });
31  return promise;
32};
33
34const cachedFunction = createCachedFunction(awesomeSlowFunction);
35
36const main = async () => {
37  const awesomeCode = await cachedFunction('code');
38  console.log('awesomeCode value is: ' + awesomeCode);
39  // After 3 seconds (the time for setTimeOut to resolve) the output will be :
40  // Slow function called
41  //  awesomeCode value is: awesome code
42
43  const awesomeYou = await cachedFunction('you');
44  console.log('awesomeYou value is: ' + awesomeYou);
45    // After 6 seconds (the time for setTimeOut to resolve) the output will be :
46  // Slow function called
47  //  awesomeYou value is: awesome you
48
49  // We are calling cached function with the same argument
50  const awesomeCode2 = await cachedFunction('code');
51  console.log('awesomeCode2 value is: ' + awesomeCode2);
52  // IMMEDIATELY after awesomeYou resolves the output will be:
53  // function already called with this argument!
54  // awesomeCode2 value is: awesome code
55}
56
57main()

如果您不理解代码,请尝试将其复制/传输到您的开发者控制台并添加一些console.log()或尝试您自己的延迟函数。

定义财产

「defineProperty」實際上與「設定」相似,每次被稱為「Object.defineProperty」時,也會被稱為「defineProperty」,但當你嘗試使用「=」來設定屬性時,你會得到一些額外的細節性,加上一個額外的「描述者」論點。在這裡,我們會用「defineProperty」作為驗證器。我們會檢查新屬性是否無法寫入或列出。

 1const handler = {
 2  defineProperty: function (target, prop, descriptor) {
 3    // For some reason we don't accept enumerable or writeable properties 
 4    console.log(typeof descriptor.value)
 5    const {enumerable, writable} = descriptor
 6    if (enumerable === true || writable === true)
 7      return false;
 8    // Checking if age is a number
 9    if (prop === 'age' && typeof descriptor.value != 'number') {
10      return false
11    }
12    return Object.defineProperty(target, prop, descriptor);
13  }
14};
15
16const profile = {name: 'bob', friends:['Al']};
17const profileProxied = new Proxy(profile, handler);
18profileProxied.age = 30;
19// Age is enumerable so profileProxied still equals  {name: 'bob', friends:['Al']};
20
21Object.defineProperty(profileProxied, 'age', {value: 23, enumerable: false, writable: false})
22//We set enumerable to false so profile.age === 23

建设

应用调用是两个函数陷阱。 构建拦截了操作员. 我发现(MDN的例子)(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)在函数构建器扩展上真的很酷,所以我会分享我的简化版本。

 1const extend = (superClass, subClass) => {
 2  const handler = {
 3    construct: function (target, args) {
 4      const newObject = {}
 5      // we populate the new object with the arguments from
 6      superClass.call(newObject, ...args);
 7      subClass.call(newObject, ...args);
 8      return newObject;
 9    },
10  }
11  return new Proxy(subClass, handler);
12}
13
14const Person = function(name) {
15  this.name = name;
16};
17
18const Boy = extend(Person, function(name, age) {
19  this.age = age;
20  this.gender = 'M'
21});
22
23const Peter = new Boy('Peter', 13);
24console.log(Peter.gender);  // 'M'
25console.log(Peter.name); // 'Peter'
26console.log(Peter.age);  // 13

别告诉我该怎么办!

Object.isExtensible检查我们是否可以将属性添加到对象中,而Object.preventExtensions允许我们防止属性被添加。在这个代码片段中,我们创建了一个骗局或交易。

 1function createTrickOrTreatTransaction(limit) {
 2  const extensibilityHandler = {
 3    preventExtensions:  function (target) {
 4      target.full = true;
 5      // this will prevent the user from even changing the existing values
 6      return Object.freeze(target);
 7    },
 8    set:  function (target, prop, val) {
 9      target[prop] = val;
10      const candyTotal = Object.values(target).reduce((a,b) => a + b, 0) - target.limit;
11
12      if (target.limit - candyTotal <= 0) {
13        // if you try to cheat the system and get more that your candy allowance, we clear your bag
14        if (target.limit - candyTotal < 0 )
15          target[prop] = 0;
16        // Target is frozen so we can't add any more properties
17
18        this.preventExtensions(target);
19      }  
20    },
21    isExtensible: function (target) {
22      // Kids can check their candy limit 
23      console.log( Object.values(target).reduce((a,b) => a + b, 0) - target.limit);
24      // But it will drop their allowance by one
25      target.limit -= 1;
26      // This will return the sum of all our keys
27      return Reflect.isExtensible(target);
28    }
29  }
30  return new Proxy ({limit}, extensibilityHandler);
31};
32
33const candyTransaction = createTrickOrTreatTransaction(10);
34
35Object.isExtensible(candyTransaction);
36// console will log 10
37// Now candyTransaction.limit = 9
38
39candyTransaction.chocolate  = 6;
40
41// The candy provider got tired and decided to interrupt the negotiations early
42Object.preventExtensions(candyTransaction);
43// now candyTransaction equals to {limit: 9, chocolate: 6, full: true}
44
45candyTransaction.chocolate = 20;
46//  candyBag equals to {limit: 9, chocolate: 6, full: true}
47// Chocolates did not go change to 20 because we called freeze in the preventExtensions trap
48
49const secondCandyTransaction = createTrickOrTreatTransaction(10);
50
51secondCandyTransaction.reeses = 8;
52secondCandyTransaction.nerds = 30;
53// secondCandyTransaction equals to {limit: 10, reeses: 8, nerds: 0, full: true}
54// This is because we called preventExtensions inside the set function if a kid tries to shove in extra candies
55
56secondCandyTransaction.sourPatch = 30;
57// secondCandyTransaction equals to {limit: 10, reeses: 8, nerds: 0, full: true}

财产描述器

你想看到一些奇怪的东西吗?

 1let candies = new Proxy({}, {
 2  // as seen above ownKeys is called once before we iterate
 3  ownKeys(target) {
 4    console.log('in own keys', target);
 5    return ['reeses', 'nerds', 'sour patch'];
 6  },
 7// on the other end getOwnPropertyDescriptor at every iteration
 8  getOwnPropertyDescriptor(target, prop) { 
 9    console.log('in getOwnPropertyDescriptor', target, prop);
10    return {
11      enumerable: false,
12      configurable: true
13    };
14  }
15});
16
17const candiesObject = Object.keys(candies);
18// console will log:
19// in own keys {}
20// in getOwnPropertyDescriptor {} reeses
21// in getOwnPropertyDescriptor {} nerds
22// in getOwnPropertyDescriptor {} sour patch
23// BUT ! candies == {} and candiesObject == []

這是因為我們將可列數設定為虛假,如果你將可列數設定為「真實」,那麼「candiesObject」將等於「[reeses],「nerds」、「sour patch」」。

原型 Get and Set

不確定什麼時候會有用,甚至不確定什麼時候會有用,但這就是它。在這裡,我們使用 setPrototype 陷阱來檢查我們對象的原型是否被篡改。

 1const createSolidPrototype = (proto) => {
 2  const handler = {
 3    setPrototypeOf: function (target, props) {
 4      target.hasBeenTampered = true;
 5      return false;
 6    },
 7    getPrototypeOf: function () {
 8      console.log('getting prototype')
 9    },
10    getOwnProperty: function() {
11      console.log('called: ' + prop);
12      return { configurable: true, enumerable: true, value: 10 };
13    }
14  };
15};

列举

列表允许我们拦截for...in,但不幸的是,我们无法使用它,因为ECMAScript 2016。

我在Firefox 40上测试了一个脚本,所以你不会说我向你撒谎,当我承诺13个陷阱时。

 1const alphabeticalOrderer = {
 2  enumerate: function (target) {
 3    console.log(target, 'enumerating');
 4    // We are filtering out any key that has a number or capital letter in it and sorting them
 5    return Object.keys(target).filter(key=> !/\d|[A-Z]/.test(key)).sort()[Symbol.iterator]();
 6  }
 7};
 8
 9const languages = {
10  france: 'French',
11  Japan: 'Japanese',
12  '43J': '32jll',
13  alaska: 'American'
14};
15
16const languagesProxy = new Proxy (languages, alphabeticalOrderer);
17
18for (var lang in languagesProxy){
19  console.log(lang);
20}
21// console outputs:
22// Object { france: 'French', japan: 'Japanese', 43J: '32jll', alaska: 'American' } enumerating
23// alaska
24// france
25
26// Usually it would output
27// france
28// Japan
29// 43J
30// alaska

您可能已经注意到,我们不使用反思来简化事情,我们将在另一篇文章中涵盖反思

table { width: 100%; } table.color-names tr th, table.color-names tr td { font-size: 1.2rem; }

table { border-collapse: collapse; border-spacing: 0; background: var(--bg); border: 1px solid var(--gs0); table-layout: auto; margin: 0 auto } table thead { background: var(--bg3) } table thead tr th { padding: .5rem .625rem .625rem; font-size: 1.625rem; font-weight: 700; color: var(--text-color) } table tr td, table tr th { padding: .5625rem .625rem; font-size: 1.5rem; color: var(--text-color); text-align: center } table tr:nth-of-type(even) { background: var(--bg3) } table tbody tr td, table tbody tr th, table thead tr th, table tr td { display: table-cell; line-height: 2.8125rem }

Published At
Categories with 技术
Tagged with
comments powered by Disqus