惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
Google DeepMind News
Google DeepMind News
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
L
LangChain Blog
IT之家
IT之家
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog
Hugging Face - Blog
Hugging Face - Blog
G
Google Developers Blog
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
Recent Announcements
Recent Announcements
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

Java不加糖的Blog

假设, AI 把我代替的那一刻真的到来 | szhshp 的第三边境研究所 小傻瓜都能懂的 AstrBot QQ 机器人集成 MCP 功能实战指南 | szhshp 的第三边境研究所 《智人之上: 从石器时代到 AI 时代的信息网络简史》阅读笔记 | szhshp 的第三边境研究所 iPadOS 26 无法设置空间场景图片壁纸的解决方法 | szhshp 的第三边境研究所 《一路云海》(终) | szhshp 的第三边境研究所 《一路云海》(四): 如何不按套路旅行 | szhshp 的第三边境研究所 《一路云海》(三): In Ya Mellow Tone | szhshp 的第三边境研究所 《一路云海》(二): 关西世博参观纪实 | szhshp 的第三边境研究所 《一路云海》(一): 新的征程 | szhshp 的第三边境研究所 2025 大阪世博会 [ 3 天前-先到先得 ] 阶段 场馆预约必中独家攻略 | szhshp 的第三边境研究所 Hackathon 随想 | szhshp 的第三边境研究所 一杯双皮奶 | szhshp 的第三边境研究所 Armbian + CasaOS + NAS 配置指南 | szhshp 的第三边境研究所 Docker 构建镜像报错: error getting credentials - err: exit status 1, out: `` | szhshp 的第三边境研究所 Disqus RIP! 论过高的维护成本如何治疗固执的坏习惯 | szhshp 的第三边境研究所 炸弹猫桌游变体规则 | szhshp 的第三边境研究所 《小岛经济学》阅读笔记 | szhshp 的第三边境研究所 《金钱心理学》阅读笔记 | szhshp 的第三边境研究所 为知笔记 RIP: 迁移剩余的笔记 | szhshp 的第三边境研究所 2025 博客第十年展望 - 再见我的过去 | szhshp 的第三边境研究所 我在独立游戏里面致敬的作品 | szhshp 的第三边境研究所 《How to make thing faster》阅读笔记 | szhshp 的第三边境研究所 《The Art of Clean Code》阅读笔记 | szhshp 的第三边境研究所 《Clean Architecture: A Craftsman Guide to Software Structure and Design》阅读笔记 | szhshp 的第三边境研究所 《How AI Works》阅读笔记 | szhshp 的第三边境研究所 游戏策划废案 - Project Uranus | szhshp 的第三边境研究所 游戏策划废案 - Project X | szhshp 的第三边境研究所 人生第一款独立游戏开发复盘 | szhshp 的第三边境研究所 Trap of Life | szhshp 的第三边境研究所 wireguard折腾记录
《The Joy of Javascript》- 4 - Meta Programming | szhshp ...
2022-07-07 · via Java不加糖的Blog

Meta

目录

相关文章

一本书里面内容较多, 因此分成了多篇 Post, 可以从此处看到相关文章:

Metaprogramming 意指使用 JS 原生的一些方法实现一些高级的步骤

Symbol

  1. 使用相同的值重新定义 symbol 将会得到两个完全不同的示例 Symbol('a') !== Symbol('a')

  2. Symbol 创建的示例有自己的域 (Local or Global)

  3. 对于 Local Symbol, 只有得到 Symbol 的引用才能使用这个 Symbol

    1. 可以通过 Object.getOwnPropertySymbols 来查看已经定义的 Symbol 但是依然无法访问和修改
    2. Spread Operator 会 copy 对应的 Symbol, 是完全拷贝
    const clone = {...obj}; 
    obj[symFoo] === clone[symFoo]; // true
    1. 可以通过 export 来导出对一个 Symbol 的引用
  4. 对于 Global Symbol 可以在任何位置引用到

Symbol Scope

/* Local */
const symFoo = Symbol('foo');
/* Global */
const symFoo = Symbol.for('foo');  /* 注意使用的方法不同 */
Symbol.keyFor(symFoo);// 'foo'

Symbol for Hidden Property

