元编程 (Metaprogramming) 是编写操作程序本身的程序的艺术,允许程序通过操作代码结构和行为来自我调整。元编程的核心是增强代码灵活性和动态性,典型的元编程功能包括拦截、修改、生成代码等
文章首发博客,点击查看
扫码关注公众号,查看更多优质文章
引文:引用维基百科元编程的概念:元编程(英语: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;
}
});
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 操作等方式,可以实现动态函数构造、逻辑优化、框架自动化等功能。尽管生成代码提高了开发效率和代码灵活性,但需要在性能、安全性和复杂性之间取得平衡
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。