ES6 에이전트의 즐거움

33005 단어 es6webdevjavascript
저자: Maciej Cieślar✏️Proxy은 JavaScript의 ES6 버전에서 가장 무시되기 쉬운 개념 중 하나입니다.
물론 그것은 일상생활에서 특별히 유용하지는 않지만, 반드시 당신의 미래 어느 때에 유용하게 쓰일 것이다.

기초 지식

Proxy 대상은 기본 작업(예를 들어 속성 찾기, 값 부여, 함수 호출)의 사용자 정의 행위를 정의하는 데 사용됩니다.
에이전트의 가장 기본적인 예는 다음과 같습니다.
const obj = {
 a: 1,
 b: 2,
};

const proxiedObj = new Proxy(obj, {
 get: (target, propertyName) => {
   // get the value from the "original" object
   const value = target[propertyName];

   if (!value && value !== 0) {
     console.warn('Trying to get non-existing property!');

     return 0;
   }

   // return the incremented value
   return value + 1;
 },
 set: (target, key, value) => {
   // decrement each value before saving
   target[key] = value - 1;

   // return true to indicate successful operation
   return true;
 },
});

proxiedObj.a = 5;

console.log(proxiedObj.a); // -> incremented obj.a (5)
console.log(obj.a); // -> 4

console.log(proxiedObj.c); // -> 0, logs the warning (the c property doesn't exist)
프록시 구조 함수를 제공하는 대상에서 각자의 명칭 정의 프로세서를 사용하여 getset 작업의 기본 동작을 차단했습니다.현재, 각 get 작업은 속성의 점차적인 증가를 되돌려주고, set은 이를 목표 대상에 저장하기 전에 이 값을 점차적으로 감소합니다.
에이전트에 있어서 중요한 것은 에이전트를 만들면 대상과 상호작용하는 유일한 방식이어야 한다는 것을 기억하는 것이다.

다양한 유형의 트랩

getset을 제외하고는 많은 함정(대상의 기본 행위를 차단하는 처리 프로그램)이 있지만, 본고에서 우리는 그 중 하나를 사용하지 않을 것이다.그렇긴 하지만, 그들에 대한 글을 더 많이 읽는 것에 흥미가 있다면, 여기는 documentation입니다.

즐겁게 놀다


기왕 우리가 대리가 어떻게 일을 하는지 알게 된 이상, 우리는 그 중에서 약간의 즐거움을 얻게 될 것이다.

물체의 상태를 관찰하다


앞에서 말한 바와 같이 에이전트 차단 조작을 사용하는 것은 매우 쉽다.할당 작업이 있을 때마다 관찰 대상의 상태를 알려 줍니다.
const observe = (object, callback) => {
 return new Proxy(object, {
   set(target, propKey, value) {
     const oldValue = target[propKey];

     target[propKey] = value;

     callback({
       property: propKey,
       newValue: value,
       oldValue,
     });

     return true;
   }
 });
};

const a = observe({ b: 1 }, arg => {
 console.log(arg);
});

a.b = 5; // -> logs from the provided callback: {property: "b", oldValue: 1, newValue: 5}
이것이 바로 우리가 해야 할 일이다. set 프로세서를 터치할 때마다 호출된 리셋이다.callback의 매개 변수로서 우리는 세 가지 속성을 가진 대상을 제공했다. 그것이 바로 속성의 이름, 낡은 값과 새 값을 바꾸는 것이다.callback을 실행하기 전에, 우리는 실제 분배가 발생하도록 목표 대상에 새로운 값을 분배합니다.우리는 작업이 성공했음을 표시하기 위해 true으로 돌아가야 한다.그렇지 않으면 TypeError을 던집니다.
이것은 live example입니다.

컬렉션의 등록 정보 확인 중


