---
title: 元式编程
date: 2018-06-09 16:29:00
updated: 2019-01-10 12:00:00
categories:
- web
tags:
- front end
---
目录
# 什么是它
# 如何实现
# 原始示例
# 逐步进阶
# ----》公共代码
# ----》函数注册
# ----》动态注册
# ----》数操分离
# ----》不会报错
# 参考文献
# 同级文章
正文
# 什么是它
“写一段自动写程序的程序”
元式编程就是将代码视作数据,直接用字符串 or AST or 其他任何形式去操纵代码,以此获得一些维护性、效率上的好处。
# 如何实现
Javascript 中,eval
、new Function()
便是两个可以用来进行元式编程的特性。
# 原始示例
class User {
constructor(userID) {
this.id = userID;
}
get_name() {
return $.ajax(`/get_name?id=${this.id}`);
}
get_sex() {
return $.ajax(`/get_sex?id=${this.id}`);
}
//下面是get_age、get_address......
}
代码问题:
①用户数据有多少个字段,我们就要定义多少个 get_something
方法。
②这些方法里逻辑都是重复的,都是一个简单的 ajax。
# 公共代码
class User {
constructor(userID) {
this.id = userID;
}
__fetchData(key) {
//这是一个private方法,直接调用类似__fetchData("age")是不被允许的
return $.ajax(`/get_${key}?id=${this.id}`)
}
get_name() {
return this.__fetchData('name');
}
get_sex() {
return this.__fetchData("sex");
}
//下面是get_age、get_address......
}
可圈可点:
①将公共逻辑进行提取,封装__fetchData
# 函数注册
class User {
constructor(userID) {
this.id = userID;
this.registerProperties(["name", "age", "sex", "address"]);
}
registerProperties(keyArray) {
keyArray.forEach(key => {
this[`get_${key}`] = () => this.__fetchData(key);
})
}
__fetchData(key) {
//这是一个private方法,直接调用类似__fetchData("age")是不被允许的
return $.ajax(`/get_${key}?id=${this.id}`)
}
}
可圈可点:
①将公共逻辑进行提取,封装__fetchData
②使用函数registerProperties
给对象注册键值,避免了有多少个字段,我们就要定义多少个 get_something
方法。
# 动态注册
class User {
constructor(userID) {
this.id = userID;
this.registerProperties(["name", "age", "sex", "address"]);
}
registerProperties(keyArray) {
keyArray.forEach(key => {
//注意这里的fnBody内部依然采用ES5的写法,因为babel目前不会编译函数字符串。
var fnBody = `return this.__fetchData("/get_${key}?id=${this.id}")
.then(function(data){
return this.__handle_${key}?_this.handle_${key}(data):data;
})`;
this[`get_${key}`] = new Function(fnBody);
})
}
__handle_name(name) {
//do somthing with name...
return name;
}
__handle_age(age) {
//do somthing with age...
return age;
}
__fetchData(key) {
//这是一个private方法,直接调用类似__fetchData("age")是不被允许的
return $.ajax(`/get_${key}?id=${this.id}`)
}
}
可圈可点:
①将公共逻辑进行提取,封装__fetchData
②使用函数registerProperties
给对象注册键值,避免了有多少个字段,我们就要定义多少个get_something
方法。
③在拉取数据之后,我们要对部分数据进行一定的处理,比如对 name 我们要去掉首尾的空格,对 age 我们要加上一个“岁”字。具体的处理方法定义在 __handle_something
里面(以某种格式规范定义)。
④通过 new Function()
,动态生成函数。
# 控操分离
class User {
constructor(userID, dataBase) {
this.id = userID;
this.__dataBase = dataBase;
for (var method in dataBase) {
//对每一个方法
this.registerMethod(method);
}
}
registerMethod(methodName) {
//这里除去了前置的"get_"
var propertyName = methodName.slice(4);
//注意这里拉取数据的方法改为使用dataBase
var fnBody = `return this.__dataBase.${methodName}()
.then(function(data){
return this.__handle_${propertyName}?_this.handle_${propertyName}(data):data;
})`;
this[`get_${propertyName}`] = new Function(fnBody);
}
__handle_name(name) {
//do somthing with name...
return name;
}
__handle_age(age) {
//do somthing with age...
return age;
}
}
var userDataBase = new UserDataBase();
var user = new User("123", userDataBase);
可圈可点:
①将公共逻辑进行提取,封装__dataBase
(),并且语义化__fetchData
②使用函数registerMethod()给对象注册方法(registerProperties
键值),避免了有多少个字段,我们就要定义多少个get_something
方法。
③在获取(拉取)数据之后,我们要对部分数据进行一定的处理,比如对 name 我们要去掉首尾的空格,对 age 我们要加上一个“岁”字。具体的处理方法定义在 __handle_something
里面(以某种格式规范定义)。
④通过 new Function()
,动态生成函数。
⑤通过一个别人封装好的 UserDataBase
里的方法来拉取传入的用户数据,促进松耦合。读取所有拉取字段的方法,然后通过元编程的方式来动态生成对应的方法,避免了冗余。
# 不会报错
function createUser(id, userDataBase) {
return new Proxy(new User(id, userDataBase), {
get: (target, property) => (typeof(target[property]) === "function" ? target[property] : () => false)
})
}
var userDataBase = new UserDataBase();
var user = createUser("123", userDataBase);
user.get_name() => // fetch name data
user.get_wwwwww() // => false
①将公共逻辑进行提取,封装__dataBase
(),并且语义化__fetchData
②使用函数registerMethod()给对象注册方法(registerProperties
键值),避免了有多少个字段,我们就要定义多少个get_something
方法。
③在获取(拉取)数据之后,我们要对部分数据进行一定的处理,比如对 name 我们要去掉首尾的空格,对 age 我们要加上一个“岁”字。具体的处理方法定义在 __handle_something
里面(以某种格式规范定义)。
④通过 new Function()
,动态生成函数。
⑤通过一个别人封装好的 UserDataBase
里的方法来拉取传入的用户数据,促进松耦合。读取所有拉取字段的方法,然后通过元编程的方式来动态生成对应的方法,避免了冗余。
⑥用户数据中不存在www
字段,即使这样执行user.get_wwwwww()
也不会报错。
# 参考文献
[王伟嘉].Javascript元编程(一).2016-04-16.segmentfault
# 同级文章
函数编程
对象编程