Javascript實現運算子過載
最近要做資料處理,自定義了一些資料結構,比如Mat,Vector,Point之類的,對於加減乘除之類的四則運算還要重複定義,程式碼顯得不是很直觀,javascript沒有運算子過載這個像C++、C#之類的功能的確令人不爽,於是想“曲線救國”,自動將翻譯程式碼實現運算子過載,實現思路其實很簡單,就是編寫一個直譯器,將程式碼編譯。例如:
S = A + B (B - C.fun())/2 + D
翻譯成
`S = replace(replace(A, '+', replace(replace(B,'',(replace(B,'-',C.fun())))),'/',2),'+',D)`
在replace函式中我們呼叫物件相應的運算子函式,replace函式程式碼如下:
/** * 轉換方法 * @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 '不同型別的物件不能使用四則運算' } 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 + '運算子無法識別' }}
replace實現非常簡單,不做過多解釋,重要的部分是如何實現程式碼的編譯。大學學習資料結構時四則運算的實現就是這翻譯的基礎,略微有些差異。簡單描述一下流程:
1、分割表示式,提取變數和運算子獲得元陣列A
2、遍歷元陣列
如果元素是運算子加減乘除,則從堆疊中彈出上一個元素,轉換為replace(last,運算子,
如果元素是‘)’,則從堆疊中彈出元素,拼接直到遇到'(',並壓入堆疊。這裡需要注意‘(’元素前是否為函式呼叫或replace,如果是函式呼叫或replace,則需要繼續向前彈出資料,閉合replace函式的閉合。
如果是一般元素,則檢視前一個元素是否replace,如果是,則需要拼接‘)’使得replace函式閉合,否則直接將元素壓入棧。
3、將2步驟中得到的棧順序組合就得到編譯後的表示式。
依據上述流程,實現程式碼:
/** * 表示式轉換工具方法 * @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}
表示式編譯的方法寫好了,接下來就是如何使編寫的程式碼被我們的翻譯機翻譯,也就是需要一個容器,兩種方法:一種就是類構造器重新定義方法屬性,另一種就是將程式碼作為引數傳入我們自定義的方法。接下來介紹一下類構造器中重新定義方法:
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在構造器中重新定義了,translate_block是對整個程式碼塊分割得到進行翻譯,程式碼如下:
/** * 類程式碼塊轉換工具 * @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')}
對於新的類,我們只要繼承OOkay類就可以在該類中使用運算子過載。對於繼承自非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) }) } }) }
對於非類中的程式碼,我們需要一個容器,這裡我採用了兩種方式,一種以ookay指令碼的方式使用,像這樣
<script type='text/ookayscript'>
let a = a+b // a、b為物件例項
</script>
還有就是將程式碼作為引數傳入__$$__
方法,該方法編譯程式碼並執行,如下:
static __$__(fn) { if(!(fn instanceof Function)){ throw '引數錯誤' } (new Function(translate_block('function',fn.toString()))).call(window)() }
這樣就實現了運算子的過載,該模組的使用可以參考git
git地址:請新增連結描述
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/964/viewspace-2816995/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 運算子過載
- 過載運算子
- YTU-OJ-實現複數類中的加運算子過載【C++運算子過載】C++
- 手寫程式語言-實現運算子過載
- [Lang] 運算子過載
- 【原】兩個時間相加的運算子過載實現
- C++運算子過載C++
- C++ 運算子過載C++
- C++——運算子過載C++
- [C++]運算子過載C++
- C# 運算子過載C#
- 運算子過載筆記筆記
- Python 運算子過載Python
- c++ 運算子過載、執行緒安全實現單例C++執行緒單例
- python之運算子過載Python
- 指標運算子過載(* 和 ->)指標
- Python——運算子過載(1)Python
- javascript運算子——邏輯運算子JavaScript
- 初步C++運算子過載學習筆記<3> 增量遞減運算子過載C++筆記
- C++ 過載運算子和過載函式C++函式
- JavaScript /= 運算子JavaScript
- JavaScript += 運算子JavaScript
- JavaScript -= 運算子JavaScript
- JavaScript *= 運算子JavaScript
- JavaScript %= 運算子JavaScript
- JavaScript |= 運算子JavaScript
- JavaScript <<= 運算子JavaScript
- JavaScript >>>= 運算子JavaScript
- JavaScript >>= 運算子JavaScript
- JavaScript &= 運算子JavaScript
- JavaScript ^= 運算子JavaScript
- JavaScript in 運算子JavaScript
- JavaScript:運算子JavaScript
- JavaScript運算子JavaScript
- C++運算子過載詳解C++
- C++中運算子的過載C++
- 瞭解下C# 運算子過載C#
- 深入C++05:運算子過載C++