생각해 보면 에이전트는 검증을 실현하기에 좋은 곳이다. 에이전트는 데이터 자체와 밀접하게 결합되지 않는다.간단한 검증 에이전트를 실현합시다.
이전 예시와 마찬가지로, 우리는 set 조작을 차단해야 한다.마지막으로, 우리는 다음과 같은 방식으로 데이터 검증을 성명하기를 희망한다.
const personWithValidation = withValidation(person, {
 firstName: [validators.string.isString(), validators.string.longerThan(3)],
 lastName: [validators.string.isString(), validators.string.longerThan(7)],
 age: [validators.number.isNumber(), validators.number.greaterThan(0)]
});
이를 위해 다음과 같이 withValidation 함수를 정의했습니다.
const withValidation = (object, schema) => {
 return new Proxy(object, {
   set: (target, key, value) => {
     const validators = schema[key];

     if (!validators || !validators.length) {
       target[key] = value;

       return true;
     }

     const shouldSet = validators.every(validator => validator(value));

     if (!shouldSet) {
       // or get some custom error
       return false;
     }

     target[key] = value;
     return true;
   }
 });
};
우선, 우리는 현재 분배 중인 속성에 제공된 모델에 validators이 있는지 확인합니다. 만약 없으면 검증할 내용이 없습니다. 우리는 값을 분배하기만 하면 됩니다.
만약 속성에 validators을 정의했다면, 우리는 모든 속성이 값을 부여하기 전에 true으로 되돌아갈 것이라고 단언합니다.만약 그 중 하나의 검증기가 false으로 되돌아오면 전체 set 작업이false로 되돌아와 에이전트가 오류를 발생합니다.
마지막으로 validators 대상을 만드는 것입니다.
const validators = {
 number: {
   greaterThan: expectedValue => {
     return value => {
       return value > expectedValue;
     };
   },
   isNumber: () => {
     return value => {
       return Number(value) === value;
     };
   }
 },
 string: {
   longerThan: expectedLength => {
     return value => {
       return value.length > expectedLength;
     };
   },
   isString: () => {
     return value => {
       return String(value) === value;
     };
   }
 }
};
validators 대상은 검증 유형에 따라 그룹을 나누는 검증 함수를 포함한다.호출할 때의 모든 검증기는 validators.number.greaterThan(0)과 같은 필요한 매개 변수를 받아들여 함수를 되돌려줍니다.검증은 되돌아오는 함수에서 진행됩니다.
우리는 가상 필드나 검증기 내부에서 오류를 내서 어디에 문제가 있는지 표시하는 등 다양한 놀라운 특성을 사용하여 검증을 확장할 수 있지만, 이것은 코드의 가독성을 떨어뜨리고 본고의 범위를 초과할 수 있다.
이것은 live example입니다.

코드 게으름 피우기


마지막 (희망이 가장 재미있는) 예시에서 우리는 모든 조작을 게으르게 하는 에이전트를 만들 수 있다.
이것은 Calculator이라는 매우 간단한 종류로 기본적인 산술 연산을 포함한다.
class Calculator {
 add(a, b) {
   return a + b;
 }

 subtract(a, b) {
   return a - b;
 }

 multiply(a, b) {
   return a * b;
 }

 divide(a, b) {
   return a / b;
 }
}
현재 일반적으로 다음 단계로 실행할 경우
new Calculator().add(1, 5) // -> 6
결과는
코드가 현장에서 실행되다.우리가 원하는 것은 코드가 신호가 실행되기를 기다리는 것이다. 마치 run 방법과 같다.이렇게 하면 작업이 필요할 때 실행되거나 필요하지 않으면 실행되지 않는다.
따라서 다음 코드(6이 아니라)는 Calculator 클래스 자체의 실례를 되돌려줍니다.
lazyCalculator.add(1, 5) // -> Calculator {}
이것은 우리에게 또 다른 좋은 특성을 제공할 것이다. 바로 방법 링크이다.
lazyCalculator.add(1, 5).divide(10, 10).run() // -> 1
이런 방법의 문제는 divide에서 우리는 add의 결과가 무엇인지 모르기 때문에 그것이 좀 쓸모가 없다는 것이다.우리가 매개 변수를 제어하기 때문에, 우리는 이전에 정의한 변수 (예를 들어 $) 를 통해 결과를 사용할 수 있는 방법을 쉽게 제공할 수 있다.
lazyCalculator.add(5, 10).subtract($, 5).multiply($, 10).run(); // -> 100
이곳의 $은 상수 Symbol에 불과하다.실행 과정 중, 우리는 그것을 이전 방법으로 동적으로 바꾸어 반환한 결과이다.
const $ = Symbol('RESULT_ARGUMENT');
이제 우리는 우리가 무엇을 실현하고자 하는지에 대해 공평한 이해를 얻었으니 시작합시다.lazify이라는 함수를 만듭니다.이 함수는 get 동작을 차단하는 에이전트를 만듭니다.
function lazify(instance) {
 const operations = [];

 const proxy = new Proxy(instance, {
   get(target, propKey) {
     const propertyOrMethod = target[propKey];

     if (!propertyOrMethod) {
       throw new Error('No property found.');
     }

     // is not a function
     if (typeof propertyOrMethod !== 'function') {
       return target[propKey];
     }

     return (...args) => {
       operations.push(internalResult => {
         return propertyOrMethod.apply(
           target,
           [...args].map(arg => (arg === $ ? internalResult : arg))
         );
       });

       return proxy;
     };
   }
 });

 return proxy;
}
get 함정 내에서 요청의 속성이 존재하는지 확인합니다.만약 없다면, 우리는 잘못을 하나 던진다.만약 속성이 함수가 아니라면, 우리는 어떠한 조작도 하지 않고 그것을 되돌릴 것이다.
에이전트는 차단 방법으로 호출하는 방법이 없습니다.반대로 그들은 그것들을 두 가지 조작으로 간주한다. get 조작과 함수 호출이다.우리의 get 처리 절차는 반드시 상응하는 행동을 취해야 한다.
현재 우리는 속성이 하나의 함수임을 확정하고, 우리는 자신의 함수를 되돌려주고, 그것은 포장기를 충당한다.패키지 함수를 실행할 때, 다른 새로운 함수를 작업 그룹에 추가합니다.패키지 함수는 프록시로 돌아가야 연결할 수 있습니다.
조작 그룹에 제공하는 함수에서, 우리는 포장기에 제공하는 매개 변수를 사용하여 이 방법을 실행합니다.이 함수는result 매개 변수로 호출되어 이전 방법으로 되돌아온 결과로 모든 $을 대체할 수 있습니다.
이런 방식을 통해 우리는 요청할 때까지 실행을 연기할 것이다.
현재 우리는 저장 조작의 밑바닥 메커니즘을 구축했고 운행 함수를 추가하는 방법인 .run() 방법을 필요로 한다.
이것은 상당히 쉽게 할 수 있다.우리가 해야 할 일은 요청된 속성 이름이run과 같은지 확인하는 것이다.만약 그렇다면, 우리는 포장 함수를 되돌려 줄 것이다. (run 충당 방법 때문에)포장기에서 우리는 조작 그룹의 모든 함수를 실행합니다.
마지막 코드는 다음과 같습니다.
const executeOperations = (operations, args) => {
 return operations.reduce((args, method) => {
   return [method(...args)];
 }, args);
};

