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

推荐订阅源

宝玉的分享
宝玉的分享
T
Threat Research - Cisco Blogs
H
Hacker News: Front Page
N
News and Events Feed by Topic
Know Your Adversary
Know Your Adversary
Cisco Talos Blog
Cisco Talos Blog
SecWiki News
SecWiki News
C
Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
K
Kaspersky official blog
Forbes - Security
Forbes - Security
Webroot Blog
Webroot Blog
Schneier on Security
Schneier on Security
P
Privacy & Cybersecurity Law Blog
H
Heimdal Security Blog
Y
Y Combinator Blog
The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
V
Vulnerabilities – Threatpost
T
Tenable Blog
T
Tailwind CSS Blog
P
Privacy International News Feed
WordPress大学
WordPress大学
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
博客园 - Franky
Hacker News: Ask HN
Hacker News: Ask HN
Jina AI
Jina AI
C
Cybersecurity and Infrastructure Security Agency CISA
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
Vercel News
Vercel News
A
About on SuperTechFans
爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
AWS News Blog
AWS News Blog
The Last Watchdog
The Last Watchdog
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
Security Archives - TechRepublic
Security Archives - TechRepublic
博客园 - 司徒正美
量子位
博客园 - 三生石上(FineUI控件)
J
Java Code Geeks
Hacker News - Newest:
Hacker News - Newest: "LLM"
Recorded Future
Recorded Future
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Martin Fowler
Martin Fowler
Project Zero
Project Zero

szhshp 的第三边境研究所

假设, 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 的第三边境研究所 如果我用手搓了个暗物质雏形 | szhshp 的第三边境研究所 《Rust Programming Language》- EX - Cheatsheet | szhshp 的第三边境研究所 新的播放器上线了 | szhshp 的第三边境研究所 Cypress - End-to-End Testing: OKTA Authentication with MFA | szhshp 的第三边境研究所 VSC x VIM - 反正多学几个快捷键没有坏处 | szhshp 的第三边境研究所 两小时 Docker 速成 - Getting Started | szhshp 的第三边境研究所 《The Joy of Javascript》- 5 - Data | szhshp 的第三边境研究所 《The Joy of Javascript》- 3 - ADT(Algebraic Data Type) | szhshp 的第三边境研究所 Web-Automation Testing | szhshp 的第三边境研究所 《The Joy of Javascript》- 2 - Functor/Monad | szhshp 的第三边境研究所 《The Joy of Javascript》- 1 - Object/Function | szhshp 的第三边境研究所 EvalError: Possible side-effect in debug-evaluate | szhshp 的第三边境研究所 前端 QA 工具链指南 - husky,commitlint,commitizen,eslint,typescript,express | szhshp 的第三边境研究所 效率工具 & 软件推荐 | szhshp 的第三边境研究所 站点开源 Project Titan | szhshp 的第三边境研究所 cmd/powershell-命令速查-Cheatsheet | szhshp 的第三边境研究所 Git-命令速查与相关问题解决-Cheatsheet & Troubleshooting | szhshp 的第三边境研究所 Tailwind.css not included in NextJS Production | szhshp 的第三边境研究所 Github GraphQL API - Data Integration | szhshp 的第三边境研究所 尝试 Windows Terminal + z.lua | szhshp 的第三边境研究所 StrokePlus.net - Powerful Hotkey Boost | szhshp 的第三边境研究所 优雅地 Hack 一台黑苹果 | szhshp 的第三边境研究所 一杯关东煮 | szhshp 的第三边境研究所 Gitee x PicGo 管理图床 | szhshp 的第三边境研究所 Create Your Own Redux (Typescript) | szhshp 的第三边境研究所 Getting Started: Ionic + Capacitor + React + Typescript + Android | szhshp 的第三边境研究所 GraphQL: File Upload & Troubleshooting | szhshp 的第三边境研究所 吉祥航空随心飞刷票工具循环查票工具 | szhshp 的第三边境研究所 NVM Windows Portable Installation | szhshp 的第三边境研究所 NextJS x Typescript - Integration & Troubleshooting | szhshp 的第三边境研究所 全站点升级 - Project Titan | szhshp 的第三边境研究所 AWS-EC2-常见问题解决-Troubleshooting | szhshp 的第三边境研究所 Workbox-Getting Started | szhshp 的第三边境研究所 《时间简史》阅读笔记 | szhshp 的第三边境研究所 《时间的秩序》阅读笔记 | szhshp 的第三边境研究所 ESLint-常见问题解决-Troubleshooting | szhshp 的第三边境研究所 站点索引 | szhshp 的第三边境研究所 个人知识库搭建 - Hexo 实现 Github/Coding 持续集成 | szhshp 的第三边境研究所 BMOB-常见问题解决-Troubleshooting | szhshp 的第三边境研究所 React + Typescript: 开启 HMR/Hot Loader | szhshp 的第三边境研究所 Yarn & NPM | szhshp 的第三边境研究所 ESLint: Typescript + React + TSX 集成 | szhshp 的第三边境研究所 《10101》EP15:过年 2020 | szhshp 的第三边境研究所 Cmder-Usage | szhshp 的第三边境研究所 Redux-DOM not refreshed | szhshp 的第三边境研究所 React Semantic UI-CheatSheet | szhshp 的第三边境研究所 Typescript: Getting Started | szhshp 的第三边境研究所 Flutter-Getting Started | szhshp 的第三边境研究所 Typescript: Access window object with type check | szhshp 的第三边境研究所 VSC Extension Development-Create A Code Formatter Extension | szhshp 的第三边境研究所 OneNote Extension-Main API | szhshp 的第三边境研究所 TortoiseGit Win10 Icon not showing correctly | szhshp 的第三边境研究所 Mongoose: aggregate() 方法实现聚合函数 | szhshp 的第三边境研究所 React In Depth - React 源码解析 - render() & Fiber | szhshp 的第三边境研究所 MongoDB x Mongoose: 实现类似 Join 的功能 | szhshp 的第三边境研究所 Disqus 评论框改造工程-实现 Disqus 代理的 Serverless 服务 | szhshp 的第三边境研究所 GUI, CLI, Terminal, Shell, TTY, Console, Bash | szhshp 的第三边境研究所 Jest Async Best Practise: No Nested Async | szhshp 的第三边境研究所 关系型数据库与非关系型数据库优缺点分析 | szhshp 的第三边境研究所 New Edge & Chromium Error - Package is invalid: 'CRX_VERSION_NUMBER_INVALID' | szhshp 的第三边境研究所 选择 GraphQL 的 N 个理由 | szhshp 的第三边境研究所 StrokePlus - 鼠标手势神器的代码编写技巧 | szhshp 的第三边境研究所 嗨,好久不见 | szhshp 的第三边境研究所 Vue + ElementUI 集成 Vue Router | szhshp 的第三边境研究所 IE8 - Unknown Runtime Error Using innerHTML & Ajax | szhshp 的第三边境研究所 React: Handling Events | szhshp 的第三边境研究所 React-Navigation Integration with Redux | szhshp 的第三边境研究所 React: States is tricky | szhshp 的第三边境研究所 《10101》EP14:小剧场 | szhshp 的第三边境研究所 ColdFusion - Basic - arguments in cffunction | szhshp 的第三边境研究所 ColdFusion - Basic - cfset | szhshp 的第三边境研究所
《The Joy of Javascript》- 4 - Meta Programming | szhshp 的第三边境研究所
2022-07-07 · via szhshp 的第三边境研究所

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.