就因为 Symbol 有这些神奇的功能, 所以可以考虑用来定义一个不可见的方法.

const _count = Symbol("count");
class Counter {
  constructor(count) {
    Object.defineProperty(this, _count, { enumerable: false, writable: true });
    this[_count] = count;
  }
  inc(by = 1) {
    return (this[_count] += by);
  }
  dec(by = 1) {
    return (this[_count] -= by);
  }
}

// Outside this class, there's no way to access the internal count property:
const counter = new Counter(1);
counter._count; // undefined 
counter.count; // undefined
counter[Symbol('count')]; // undefined
counter[Symbol.for("count")]; // undefined

另外, 这边需要注意一下通过一些特定的 api (比如 Reflect.ownKeys and Object.getOwnPropertySymbols ) 还是可以找到这个方法.

利用 Global Symbol 实现方法名的一致性

可以定义一个 Symbol, 使得在使用的时候进行判断其是否存在

export const toJson = (obj) => {
  /* 如果 obj 含有一个特定签名的方法则调用此方法, 否则调用默认方法 */
  return isFunction(obj[Symbol.for("toJson")])
    ? obj[Symbol.for("toJson")]()
    : JSON.stringify(obj);
};

Proxy

  • Proxy 对象用于创建一个对象的代理, 从而实现基本操作的拦截和自定义
  • 可以操作对象的属性查找, 赋值, 枚举, 函数调用等.
const obj = {
  a: 1, 
  b: 2, 
};
const proxy = new Proxy(obj, {
  get: function (target, property) {
    return 3;
  }, 
});

obj.a // 1
proxy.a // 3
proxy.abcdef // 3 全部被代理并改写成 3
proxy // {a:1, b:2} 但是这个地方很神奇的, 依然显示 1 和 2
const traceLogHandler = {
  get(target, key) {
    /* target = credentials */
    console.log( `${(new Date())} [TRACE] Calling: ${key}` );
    return target[key];
  }, 
};

const credentials = {
  username: "@luijar", 
  password: "Som3thingR@ndom", 
  login: () => {
    console.log("Logging in...");
  }, 
};

const credentials$Proxy = new Proxy(credentials, traceLogHandler);

credentials$Proxy.login();

Composing Proxy

const passwordObfuscatorHandler = {
  get(target, key) {
    /* 将密码替换成无意义的字符 */
    if (key === "password" || key === "pwd") {
      return "X".repeat(10);
    }
    return target[key];
  },
  has(target, key) {
    /* 将这个属性从 Proxy 中移除 */
    if (key === "password" || key === "pwd") {
      return false;
    }
    return true;
  },
};

credentials$Proxy.password; // 'XXXXXXXXXX'

/* has 方法移除了这个 key */
'password' in credentials$Proxy;

如果希望有多个 Proxy嵌套那么就可以考虑使用compose方法:

const credentials$Proxy = new Proxy(
  new Proxy(credentials, passwordObfuscatorHandler),
  traceLogHandler
);

转换成

const weave = curry((handler, target) => new Proxy(target, handler));


const tracer = weave(traceLogHandler);  // 使用方法: tracer(target)
const obfuscator = weave(passwordObfuscatorHandler); // 使用方法: obfuscator(target)


const credentials$Proxy = compose(tracer, obfuscator)(credentials);
// 或者可以使用更方便,更简洁的 Pipeline Operator:
const credentials$Proxy = credentials |> obfuscator |> tracer;

Reflect

Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods are the same as those of proxy handlers. Reflect is not a function object, so it’s not constructible.

  • Reflect 是一个内置对象
  • Reflect 可以劫持一些 JS 的操作
  • Reflect 不是一个函数对象, 不能进行构造, 因此不能通过 new 关键字来使用它.
const handler = (...props) => ({
  /* 在这个proxy里面设定set方法 */
  set(target, key) {
    /* 如果我set了一个特定的field */
    if (props.includes(key)) {
      debugger

      /* 调用默认的 set方法 */
      Reflect.set(...arguments);

      /* 使用被代理的obj的内置方法, target.data 当做参数传递进去 */
      const value = Reflect.apply(target.test, target, [target.data]);

      /* 给 target 的 value 设置计算后的值 */
      Reflect.set(target, "value", value);
      return true;
    }
  },
});