const $ = Symbol('RESULT_ARGUMENT');

function lazify(instance) {
 const operations = [];

 const proxy = new Proxy(instance, {
   get(target, propKey) {
     const propertyOrMethod = target[propKey];

     if (propKey === 'run') {
       return (...args) => {
         return executeOperations(operations, args)[0];
       };
     }

     if (!propertyOrMethod) {
       throw new Error('No property found.');
     }

     // is not a function
     if (typeof propertyOrMethod !== 'function') {
       return target[propKey];
     }

     return (...args) => {
       operations.push(internalResult => {
         return propertyOrMethod.apply(
           target,
           [...args].map(arg => (arg === $ ? internalResult : arg))
         );
       });

       return proxy;
     };
   }
 });

 return proxy;
}
executeOperations 함수는 함수 그룹을 받아들여 하나씩 실행하고 이전 함수의 결과를 다음 함수의 호출에 전달합니다.
마지막 예:
const lazyCalculator = lazify(new Calculator());

const a = lazyCalculator
 .add(5, 10)
 .subtract($, 5)
 .multiply($, 10);

console.log(a.run()); // -> 100
만약 더 많은 기능을 추가하는 것에 관심이 있다면, 나는 lazify 함수에 다른 기능 - 비동기적인 실행, 사용자 정의 방법 이름, 그리고 .chain() 방법을 통해 사용자 정의 함수를 추가할 가능성을 추가했습니다.live example에는 lazify 기능의 두 가지 버전이 제공됩니다.

총결산


현재 당신은 에이전트의 역할을 보았습니다. 나는 당신이 자신의 코드 라이브러리에서 그것들의 좋은 용도를 찾을 수 있기를 바랍니다.
에이전트는 여기서 소개한 것보다 더 많은 흥미로운 용도가 있다. 예를 들어 마이너스 색인을 실현하고 대상에 존재하지 않는 모든 속성을 포착하는 것이다.그러나 조심해야 한다. performance이 중요한 요소일 때 대리는 나쁜 선택이다.
편집자: 이 문장에 무슨 문제가 있습니까?정확한 버전 here을 찾을 수 있습니다.

플러그인: 네트워크 어플리케이션용 DVR용 LogRocket


 

 
LogRocket은 프론트 로그 기록 도구로 질문을 다시 재생할 수 있습니다. 마치 브라우저에서 발생한 것처럼.LogRocket은 오류가 발생한 원인을 추측하거나 화면 캡처와 로그 저장을 물어보지 않고 세션을 다시 재생할 수 있도록 합니다.프레임워크가 어떻든지 간에 모든 응용 프로그램과 완벽하게 어울릴 수 있으며, 플러그인은 Redux, Vuex, @ngrx/store의 추가 상하문을 기록합니다.
 
LogRocket은 Redux 작업과 상태를 기록하는 것 외에도 콘솔 로그, JavaScript 오류, 스택 추적, 헤더+본문이 있는 네트워크 요청/응답, 브라우저 메타데이터와 사용자 정의 로그를 기록합니다.또한 DOM을 사용하여 페이지에 HTML과 CSS를 기록하여 가장 복잡한 단일 페이지 응용 프로그램이라도 픽셀 수준의 비디오를 재구성합니다.
 
Try it for free .
게시물 Having fun with ES6 proxiesLogRocket Blog에 먼저 올라왔습니다.

좋은 웹페이지 즐겨찾기