JavaScript_proxy-and-reflect-2.md

๐Ÿก

Javascript Study

๋ชจ๋˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ Deep Dive + Mozilla

๐Ÿ“Œ Proxy & Reflect: part2

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Proxy#%ED%94%84%EB%9D%BC%EC%9D%B4%EB%B9%97_%EC%86%8D%EC%84%B1_%ED%8F%AC%EC%9B%8C%EB%94%A9_%EC%97%86%EC%9D%8C
https://replit.com/@dusunax/javascript#index.js

  • Proxy ์ข€ ๋” ์•Œ์•„๋ณด๊ธฐ

Proxy ์˜ˆ์ œ ์‚ดํŽด๋ณด๊ธฐ-1

๊ธฐ๋ณธ ํ”„๋ก์‹œ

const handler = {
  get(obj, prop) {
    return prop in obj ? obj[prop] : 37; // ํ‚ค๊ฐ€ ์žˆ์œผ๋ฉด ๊ฐ’์„ ๋ฐ˜ํ™˜, ์•„๋‹ˆ๋ฉด 37 ๋ฐ˜ํ™˜
  },
};

const p = new Proxy(
  {
    a: 1,
    b: undefined,
  },
  handler
);
// p.a = 1;
// p.b = undefined;

console.log(p.a, p.b);
//  1, undefined

console.log("c" in p, p.c);
//  false, 37

No-op ํฌ์›Œ๋”ฉ ํ”„๋ก์‹œ

  • ํ”„๋ก์‹œ๊ฐ€ target ๊ฐ์ฒด์—๊ฒŒ ์ž‘์—… ์ „๋‹ฌ
const targetEmpty = {};
const fp = new Proxy(targetEmpty, {});

fp.a = 37;
//  ๋Œ€์ƒ ๊ฐ์ฒด์—๊ฒŒ ์ž‘์—… ์ „๋‹ฌ

console.log(targetEmpty.a); // 37
console.log(fp); // { a: 37 }
console.log(fp.a); // 37

๊ฐ์ฒด ํ”„๋ผ์ด๋น— ์†์„ฑ์— ์ง์ ‘ ์ ‘๊ทผX

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/GlobalObjects/Proxy#ํ”„๋ผ์ด๋น—์†์„ฑํฌ์›Œ๋”ฉ์—†์Œ

  • ํ”„๋ก์‹œ โ‡’ ๋‹ค๋ฅธ ID๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด, ๋ž˜ํ•‘๋œ ๊ฐ์ฒด์™€ ์™ธ๋ถ€ ์‚ฌ์ด์—์„œ ์ž‘๋™
class Secret {
  #secret; // private
  constructor(secret) {
    this.#secret = secret;
  }

  get secret() {
    return this.#secret.replace(/\d+/, "[REDACTED]");
  }
}

const aSecret = new Secret("์ „ํ™”๋ฒˆํ˜ธ 1234567");
console.log(aSecret.secret); // [REDACTED]

// no-op ํฌ์›Œ๋”ฉ ๊ฐ™์ง€๋งŒ...
const proxy = new Proxy(aSecret, {});
console.log(proxy.secret); // TypeError: Cannot read private member #secret from an object whose class did not declare it

image

  • get()์ด ํ˜ธ์ถœ๋  ๋•Œ, this๊ฐ’์ด secret์ด ์•„๋‹ˆ๋ผ proxy์ด๋ฏ€๋กœ #secret์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์—†์Œ
    • secret์„ this๋กœ ์‚ฌ์šฉํ•  ๊ฒƒ

๐Ÿ’ก get trap

get ํŠธ๋žฉ์€ return target[prop]๊ณผ ๊ฐ™์ด ์›๋ž˜ ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Proxy ๊ฐ์ฒด๊ฐ€ ์›๋ž˜ ๊ฐ์ฒด์˜ ํ–‰๋™์„ ๋ณด๊ฐ•ํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉด์„œ๋„, ๊ธฐ์กด์˜ ๊ฐ์ฒด ๋™์ž‘์„ ๊ทธ๋Œ€๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const proxyS = new Proxy(aSecret, {
  get(target, prop) {
    // 'this' == Reflect.get(target, prop, receiver)
    return target[prop];
  },
});
  • ๋ฉ”์†Œ๋“œ์˜ this๊ฐ’ ์›๋ž˜ ๊ฐ์ฒด๋กœ ๋ฆฌ๋””๋ ‰์…˜
class Secret4 {
  #x = 1;
  x() {
    return this.#x;
  }
}

const aSecret4 = new Secret4(); // target
const proxy4 = new Proxy(aSecret4, {
  get(target, prop, receiver) {
    const value = target[prop];
    if (value instanceof Function) {
      // instance๊ฐ€ ํ•จ์ˆ˜์ธ ๊ฒฝ์šฐ
      return function (...args) {
        // ํ•จ์ˆ˜ ๋ž˜ํ•‘
        return value.apply(this === receiver ? target : this, args); // ๊ธฐ์กด ๋™์ž‘์„ ๊ฐ€๋กœ์ฑ”
        // ๋งŒ์•ฝ this๊ฐ€ receiver์™€ ๊ฐ™๋‹ค๋ฉด(this === receiver๊ฐ€ ์ฐธ์ด๋ผ๋ฉด), target ๊ฐ์ฒด๋กœ ์„ค์ •
        // ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํ˜„์žฌ ๊ฐ์ฒด(this)๋กœ ์„ค์ •
      };
    }
    return value;
  },
});
console.log(proxy4.x());
  • ์ผ๋ถ€ js ๊ฐ์ฒด์—๋Š” ์•ก์„ธ์Šค ํ•  ์ˆ˜ ์—†๋Š” ๋‚ด๋ถ€ ์Šฌ๋กฏ์ด ์žˆ์Œ ex) Map์˜ [[MapData]]
  • ๊ฒ€์ฆ: ๊ฐ์ฒด์— ์ „๋‹ฌ๋œ ๊ฐ’ ํ™•์ธ ๊ฐ€๋Šฅ
