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

文章首发博客,点击查看

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

引文:引用维基百科元编程的概念:元编程(英语:Metaprogramming),又译元编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的资料,或者在编译时完成部分本应在运行时完成的工作。多数情况下,与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译;编写元程序的语言称之为元语言。被操纵的程序的语言称之为“目标语言”。一门编程语言同时也是自身的元语言的能力称之为“反射”或者“自反”

文章首发博客,点击查看

概念

元编程的概念估计很多人都不知道,看了上面的概念后仍是云里雾里。其实从上文中简单总结元编程的能力有:可以动态生成代码、修改代码,总结下来就是以下两类:

  • 生成代码
  • 反射代码

到这里可能有些人还不是很懂,确认概念有时很晦涩难懂

生成代码

生成代码 (Code Generation) 是元编程的一项核心能力,指程序在运行时动态创建代码,并将其编译或执行。这种能力使得程序可以自我调整、自定义行为、动态扩展功能,甚至生成适应特定场景的优化代码;生成的代码可以在运行时通过解释器、编译器或虚拟机即时执行,比如说evalnew 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]]hasin 操作符
[[Delete]]deletePropertydelete 操作符
[[Call]]apply函数调用
[[Construct]]constructnew 操作符
[[GetPrototypeOf]]getPrototypeOfObject.getPrototypeOf
[[SetPrototypeOf]]setPrototypeOfObject.setPrototypeOf
[[IsExtensible]]isExtensibleObject.isExtensible
[[PreventExtensions]]preventExtensionsObject.preventExtensions
[[DefineOwnProperty]]definePropertyObject.defineProperty, Object.defineProperties
[[GetOwnProperty]]getOwnPropertyDescriptorObject.getOwnPropertyDescriptor, for..in, Object.keys/values/entries
[[OwnPropertyKeys]]ownKeysObject.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 操作等方式,可以实现动态函数构造、逻辑优化、框架自动化等功能。尽管生成代码提高了开发效率和代码灵活性,但需要在性能、安全性和复杂性之间取得平衡


ihengshuai
70 声望7 粉丝

人生是一场修行


下一篇 »
IP协议