Javascript實現運算符重載
阿新 • • 發佈:2018-03-19
運算符重載 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 函數代碼如下:
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地址:https://gitee.com/djxfire/javascript_operator_overloading.git請添加鏈接描述
Javascript實現運算符重載