const validator = {
  set(obj, prop, value) {
    if (prop === "age") {
      // age property์ผ ๋•Œ
      if (!Number.isInteger(value)) {
        // ์ •์ˆ˜ ์•„๋‹˜
        throw new TypeError("The age is not an integer");
      }
      if (value > 200) {
        // ์‚ฌ๋žŒ ๋‚˜์ด ์•„๋‹˜
        throw new RangeError("The age seems invalid");
      }
    }

    // ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๊ธฐ๋ณธ ๋™์ 
    obj[prop] = value;

    // ์„ฑ๊ณต ํ‘œ์‹œ
    return true;
  },
};

const person = new Proxy({}, validator);

person.age = 100;
console.log(person.age); // 100
person.age = "young"; // ์˜ˆ์™ธ ๋ฐœ์ƒ: type error
person.age = 300; // ์˜ˆ์™ธ ๋ฐœ์ƒ: range error

image

์˜ˆ์ œ: ์ƒ์„ฑ์ž ํ™•์žฅํ•˜๊ธฐ, construct(), apply()

  • prototype extend: ํ”„๋กœํ† ํƒ€์ž…์„ extendํ•œ ํด๋ž˜์Šค ์ƒ์„ฑ์ž
    • extend ์–ด๋–ป๊ฒŒ? Proxy๋กœ sup ์ƒ์„ฑ์ž๋ฅผ ๊ฐ์‹ธ์„œ ๋™์ž‘ ์ถ”๊ฐ€
function extend(sup, base) {
  base.prototype = Object.create(sup.prototype);
  base.prototype.constructor = new Proxy(base, {
    construct(target, args) {
      const obj = Object.create(base.prototype);
      this.apply(target, obj, args);
      return obj;
    },
    apply(target, that, args) {
      sup.apply(that, args);
      base.apply(that, args);
    },
  });

  // ํ™•์žฅ๋œ ํด๋ž˜์Šค ์ƒ์„ฑ์ž ํ•จ์ˆ˜ ๋ฐ˜ํ™˜
  return base.prototype.constructor;
}

const Person = function (name) {
  this.name = name;
};

const Boy = extend(Person, function (name, age) {
  this.age = age;
});

Boy.prototype.gender = "M"; // Boy ํด๋ž˜์Šค ์ƒ์„ฑ์ž์˜ ํ”„๋กœํ†  ํƒ€์ž…์— ๊ฐ’ ์ถ”๊ฐ€

const Peter = new Boy("Peter", 13);

// ํ”„๋กœํผํ‹ฐ์™€ ๋ฉ”์„œ๋“œ ์ถœ๋ ฅ
console.log(Person.prototype); // {}
console.log(Boy.prototype); // Person { constructor: [Function (anonymous)], gender: 'M' }
console.log(Boy.prototype.gender); // "M"
console.log(Peter.gender); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13
  • constructor function = to โ‡’ class declaration
// Person ํด๋ž˜์Šค ์ •์˜
const Person = function (name) {
  // ์ต๋ช…ํ•จ์ˆ˜, this.ํ‚ค = ๊ฐ’
  this.name = name;
};

image

  • construct(): new ์—ฐ์‚ฐ์ž์— ๋Œ€ํ•œ ํŠธ๋žฉ (new target์ด ์œ ํšจํ•ด์•ผ ํ•จ, [[Construct]])
new Proxy(target, {
  construct(target, argumentsList, newTarget) {},
  // target: ๋Œ€์ƒ ๊ฐ์ฒด, argumentsList: ์ƒ์„ฑ์ž ์ธ์ˆ˜ ๋ชฉ๋ก, newTarget ์›๋ž˜ ํ˜ธ์ถœ๋œ ์ƒ์„ฑ์ž
});
// ---------------------
// construct()
// ---------------------
function monster1(disposition) {
  this.disposition = disposition;
  console.log("๋‚˜๋Š” target");
}

const handler1 = {
  construct(target, args) {
    console.log(`Creating a ${target.name}`);
    console.log(target);
    // Expected output: "Creating a monster1"

    return new target(...args);
  },
};

// target, handler
const proxy1 = new Proxy(monster1, handler1);
console.log(proxy1 + "hi");

console.log(new proxy1("fierce").disposition);
// Expected output: "fierce"
  • apply(): ํ•จ์ˆ˜ ํ˜ธ์ถœ์— ๋Œ€ํ•œ ํŠธ๋žฉ
var p = new Proxy(target, {
  apply: function (target, thisArg, argumentsList) {},
});
// ---------------------
// apply()
// ---------------------
function multiply(a, b) {
  return a * b;
}

const handler = {
  apply: function (target, thisArg, argumentsList) {
    console.log(`Calculate multiply than convert to rem: ${argumentsList}`);

    return target(argumentsList[0], argumentsList[1]) * 0.0625;
  },
};

const calcRem = new Proxy(multiply, handler);

console.log(multiply(8, 2));
// Expected output: 16
console.log(calcRem(8, 2));
// Expected output: 1

=> Proxy ์˜ˆ์ œ ์‚ดํŽด๋ณด๊ธฐ-2์—์„œ ๊ณ„์†