元编程 (Metaprogramming) 是编写操作程序本身的程序的艺术,允许程序通过操作代码结构和行为来自我调整。元编程的核心是增强代码灵活性和动态性,典型的元编程功能包括拦截、修改、生成代码等
文章首发博客,点击查看
扫码关注公众号,查看更多优质文章
引文:引用维基百科元编程的概念:元编程(英语:Metaprogramming),又译元编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的资料,或者在编译时完成部分本应在运行时完成的工作。多数情况下,与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译;编写元程序的语言称之为元语言。被操纵的程序的语言称之为“目标语言”。一门编程语言同时也是自身的元语言的能力称之为“反射”或者“自反”
文章首发博客,点击查看
概念
元编程的概念估计很多人都不知道,看了上面的概念后仍是云里雾里。其实从上文中简单总结元编程的能力有:可以动态生成代码、修改代码,总结下来就是以下两类:
- 生成代码
- 反射代码
到这里可能有些人还不是很懂,确认概念有时很晦涩难懂
生成代码
生成代码 (Code Generation) 是元编程的一项核心能力,指程序在运行时动态创建代码,并将其编译或执行。这种能力使得程序可以自我调整、自定义行为、动态扩展功能,甚至生成适应特定场景的优化代码;生成的代码可以在运行时通过解释器、编译器或虚拟机即时执行,比如说eval、new Function等等:
eval('function createApp() { return "app"; }');
console.log(createApp()); // appconst 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;}
});
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 操作等方式,可以实现动态函数构造、逻辑优化、框架自动化等功能。尽管生成代码提高了开发效率和代码灵活性,但需要在性能、安全性和复杂性之间取得平衡