Javascript元编程

news/2024/12/22 14:29:41/文章来源:https://www.cnblogs.com/98kk/p/18622109

元编程 (Metaprogramming) 是编写操作程序本身的程序的艺术,允许程序通过操作代码结构和行为来自我调整。元编程的核心是增强代码灵活性和动态性,典型的元编程功能包括拦截、修改、生成代码等

文章首发博客,点击查看

扫码关注公众号,查看更多优质文章
image

引文:引用维基百科元编程的概念:元编程(英语: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!}
}

实际意义

  1. 灵活性
    元编程极大增强了代码的动态性和灵活性。例如:
  • 动态地拦截和修改对象行为
  • 动态生成新的方法和属性
  1. 抽象能力
    元编程允许开发者实现更高层次的抽象。例如,通过 Proxy 构建数据校验、方法拦截等框架功能

  2. 性能优化
    元编程可以用来优化性能,例如延迟求值和缓存

元编程的限制

  • 调试难度:元编程会隐藏代码的真实行为,增加了调试难度
  • 性能开销:频繁使用 Proxy 和动态生成代码可能影响性能
  • 复杂性:元编程可能让代码过于灵活,增加了可读性和维护性的挑战

文章首发博客,点击查看

总结

元编程中的生成代码是一种动态扩展程序能力的核心技术,通过字符串拼接、模板化代码生成或 AST 操作等方式,可以实现动态函数构造、逻辑优化、框架自动化等功能。尽管生成代码提高了开发效率和代码灵活性,但需要在性能、安全性和复杂性之间取得平衡

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/856801.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

23粘性定位-z index-浮动-浮动练习

一、粘性定位 - sticky 另外还有一个定位的值是position:sticky,比起其他定位值更新一些。 sticky是一个大家期待已久的属性; 可以看作是相对定位和固定(绝对)定位的结合体; 它允许被定位的元素表现得像相对定位一样,直到它滚动到某个阈值点; 当达到这个阈值点时,就会变…

iPhone越狱版和免越狱版iMessages群发,iMessages短信,imessages推信群发实现原理

Apple公司全线在mac os与ios两个操作系统上内置了FaceTime与iMessage两个应用。完美替代运营商的短信与电话。并且FaceTime与iMessage的帐号不仅仅与Apple ID 绑定,同时也与使用这Apple ID的手机号码绑定,这样的漏洞自然给无孔不入的群发垃圾信息商们提供了后门。这样iPhone的…

IDEA Spring MVC配置.

参考1 https://blog.csdn.net/qq_74329022/article/details/138326488 示例 省赛 easyspring 配置。

专业数据恢复软件iFindDataRecoveryv9.2.3 绿色便携版

睿共享*关注我 前言 iFinD Data Recovery一款特别实用的数据找回工具,它很厉害,能帮你在SSD硬盘和Windows10系统上找回丢失的数据。而且,它还能深度扫描并恢复各种主流数码相机里的RAW格式照片,速度超快,用起来也很稳定顺畅,就算是新手也能轻松上手使用。 安装环境 [名称…

【Rive】Android与Rive交互

1 Android与Rive交互的常用接口 1.1 RiveAnimationView参数 <app.rive.runtime.kotlin.RiveAnimationViewandroid:id="@+id/rive_view"android:layout_width="match_parent"android:layout_height="match_parent"android:adjustViewBounds=&q…

【Rive】混合动画

1 混合动画简介 ​ 【Rive】动画 中介绍了 Rive 中动画的基础概念和一般动画的制作流程,本文将介绍混合动画的基础概念和一般制作流程。Unity 中混合动画介绍详见→ 【Unity3D】动画混合。 ​ 混合动画是指同一时刻多个动画按照一定比例同时执行,这些动画控制的动画参数…

Command-line Environment

Command-line Environment 任务控制 shell会使用UNIX提供的信号机制去执行进程间的通信,进程收到信号的时候,会基于信号改变其执行 停止 停止任务:^c 信号:SIGINT在进行find遍历目录时,使用^c,发出SIGINT到该进程,停止了该任务 课程中提供了一个脚本,忽略了SIGINT信号的…

【嵌入式开发】探讨下PC端的BLE开发

目前在嵌入式设备端,实现了不少ble的功能。比如音频传输,图片传输。一般要方便演示,需要开发个对应的手机app。但是我又是很偷懒的人。想着,python这个工具这么强大,在PC端可以用python几行代码就实现掉吧? 说干就干! 根据同事的推荐,用了python的bleak蓝牙库。 我是在…

Jmeter 修改Sampler result 结果信息

首先说一下,jmeter的Sampler result是什么? Jmeter 的Samplers result 是jmeter在向服务器发送请求后,接收到服务器响应的基本信息的展示,如 sample 的开始请求时间、发送的内容大小、基于协议的响应状态码和响应消息等信息。 什么是基于协议的响应状态码和响应消息。比如h…

Shell Script

Shell Script 赋值操作 foo=bar echo $foo注意: 不要使用空格分开shell 将会把foo当作一个程序 转义 Bash通过使用和""来定义字符串 ""会将字符串中的变量转义 echo "String is $foo"会将字符串中的变量原样输出 echo String is $foo函数 函数内…

聊一聊 C#前台线程 如何阻塞程序退出

一:背景 1. 讲故事 这篇文章起源于我的 C#内功修炼训练营里的一位朋友提的问题:后台线程的内部是如何运转的 ? ,犹记得C# Via CLR这本书中 Jeffery 就聊到了他曾经给别人解决一个程序无法退出的bug,最后发现是有一个 Backgrond=false 的线程导致的。恰巧在我分析的350+dum…

聊一聊 C#后台线程 如何阻塞程序退出

一:背景 1. 讲故事 这篇文章起源于我的 C#内功修炼训练营里的一位朋友提的问题:后台线程的内部是如何运转的 ? ,犹记得C# Via CLR这本书中 Jeffery 就聊到了他曾经给别人解决一个程序无法退出的bug,最后发现是有一个 Backgrond=false 的线程导致的。恰巧在我分析的350+dum…