Javascript實(shí)現(xiàn)運(yùn)算符重載詳解
最近要做數(shù)據(jù)處理,自定義了一些數(shù)據(jù)結(jié)構(gòu),比如Mat,Vector,Point之類的,對(duì)于加減乘除之類的四則運(yùn)算還要重復(fù)定義,代碼顯得不是很直觀,javascript沒有運(yùn)算符重載這個(gè)像C++、C#之類的功能的確令人不爽,于是想“曲線救國”,自動(dòng)將翻譯代碼實(shí)現(xiàn)運(yùn)算符重載,實(shí)現(xiàn)思路其實(shí)很簡(jiǎn)單,就是編寫一個(gè)解釋器,將代碼編譯。例如:
S = A + B (B - C.fun())/2 + D
翻譯成
`S = replace(replace(A, '+', replace(replace(B,'',(replace(B,'-',C.fun())))),'/',2),'+',D)`
在replace函數(shù)中我們調(diào)用對(duì)象相應(yīng)的運(yùn)算符函數(shù),replace函數(shù)代碼如下:
/** * 轉(zhuǎn)換方法 * @param a * @param op * @param b * @returns {*} * @private */ export function __replace__(a,op,b){ if(typeof(a) != 'object' && typeof(b) != 'object'){ return new Function('a','b','return a' + op + 'b')(a,b) } if(!Object.getPrototypeOf(a).isPrototypeOf(b) && Object.getPrototypeOf(b).isPrototypeOf(a)){ throw '不同類型的對(duì)象不能使用四則運(yùn)算' } let target = null if (Object.getPrototypeOf(a).isPrototypeOf(b)) { target = new Function('return ' + b.__proto__.constructor.name)() } if (Object.getPrototypeOf(b).isPrototypeOf(a)) { target = new Function('return ' + a.__proto__.constructor.name)() } if (op == '+') { if (target.__add__ != undefined) { return target.__add__(a, b) }else { throw target.toString() +'\n未定義__add__方法' } }else if(op == '-') { if (target.__plus__ != undefined) { return target.__plus__(a, b) }else { throw target.toString() + '\n未定義__plus__方法' } }else if(op == '*') { if (target.__multiply__ != undefined) { return target.__multiply__(a, b) }else { throw target.toString() + '\n未定義__multiply__方法' } } else if (op == '/') { if (target.__divide__ != undefined) { return target.__divide__(a, b) }else { throw target.toString() + '\n未定義__divide__方法' } } else if (op == '%') { if (target.__mod__ != undefined) { return target.__mod__(a, b) }else { throw target.toString() + '\n未定義__mod__方法' } } else if(op == '.*') { if (target.__dot_multiply__ != undefined) { return target.__dot_multiply__(a, b) }else { throw target.toString() + '\n未定義__dot_multiply__方法' } } else if(op == './') { if (target.__dot_divide__ != undefined) { return target.__dot_divide__(a, b) }else { throw target.toString() + '\n未定義__dot_divide__方法' } } else if(op == '**') { if (target.__power__ != undefined) { return target.__power__(a, b) }else { throw target.toString() + '\n未定義__power__方法' } }else { throw op + '運(yùn)算符無法識(shí)別' } }
replace實(shí)現(xiàn)非常簡(jiǎn)單,不做過多解釋,重要的部分是如何實(shí)現(xiàn)代碼的編譯。大學(xué)學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)時(shí)四則運(yùn)算的實(shí)現(xiàn)就是這翻譯的基礎(chǔ),略微有些差異。簡(jiǎn)單描述一下流程:
1、分割表達(dá)式,提取變量和運(yùn)算符獲得元數(shù)組A
2、遍歷元數(shù)組
如果元素是運(yùn)算符加減乘除,則從堆棧中彈出上一個(gè)元素,轉(zhuǎn)換為replace(last,操作符,
如果元素是‘)',則從堆棧中彈出元素,拼接直到遇到'(',并壓入堆棧。這里需要注意‘('元素前是否為函數(shù)調(diào)用或replace,如果是函數(shù)調(diào)用或replace,則需要繼續(xù)向前彈出數(shù)據(jù),閉合replace函數(shù)的閉合。
如果是一般元素,則查看前一個(gè)元素是否replace,如果是,則需要拼接‘)'使得replace函數(shù)閉合,否則直接將元素壓入棧。
3、將2步驟中得到的棧順序組合就得到編譯后的表達(dá)式。
依據(jù)上述流程,實(shí)現(xiàn)代碼:
/** * 表達(dá)式轉(zhuǎn)換工具方法 * @param code */ export function translate (code) { let data = [] let tmp_code = code.replace(/\s/g,'') let tmp = [] let vari = tmp_code.split(/["]+[^"]*["]+|[']+[^']*[']+|\*\*|\+|-|\*|\/|\(|\)|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|\}|=|%|\.\/|\.\*|,/g) let ops = tmp_code.match(/["]+[^"]*["]+|[']+[^']*[']+|\*\*|\+|-|\*|\/|\(|\)|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|\}|=|%|\.\/|\.\*|,/g) for (let i = 0,len = ops.length; i < len; i++) { if (vari[i] != '') { tmp.push(vari[i]) } if (ops[i] != '') { tmp.push(ops[i]) } } tmp.push(vari[ops.length]) for (let i = 0; i < tmp.length; i++){ let item = tmp[i] if(/\*\*|\+|-|\*|\/|%|\.\/|\.\*/.test(tmp[i])) { let top = data.pop() let trans = '__replace__(' + top + ',\'' + tmp[i] + '\',' data.push(trans) }else{ if (')' == tmp[i]) { let trans0 = tmp[i] let top0 = data.pop() while (top0 != '(') { trans0 = top0 + trans0 top0 = data.pop() } trans0 = top0 + trans0 let pre = data[data.length - 1] while(/[_\w]+[\.]?[_\w]+/.test(pre) && !/^__replace__\(/.test(pre) && pre != undefined) { pre = data.pop() trans0 = pre + trans0 pre = data[data.length - 1] } pre = data[data.length - 1] while(pre != undefined && /^__replace__\(/.test(pre)){ pre = data.pop() trans0 = pre + trans0 + ')' pre = data[data.length - 1] } data.push(trans0) }else { let pre = data[data.length - 1] let trans1 = tmp[i] while(pre != undefined && /^__replace__\(/.test(pre) && !/\*\*|\+|-|\*|\/|\(|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|=|\}|%|\.\/|\.\*/.test(item) && !/^__replace__\(/.test(item)) { if(tmp[i + 1] == undefined){ pre = data.pop() trans1 = pre + trans1 + ')' break; }else{ pre = data.pop() trans1 = pre + trans1 + ')' pre = data[data.length - 1] } } data.push(trans1) } } } let result = '' data.forEach((value, key, own) => { result += value }) return result }
表達(dá)式編譯的方法寫好了,接下來就是如何使編寫的代碼被我們的翻譯機(jī)翻譯,也就是需要一個(gè)容器,兩種方法:一種就是類構(gòu)造器重新定義方法屬性,另一種就是將代碼作為參數(shù)傳入我們自定義的方法。接下來介紹一下類構(gòu)造器中重新定義方法:
export default class OOkay { constructor () { let protos = Object.getOwnPropertyNames(Object.getPrototypeOf(this)) protos.forEach((proto, key, own) => { if(proto != 'constructor'){ Object.defineProperty(this, proto, { value:new Function(translate_block(proto, this[proto].toString())).call(this) }) } }) } }
由上面可以看出,我們使用Object.defineProperty在構(gòu)造器中重新定義了,translate_block是對(duì)整個(gè)代碼塊分割得到進(jìn)行翻譯,代碼如下:
/** * 類代碼塊轉(zhuǎn)換工具 * @param name * @param block * @returns {string} */ export function translate_block (name , block) { let codes = block.split('\n') let reg = new RegExp('^' + name + '$') console.log(reg.source) codes[0] = codes[0].replace(name,'function') for(let i = 1; i < codes.length; i++) { if (codes[i].indexOf('//') != -1) { codes[i] = codes[i].substring(0,codes[i].indexOf('//')) } if(/\*\*|\+|-|\*|\/|%|\.\/|\.\*/g.test(codes[i])){ if (codes[i].indexOf('return ') != -1) { let ret_index = codes[i].indexOf('return ') + 7 codes[i] = codes[i].substring(0,ret_index) + translate(codes[i].substring(ret_index)) }else { let eq_index = codes[i].indexOf('=') + 1 codes[i] = codes[i].substring(0,eq_index) + translate(codes[i].substring(eq_index)) } } } return 'return ' + codes.join('\n') }
對(duì)于新的類,我們只要繼承OOkay類就可以在該類中使用運(yùn)算符重載。對(duì)于繼承自非OOkay類的,我們可以采用注入的方式,如下:
/** * 非繼承類的注入方法 * @param target */ static inject (target) { let protos = Object.getOwnPropertyNames(Object.getPrototypeOf(target)) protos.forEach((proto, key, own) => { if (proto != 'constructor') { Object.defineProperty(target, proto, { value:new Function(translate_block(proto, target[proto].toString())).call(target) }) } }) }
對(duì)于非類中的代碼,我們需要一個(gè)容器,這里我采用了兩種方式,一種以ookay腳本的方式使用,像這樣
<script type='text/ookayscript'>
let a = a+b // a、b為對(duì)象實(shí)例
</script>
還有就是將代碼作為參數(shù)傳入__$$__方法,該方法編譯代碼并執(zhí)行,如下:
static __$__(fn) { if(!(fn instanceof Function)){ throw '參數(shù)錯(cuò)誤' } (new Function(translate_block('function',fn.toString()))).call(window)() }
這樣就實(shí)現(xiàn)了運(yùn)算符的重載
相關(guān)文章
淺談JavaScript的push(),pop(),concat()方法
下面小編就為大家?guī)硪黄獪\談JavaScript的push(),pop(),concat()方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06JavaScript實(shí)現(xiàn)控制并發(fā)請(qǐng)求的方法詳解
這篇文章主要為大家詳細(xì)介紹了如果有100個(gè)請(qǐng)求,那么如何使用JavaScript實(shí)現(xiàn)控制并發(fā)請(qǐng)求,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一2024-03-03抽出www.templatemonster.com的鼠標(biāo)懸停加載大圖模板的代碼
抽出www.templatemonster.com的鼠標(biāo)懸停加載大圖模板的代碼...2007-07-07JavaScript實(shí)現(xiàn)的開關(guān)燈泡點(diǎn)擊切換特效示例
這篇文章主要介紹了JavaScript實(shí)現(xiàn)的開關(guān)燈泡點(diǎn)擊切換特效,涉及javascript事件響應(yīng)及頁面元素屬性動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-07-07javascript 拖動(dòng)表格行實(shí)現(xiàn)代碼
用js實(shí)現(xiàn)的拖動(dòng)表格的tr行的實(shí)現(xiàn)代碼,需要的朋友可以參考下。2011-05-05JavaScript使用享元模式實(shí)現(xiàn)文件上傳優(yōu)化操作示例
這篇文章主要介紹了JavaScript使用享元模式實(shí)現(xiàn)文件上傳優(yōu)化操作,結(jié)合實(shí)例形式分析了javascript基于享元模式進(jìn)行文件上傳優(yōu)化操作的原理、步驟及相關(guān)使用技巧,需要的朋友可以參考下2018-08-08javascript實(shí)現(xiàn)隨機(jī)讀取數(shù)組的方法
這篇文章主要介紹了javascript實(shí)現(xiàn)隨機(jī)讀取數(shù)組的方法,涉及javascript隨機(jī)數(shù)及針對(duì)數(shù)組操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-08-08基于JavaScript實(shí)現(xiàn)鼠標(biāo)懸浮彈出跟隨鼠標(biāo)移動(dòng)的帶箭頭的信息層
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)鼠標(biāo)懸浮彈出跟隨鼠標(biāo)移動(dòng)的帶箭頭的信息層 的相關(guān)資料,需要的朋友可以參考下2016-01-01