Javascript元编程
本文首发博客网站,由于图片和格式解析问题,可前往阅读原文
元编程 (Metaprogramming) 是编写操作程序本身的程序的艺术,允许程序通过操作代码结构和行为来自我调整。元编程的核心是增强代码灵活性和动态性,典型的元编程功能包括拦截、修改、生成代码等
:::tip 引文
引用维基百科元编程的概念:元编程(英语:Metaprogramming),又译元编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的资料,或者在编译时完成部分本应在运行时完成的工作。多数情况下,与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译
编写元程序的语言称之为元语言。被操纵的程序的语言称之为“目标语言”。一门编程语言同时也是自身的元语言的能力称之为“反射”或者“自反”
:::
概念
元编程的概念估计很多人都不知道,看了上面的概念后仍是云里雾里。其实从上文中简单总结元编程的能力有:可以动态生成代码、修改代码,总结下来就是以下两类:
- 生成代码
- 反射代码
到这里可能有些人还不是很懂,确认概念有时很晦涩难懂
生成代码
生成代码 (Code Generation) 是元编程的一项核心能力,指程序在运行时动态创建代码,并将其编译或执行。这种能力使得程序可以自我调整、自定义行为、动态扩展功能,甚至生成适应特定场景的优化代码;生成的代码可以在运行时通过解释器、编译器或虚拟机即时执行,比如说eval、new Function等等:
eval('function createApp() { return "app"; }'); console.log(createApp()); // app const sum = new Function('a', 'b', 'return a + b'); console.log(sum(2, 6)); // 8
通过生成代码,可以在运行时针对特定需求优化程序逻辑,减少开发者手动编写重复性代码的负担
案例
以下为多年前写的模板编译代码,其中就是动态生成能力:
function ctor(vessel, ctor_template, data, this_arg) { vessel.innerHTML = typeof ctor_template === "string" ? ctor_template : ctor_template(data, this_arg); return vessel; } function full_ctor(data, _has_clone) { element = ctor(document.createElement("div"), vessel_ctor, data).firstElementChild; (_has_clone === undefined ? has_clone : _has_clone) || (element._tmpl = self._tmpl, self.replaceNodeEx(self = element)); return ctor(element, inner_ctor, data, element); } var build_logic_reg, build_data_reg, build_bround_reg; function build(sc) { var html_block_count = 0; var fun_obj = new Function("$param", "$this", "var r=[];with(typeof $param!=='undefined'?$param:($param={})){" + sc.replace(build_logic_reg, function (res, p1, p2) { html_block_count++; return p1 + "r.push('" + p2.replace(/'/g, "\\'") + "');"; }) .replace(build_data_reg, "r.push($1);") .replace(build_bround_reg, "") + "};" + "return r.join('');" ); return html_block_count === 1 ? sc : fun_obj; } try { if (typeof sc === "string" && this instanceof HTMLElement) { var tmp = this.outerHTML.split(">" + this.innerHTML + "<"); sc = HTMLElement.html2String(tmp[0] + ">" + sc + "<" + tmp[1]); } else if (["string", "function"].indexOf(typeof sc) < 0) { brounds = has_clone, has_clone = data, data = sc; sc = this._tmpl || (~["TEMPLATE", "TEXTAREA"].indexOf(this.tagName) ? HTMLElement.html2String(this.innerHTML) : HTMLElement.html2String(this.outerHTML)); } brounds || (brounds = ["[", "]"]); build_logic_reg = new RegExp("(^|%\\" + brounds[1] + ")(?!\\" + brounds[0] + "%)(.+?)(?=$|\\" + brounds[0] + "%)", "g"); build_data_reg = new RegExp("\\" + brounds[0] + "%=(.+?)%\\" + brounds[1] + "", "g"); build_bround_reg = new RegExp("\\" + brounds[0] + "%|%\\" + brounds[1] + "", "g"); if (typeof sc === "string") { /** * 编译模板字符串 */ var template_str = sc.replace(;/<!--!([\s\S]*?)-->/g, "$1").replace(/<!--[\s\S]*?-->|[\r\t\n]/g, " "); // <(?![\s\S]*?<)[\s\S]+$ var vessel_str = template_str.match(/^.*?<.+?>|<\/[^>]*?>(?!.*?<\/)$/g); //不严格匹配开始结束标签内不能包含 < | > 字符 var inner_str = template_str.slice(vessel_str[0].length, vessel_str[1] && -vessel_str[1].length); if (~["TEMPLATE", "TEXTAREA"].indexOf(self.tagName)) { self._tmpl = full_ctor; } else { self._tmpl = function (data, _has_clone) { _has_clone = _has_clone === undefined ? has_clone : _has_clone; return (_has_clone || typeof vessel_ctor === "function") ? full_ctor(data, _has_clone || typeof vessel_ctor !== "function") : ctor(self, inner_ctor, data, self); }; } } else { this._tmpl = sc; } return data !== undefined ? (self || this)._tmpl(data, has_clone) : (self || this)._tmpl; } catch (e) { console.error(e); return false; }
从上也可以看出动态生成代码难以维护,复杂度很高
反射
反射提供了一种机制,使程序能够动态地访问、检测和操作代码的类型、属性和方法,而不需要提前知道这些具体内容;常见的用途如下:
- 类型检查:在运行时获取对象的类型信息
- 动态方法调用:在运行时调用对象的方法或访问属性
- 代码生成和修改:动态创建类或函数,改变程序结构
- 框架和工具支持:反射在许多框架(如依赖注入、ORM)中用于自动化和动态绑定
访问属性
在es6之前就提供了很多方法来访问底层信息,如:Object.keys
:
const app = { name: 'app', version: '1.0.0', author: 'jay' }; Object.keys(app).forEach(key => { console.log(key, app[key]); })
修改值
通过方法允许修改自身的属性值
const obj = { age: 1, increment: function () { this.age += 1; } } obj.increment();
拦截
Object.defineProperty静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象
let value = null; const user = {}; Object.defineProperty(user, 'grade', { set(v) { if (v > 90) { value = 'A'; } else if (v > 80) { value = 'B'; } else { value = 'C'; } return user; }, get() { return value; } });
:::tip ES6
在es6之前对于元编程可能分散在各种不同的api种,而es6后Proxy 和 Reflect 就是 JavaScript 两种现代元编程工具
:::
Proxy
Proxy 是 ES6 引入的元编程特性,用于创建一个代理对象,对对象的基本操作(如属性访问、赋值、函数调用等)进行拦截和定制
使用Proxy可以很方便的进行数据的拦截验证等等:
const validator = { set(target, property, value) { if (property === "age" && (value < 0 || value > 150)) { throw new Error("Invalid age value."); } target[property] = value; return true; }, }; const person = new Proxy({}, validator); person.age = 30; // OK person.age = -5; // Error: Invalid age value.
Reflect
Reflect 是 ES6 引入的另一个工具,提供一组静态方法,用于操作对象的元行为。Reflect 的方法与 Proxy 的拦截方法一一对应,便于构建透明代理
const obj = { message: "Hello" }; // 使用 Reflect 访问属性 console.log(Reflect.get(obj, "message")); // Hello // 使用 Reflect 设置属性 Reflect.set(obj, "message", "Hi"); console.log(obj.message); // Hi
Proxy 与 Reflect 联合使用
const handler = { get(target, property) { console.log(`Accessing property "${property}"`); return Reflect.get(target, property); }, }; const proxy = new Proxy({ message: "Hello" }, handler); console.log(proxy.message); // Accessing property "message" -> Hello
提供的相关方法
内部方法 | Handler 方法 | 何时触发 |
---|---|---|
[[Get]] |
get |
读取属性 |
[[Set]] |
set |
写入属性 |
[[HasProperty]] |
has |
in 操作符 |
[[Delete]] |
deleteProperty |
delete 操作符 |
[[Call]] |
apply |
函数调用 |
[[Construct]] |
construct |
new 操作符 |
[[GetPrototypeOf]] |
getPrototypeOf |
Object.getPrototypeOf |
[[SetPrototypeOf]] |
setPrototypeOf |
Object.setPrototypeOf |
[[IsExtensible]] |
isExtensible |
Object.isExtensible |
[[PreventExtensions]] |
preventExtensions |
Object.preventExtensions |
[[DefineOwnProperty]] |
defineProperty |
Object.defineProperty, Object.defineProperties |
[[GetOwnProperty]] |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor, for..in , Object.keys/values/entries |
[[OwnPropertyKeys]] |
ownKeys |
Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in , Object.keys/values/entries |
其他语言
Java 是静态类型语言,反射机制非常重要,用于在运行时检查类、方法和字段
来看段Java中的反射:
class Person { public void sayHello() { System.out.println("Hello, World!"); } } public class ReflectionExample { public static void main(String[] args) throws Exception { Class<?> clazz = Person.class; Object obj = clazz.getDeclaredConstructor().newInstance(); Method method = clazz.getMethod("sayHello"); method.invoke(obj); // 输出: Hello, World! } }
实际意义
- 灵活性
元编程极大增强了代码的动态性和灵活性。例如:
- 动态地拦截和修改对象行为
- 动态生成新的方法和属性
-
抽象能力
元编程允许开发者实现更高层次的抽象。例如,通过 Proxy 构建数据校验、方法拦截等框架功能 -
性能优化
元编程可以用来优化性能,例如延迟求值和缓存
元编程的限制
- 调试难度:元编程会隐藏代码的真实行为,增加了调试难度
- 性能开销:频繁使用 Proxy 和动态生成代码可能影响性能
- 复杂性:元编程可能让代码过于灵活,增加了可读性和维护性的挑战
总结
元编程中的生成代码是一种动态扩展程序能力的核心技术,通过字符串拼接、模板化代码生成或 AST 操作等方式,可以实现动态函数构造、逻辑优化、框架自动化等功能。尽管生成代码提高了开发效率和代码灵活性,但需要在性能、安全性和复杂性之间取得平衡
本文首发博客网站,由于图片和格式解析问题,可前往阅读原文
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)