共计 3022 个字符,预计需要花费 8 分钟才能阅读完成。
什么是元编程?
维基百科:
元编程
(meta programming)是一种编程技术,编写进去的计算机程序可能将其余程序作为数据来解决。意味着能够编写出这样的程序:它可能
读取、生成、剖析或者转换
其它程序,甚至在运行时批改程序本身
(反射)。
元编程中的 元
的概念能够了解为 程序 自身,元编程关注以下的一点或几点:
-
1、运行时批改语言构造,这种景象被称为
反射编程
或反射
;- 自省:代码检视本人;
- 自我批改:代码批改本人;
- 调解 :代码批改
默认的语言行为
而使其余代码受影响;
- 2、生成代码;
一、自省
代码可能自我查看、拜访外部属性,取得代码的底层信息!
// 拜访对象本身属性
var users = {
'Tom': 32,
'Bill': 50,
'Sam': 65
};
Object.keys(users).forEach(name => {const age = users[name];
console.log(`User ${name} is ${age} years old!`);
});
// 输入后果
User Tom is 32 years old!
User Bill is 50 years old!
User Sam is 65 years old!
自省 在平时的业务开发中很常见,这也是元编程技术的一种应用!
二、自我批改
顾名思义,代码能够批改本身属性或者其余底层信息!
let a = 1;
if (a == 1 && a == 2 && a == 3) {console.log("元编程");
}
上述代码在失常状况下是无论如何也无奈满足条件输入,
因为一个值不可能同时满足等于 1、2、3;然而,利用元编程就能够实现:
// 批改本身
let a = {[Symbol.toPrimitive]: ((i) => () => ++i)(0);
}
if (a == 1 && a == 2 && a == 3) {console.log("元编程");
}
// 输入 '元编程'
Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的;
在对象转换为原始值的时候会被调用,初始值为 1,调用一次 +1,就能够满足a == 1 && a == 2 && a == 3
;
上述函数变形为:
let a = {[Symbol.toPrimitive]: (function (i){return function (){return ++i}
})(0)
}
if (a == 1 && a == 2 && a == 3) {console.log("元编程");
}
在开发过程中 自我批改 应该要尽力防止,能够设想:正在应用一个数据的同时又在批改这个数据,后容易造成不可预期的谬误!
三、调解
代码 批改默认的语言行为
而使其余代码受影响,最显著的体现为扭转其它对象的语义!
在元编程中,调解的概念相似于包装、捕捉、拦挡。
Object.defineProperty()
就是典型的 调解 的使用:
var sun = {};
Object.defineProperty(sun, 'rises', {
value: true,
configurable: false,
writable: false,
enumerable: false
});
console.log('sun rises', sun.rises);
sun.rises = false;
console.log('sun rises', sun.rises);
// 输入
sun rises true
sun rises true
下面例子中,新创建了一个一般对象 sun
,之后通过Object.defineProperty
扭转了它的语义:为其定义了一个不可写的 rises
属性。
四、Reflect & Proxy
MDN:
从 ECMAScript 2015 开始,JavaScript 取得了
Proxy
和Reflect
对象的反对,容许你拦挡并定义根本语言操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。借助这两个对象,你能够在 JavaScript 元级别进行编程。
// Proxy 的 handler 和 Reflect 对象 13 个办法
.apply() // 对一个函数进行调用操作, 和 Function.prototype.apply() 性能相似
.construct() // 对构造函数进行 new 操作,相当于执行 new target(...args)
.get() // 获取对象身上某个属性的值,相似于 target[name]。.has() // 判断一个对象是否存在某个属性,和 in 运算符 的性能完全相同
.ownKeys() // 返回一个蕴含所有本身属性(不蕴含继承属性)的数组,相似于 Object.keys()
.set() // 将值调配给属性的函数。返回一个 Boolean,如果更新胜利,则返回 true
.setPrototypeOf() // 设置对象原型的函数. 返回一个 Boolean,如果更新胜利,则返回 true。.defineProperty() // Object.defineProperty() 相似
.deleteProperty() // 作为函数的 delete 操作符,相当于执行 delete target[name]。.getOwnPropertyDescriptor() // 对象中存在该属性,则返回对应的属性描述符,相似于 Object.getOwnPropertyDescriptor()
.getPrototypeOf() // 相似于 Object.getPrototypeOf()。.isExtensible() // 相似于 Object.isExtensible()
.preventExtensions() // 相似于 Object.preventExtensions()。返回一个 Boolean。
1、Reflect
Reflect
是一个内置对象,它提供了可拦挡 JavaScript 操作的办法。
该办法和 Proxy 的 handler
相似,但 Reflect
办法并不是一个函数对象,因而它是不可结构的。
Reflect
的所有属性和办法都是动态的(就像 Math
对象);
以 Reflect.has()
为例,与 in
运算符比照,检测一个对象是否存在特定属性:
Reflect.has(Object, "assign"); // true
"assign" in Object; // true
2、Proxy
在 ECMAScript 6 中引入的 Proxy
对象能够拦挡某些操作并实现自定义行为。
例如获取一个对象上的属性:
let handler = {get: function(target, name){return name in target ? target[name] : 42;
}};
let p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42
Proxy
对象定义了一个指标(这里是一个空对象)和一个实现了 get
劫持的 handler 对象。
代理的对象在获取未定义的属性时不会返回 undefined
,而是返回 42。
五、生成代码
js 中应用元编程技术生成代码最常见的函数 eval()
:函数会将传入的字符串当做 JavaScript 代码进行执行。
let str = "function sayHello(){console.log('hello')}";
eval(str);
sayHello();
// 输入
hello
六、总结
元编程是当你将程序的逻辑转向关注它本身(或者它的运行时环境)时进行的编程,要么为了考察它本人的构造,要么为了批改它。
元编程的次要价值是 扩大语言的一般机制来提供额定的能力
。
参考
- MDN:元编程
- JavaScript 元编程
- 浅谈 es6 中的元编程