const block = {
  test: (v) => v + 1,
};

/* 可以看到这个地方首先创建了一个proxy */
const smartBlock = new Proxy(block, handler("data"));

smartBlock.data = 3; // 如此一来在每一次设置 data 参数的时候就会自动更新 value
smartBlock.value; // 4
smartBlock.data = 4;
smartBlock.value; // 5

Revocable Proxy

  • 这个和原生的 proxy 也没有什么太大的区别, 就多加了一个可以 switch off 的方法
  • 注意无论这个 prox 是开启还是关闭, obj 都无法感知到其存在
const handler = (...props) => ({
  set(target, key) {
    if (props.includes(key)) {
      Reflect.set(...arguments);
      const value = Reflect.apply(target.test, target, [target.data]);
      Reflect.set(target, "value", value);
      return true;
    }
  }, 
});

const block = {
  test: (v) => v + 1, 
};

/* 使用的时候有一些很小的区别, 这里不使用 new 进行构造 */
const smartBlock =  Proxy.revocable(block, handler("data"));

smartBlock.data = 3; // 如此一来在每一次设置 data 参数的时候就会自动更新 value
smartBlock.value; // 4

smartBlock.revoke()

smartBlock.data = 4;
smartBlock.value; // 因为 Proxy 被 revoked, 这里不等于 5 了

Method Decorators

class Counter {
  constructor(count) {
    this[_count] = count;
  }
  inc(by = 0) {
    return (this[_count] += by);
  }
  dec(by = 0) {
    return (this[_count] -= by);
  }
}

const decorator = {
  actions: {
    before: () => {},
    after: () => {},
  },
  methods: [],
};

/* 这个地方是 decrator 一个使用的例子. */
const validation = {
  actions: { before: checkLimit, after: identity },
  methods: ["inc", "dec"],
};

/* 首先这一设计一个简单的validate方法 */
const { isFinite, isInteger } = Number;
const checkLimit = (value = 1) =>
  isFinite(value) && isInteger(value) && value >= 0
    ? value
    : throw new RangeError("Expected a positive number");

/* Proxy 的 get 方法无法直接获得到 proxied method 的实际调用参数, 因此这个地方设计了一个 Higher-order function  */
const decorate = (decorator, obj) =>
  new Proxy(obj, {
    get(target, key) {
      if (!decorator.methods.includes(key)) {
        return Reflect.get(...arguments);
      }
      const methodRef = target[key];
      return (...capturedArgs) => {
        /* 在这里进行 before 调用 */
        const newArgs = decorator.actions?.before.call(target, ...capturedArgs);

        const result = methodRef.call(target, ...[newArgs]);

        /* 在这里进行 after 调用 */
        return decorator.actions?.after.call(target, result);
      };
    },
  });

/* 使用 decorate 这个 higher order function */
const counter$Proxy = decorate(validation, new Counter(3));
counter$Proxy.inc(); // 4 
counter$Proxy.inc(3); // 7 
counter$Proxy.inc(-3); // RangeError, 直接终止 inc 方法的执行

上面这一段 decorator 会产生以下这样的调用逻辑:

Proxy 虽然非常的有用,但是需要注意的是, 因为其调用逻辑很复杂, 可能造成其他开发者在这地方调试很久很久, 所以这类 metaProgramming 使用的时候要慎重.

Summary

  • Metaprogramming is the art of using the programming language itself to influence new behavior or to automate code. It can be implemented statically via symbols or dynamically via code weaving.
  • The Symbol primitive data type is used to create unique, collision-free object properties.
  • Symbols make objects extensible by preventing code from accidentally breaking API contracts or the internal workings of an object.
  • You can use symbols to create static hooks that you can use to alter your code’s behavior with regard to fundamental operations such as looping, primitive conversion, and printing.
  • JavaScript ships with native reflection APIs such as Proxy and Reflect. These APIs allow you to weave code into the runtime representation of objects dynamically without polluting their interface with other concerns.
  • JavaScript’s reflection APIs make it easy to develop method decorators, which allow you to implement cross-cutting behavior and modularize sources of duplication in your code.