摘要:中,便是兩個(gè)可以用來(lái)進(jìn)行元編程的特性。這也就是元編程的優(yōu)點(diǎn)之一,程序可以根據(jù)傳入?yún)?shù)對(duì)象的不同,動(dòng)態(tài)地生成對(duì)應(yīng)的程序,從而減少大量冗余的代碼。
首發(fā)于知乎專欄:http://zhuanlan.zhihu.com/starkwang
這幾天把一年多前買(mǎi)的《松本行弘的程序世界》重新看了看,很多當(dāng)時(shí)不能理解的東西現(xiàn)在再去看真是茅塞頓開(kāi)呀,看到元編程那一段真是把我震撼到了,后來(lái)發(fā)現(xiàn) Javascript 里其實(shí)也是有一些支持元編程的特性的,今天就用一個(gè) DEMO 示范一下吧。
什么元編程“元編程”這個(gè)名字看起來(lái)高端大氣上檔次,它的含義也是相當(dāng)高端:“寫(xiě)一段自動(dòng)寫(xiě)程序的程序”,不要誤會(huì),我們做的可不是人工智能。
言簡(jiǎn)意賅地說(shuō),元編程就是將代碼視作數(shù)據(jù),直接用字符串 or AST or 其他任何形式去操縱代碼,以此獲得一些維護(hù)性、效率上的好處。
Javascript 中,eval、new Function()便是兩個(gè)可以用來(lái)進(jìn)行元編程的特性。
原始示例現(xiàn)在我們有一堆用戶的數(shù)據(jù),具體字段有name,sex,age,address等等,通過(guò)類似 /get_name?id=123456 來(lái)拉取數(shù)據(jù)
那么我們很容易寫(xiě)出這樣的代碼:
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...... }
這段代碼的問(wèn)題在哪呢?
首先,用戶數(shù)據(jù)有多少個(gè)字段,我們就要定義多少個(gè) get_something 方法,更可怕的是這些方法里邏輯都是重復(fù)的,都是一個(gè)簡(jiǎn)單的 ajax。
進(jìn)階(一)我們可以把拉取數(shù)據(jù)的邏輯封裝到 __fetchData 里:
class User { constructor(userID) { this.id = userID; } __fetchData(key) { //這是一個(gè)private方法,直接調(diào)用類似__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...... }
然后,冗余的問(wèn)題可以通過(guò)registerProperties來(lái)解決:
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) { //這是一個(gè)private方法,直接調(diào)用類似__fetchData("age")是不被允許的 return $.ajax(`/get_${key}?id=${this.id}`) } }進(jìn)階(三)
到目前為止我們都沒(méi)有涉及到任何元編程的概念,下面我們加上更高的需求:
在拉去數(shù)據(jù)之后,我們要對(duì)部分?jǐn)?shù)據(jù)進(jìn)行一定的處理,比如對(duì) name 我們要去掉首尾的空格,對(duì) age 我們要加上一個(gè) 歲 字。具體的處理方法定義在 __handle_something 里面。
這里我們便可以通過(guò) new Function() 來(lái)動(dòng)態(tài)生成函數(shù),元編程開(kāi)始顯現(xiàn)威力:
class User { constructor(userID) { this.id = userID; this.registerProperties(["name", "age", "sex", "address"]); } registerProperties(keyArray) { keyArray.forEach(key => { //注意這里的fnBody內(nèi)部依然采用ES5的寫(xiě)法,因?yàn)閎abel目前不會(huì)編譯函數(shù)字符串。 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) { //這是一個(gè)private方法,直接調(diào)用類似__fetchData("age")是不被允許的 return $.ajax(`/get_${key}?id=${this.id}`) } }進(jìn)階(四)
下面我們讓需求更加{{BANNED}}一點(diǎn):
數(shù)據(jù)并非通過(guò) ajax 直接拉取,而是通過(guò)一個(gè)別人封裝好的 UserDataBase 里的方法來(lái)拉取;
數(shù)據(jù)的字段并非只有name,sex,age,address四個(gè),而是要根據(jù) UserDataBase 里給你的方法決定。給你1000個(gè)get不同字段的方法,User類里也要有對(duì)應(yīng)的1000個(gè)方法。
class UserDataBase { constructor() {} get_name(id) {} get_age(id) {} get_address(id) {} get_sex(id) {} get_anything_else1(id) {} get_anything_else2(id) {} get_anything_else3(id) {} get_anything_else4(id) {} //...... }
這里我們就需要用到 JS 的反射機(jī)制來(lái)讀取所有拉取字段的方法,然后通過(guò)元編程的方式來(lái)動(dòng)態(tài)生成對(duì)應(yīng)的方法。
class User { constructor(userID, dataBase) { this.id = userID; this.__dataBase = dataBase; for (var method in dataBase) { //對(duì)每一個(gè)方法 this.registerMethod(method); } } registerMethod(methodName) { //這里除去了前置的"get_" var propertyName = methodName.slice(4); //注意這里拉取數(shù)據(jù)的方法改為使用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);
這樣即使用戶數(shù)據(jù)有一萬(wàn)種不同的屬性字段,只要保證 UserDataBase 中良好地定義了對(duì)應(yīng)的拉取方法,我們的 User 就能自動(dòng)生成對(duì)應(yīng)的方法。
這也就是元編程的優(yōu)點(diǎn)之一,程序可以根據(jù)傳入?yún)?shù)/對(duì)象的不同,動(dòng)態(tài)地生成對(duì)應(yīng)的程序,從而減少大量冗余的代碼。
進(jìn)階(五)現(xiàn)在程序里還有點(diǎn)小瑕疵:
//用戶數(shù)據(jù)中不存在www字段,若這樣執(zhí)行會(huì)報(bào)錯(cuò): user.get_www(); //user.get_www is not a function
現(xiàn)在我們要保證像上面那樣執(zhí)行任意的 user.get_xxxx() ,程序不會(huì)報(bào)錯(cuò),而是返回 false:
//用戶數(shù)據(jù)中不存在www字段: user.get_www(); // => false
Javascript 里缺少了 Ruby 中 method_missing 這樣黑科技的內(nèi)核方法,但是我們可以通過(guò) ES6 的 Proxy 特性來(lái)模擬:
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總結(jié)
其實(shí)這里的 DEMO 只是元編程的一個(gè)小應(yīng)用,下一篇文章里我們會(huì)通過(guò)元編程實(shí)現(xiàn)一個(gè)簡(jiǎn)單的表單驗(yàn)證 DSL :
//類似 form.name["is not empty"]["length is between",1,20] // => true or false參考
來(lái)來(lái)來(lái),咱么元編程入個(gè)門(mén)
元編程之javascript
JavaScript 元編程之ES6 Proxy
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/87748.html
摘要:在這里講到的很多也許只和程序?qū)τ诠ぷ鳈C(jī)制的操作有關(guān),但是作為初探也許也就足夠了。一般情況下還有空字符串都會(huì)被判斷成。面向特征編程面向特征編程的全稱是。 引子 元編程會(huì)有如下的定義: 一種計(jì)算機(jī)程序的編寫(xiě)方式,它可以將其它程序(或者其本身)作為數(shù)據(jù)進(jìn)行編寫(xiě)和操作,或者在編譯時(shí)做一部分工作,在運(yùn)行的時(shí)候做另外一部分工作。 在這里講到的很多也許只和程序?qū)τ诠ぷ鳈C(jī)制的操作有關(guān),但是作為初...
摘要:事實(shí)上,實(shí)現(xiàn)元編程有多種方式,從語(yǔ)言本身來(lái)講,可以分為兩類增強(qiáng)型與新的語(yǔ)法實(shí)現(xiàn),前者的代表是反射,后者的代表為。在第二部分,我們嘗試在語(yǔ)言基礎(chǔ)上增加原生的元編程能力并介紹了該思路的實(shí)現(xiàn)框架。 語(yǔ)言的自由度 自由度這個(gè)概念在不同領(lǐng)域有不同的定義,我們借鑒數(shù)學(xué)中構(gòu)成一個(gè)空間的維數(shù)來(lái)表達(dá)其自由度的做法,在此指的是:解決同一個(gè)問(wèn)題彼此不相關(guān)的設(shè)計(jì)方法學(xué)數(shù)量。 例如,解決一個(gè)比如商品打折的問(wèn)題,...
摘要:事實(shí)上,實(shí)現(xiàn)元編程有多種方式,從語(yǔ)言本身來(lái)講,可以分為兩類增強(qiáng)型與新的語(yǔ)法實(shí)現(xiàn),前者的代表是反射,后者的代表為。在第二部分,我們嘗試在語(yǔ)言基礎(chǔ)上增加原生的元編程能力并介紹了該思路的實(shí)現(xiàn)框架。 語(yǔ)言的自由度 自由度這個(gè)概念在不同領(lǐng)域有不同的定義,我們借鑒數(shù)學(xué)中構(gòu)成一個(gè)空間的維數(shù)來(lái)表達(dá)其自由度的做法,在此指的是:解決同一個(gè)問(wèn)題彼此不相關(guān)的設(shè)計(jì)方法學(xué)數(shù)量。 例如,解決一個(gè)比如商品打折的問(wèn)題,...
摘要:邏輯運(yùn)算一般語(yǔ)言中,邏輯運(yùn)算與布爾元算是等義的,其運(yùn)算元與目標(biāo)類型都是布爾值。除此之外,還有以下的兩條特性運(yùn)算符會(huì)將運(yùn)算元理解為布爾值,以進(jìn)行布爾運(yùn)算。運(yùn)算過(guò)程是支持布爾短路的。 邏輯運(yùn)算 一般語(yǔ)言中,邏輯運(yùn)算與布爾元算是等義的,其運(yùn)算元與目標(biāo)類型都是布爾值。JavaScript當(dāng)然支持這種純布爾運(yùn)算,不但如此,JavaScript還包括另外一種邏輯運(yùn)算,它的表達(dá)式結(jié)果是不確定的。 ...
閱讀 2882·2021-09-28 09:36
閱讀 3608·2021-09-27 13:59
閱讀 2484·2021-08-31 09:44
閱讀 2278·2019-08-30 15:54
閱讀 2352·2019-08-30 15:44
閱讀 1180·2019-08-30 13:45
閱讀 1223·2019-08-29 18:38
閱讀 1207·2019-08-29 18:37