代理程序是一个非常酷的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 Method | Handler 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 }