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函数代码如下:
/** *转换方法 *@parama *@paramop *@paramb *@returns{*} *@private */ exportfunction__replace__(a,op,b){ if(typeof(a)!='object'&&typeof(b)!='object'){ returnnewFunction('a','b','returna'+op+'b')(a,b) } if(!Object.getPrototypeOf(a).isPrototypeOf(b) &&Object.getPrototypeOf(b).isPrototypeOf(a)){ throw'不同类型的对象不能使用四则运算' } lettarget=null if(Object.getPrototypeOf(a).isPrototypeOf(b)){ target=newFunction('return'+b.__proto__.constructor.name)() } if(Object.getPrototypeOf(b).isPrototypeOf(a)){ target=newFunction('return'+a.__proto__.constructor.name)() } if(op=='+'){ if(target.__add__!=undefined){ returntarget.__add__(a,b) }else{ throwtarget.toString()+'\n未定义__add__方法' } }elseif(op=='-'){ if(target.__plus__!=undefined){ returntarget.__plus__(a,b) }else{ throwtarget.toString()+'\n未定义__plus__方法' } }elseif(op=='*'){ if(target.__multiply__!=undefined){ returntarget.__multiply__(a,b) }else{ throwtarget.toString()+'\n未定义__multiply__方法' } }elseif(op=='/'){ if(target.__divide__!=undefined){ returntarget.__divide__(a,b) }else{ throwtarget.toString()+'\n未定义__divide__方法' } }elseif(op=='%'){ if(target.__mod__!=undefined){ returntarget.__mod__(a,b) }else{ throwtarget.toString()+'\n未定义__mod__方法' } }elseif(op=='.*'){ if(target.__dot_multiply__!=undefined){ returntarget.__dot_multiply__(a,b) }else{ throwtarget.toString()+'\n未定义__dot_multiply__方法' } }elseif(op=='./'){ if(target.__dot_divide__!=undefined){ returntarget.__dot_divide__(a,b) }else{ throwtarget.toString()+'\n未定义__dot_divide__方法' } }elseif(op=='**'){ if(target.__power__!=undefined){ returntarget.__power__(a,b) }else{ throwtarget.toString()+'\n未定义__power__方法' } }else{ throwop+'运算符无法识别' } }
replace实现非常简单,不做过多解释,重要的部分是如何实现代码的编译。大学学习数据结构时四则运算的实现就是这翻译的基础,略微有些差异。简单描述一下流程:
1、分割表达式,提取变量和运算符获得元数组A
2、遍历元数组
如果元素是运算符加减乘除,则从堆栈中弹出上一个元素,转换为replace(last,操作符,
如果元素是‘)',则从堆栈中弹出元素,拼接直到遇到'(',并压入堆栈。这里需要注意‘('元素前是否为函数调用或replace,如果是函数调用或replace,则需要继续向前弹出数据,闭合replace函数的闭合。
如果是一般元素,则查看前一个元素是否replace,如果是,则需要拼接‘)'使得replace函数闭合,否则直接将元素压入栈。
3、将2步骤中得到的栈顺序组合就得到编译后的表达式。
依据上述流程,实现代码:
/** *表达式转换工具方法 *@paramcode */ exportfunctiontranslate(code){ letdata=[] lettmp_code=code.replace(/\s/g,'') lettmp=[] letvari=tmp_code.split(/["]+[^"]*["]+|[']+[^']*[']+|\*\*|\+|-|\*|\/|\(|\)|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|\}|=|%|\.\/|\.\*|,/g) letops=tmp_code.match(/["]+[^"]*["]+|[']+[^']*[']+|\*\*|\+|-|\*|\/|\(|\)|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|\}|=|%|\.\/|\.\*|,/g) for(leti=0,len=ops.length;i[=]|<[=]|={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) } } } letresult='' data.forEach((value,key,own)=>{ result+=value }) returnresult }
表达式编译的方法写好了,接下来就是如何使编写的代码被我们的翻译机翻译,也就是需要一个容器,两种方法:一种就是类构造器重新定义方法属性,另一种就是将代码作为参数传入我们自定义的方法。接下来介绍一下类构造器中重新定义方法:
exportdefaultclassOOkay{ constructor(){ letprotos=Object.getOwnPropertyNames(Object.getPrototypeOf(this)) protos.forEach((proto,key,own)=>{ if(proto!='constructor'){ Object.defineProperty(this,proto,{ value:newFunction(translate_block(proto,this[proto].toString())).call(this) }) } }) } }
由上面可以看出,我们使用Object.defineProperty在构造器中重新定义了,translate_block是对整个代码块分割得到进行翻译,代码如下:
/** *类代码块转换工具 *@paramname *@paramblock *@returns{string} */ exportfunctiontranslate_block(name,block){ letcodes=block.split('\n') letreg=newRegExp('^'+name+'$') console.log(reg.source) codes[0]=codes[0].replace(name,'function') for(leti=1;i对于新的类,我们只要继承OOkay类就可以在该类中使用运算符重载。对于继承自非OOkay类的,我们可以采用注入的方式,如下:
/** *非继承类的注入方法 *@paramtarget */ staticinject(target){ letprotos=Object.getOwnPropertyNames(Object.getPrototypeOf(target)) protos.forEach((proto,key,own)=>{ if(proto!='constructor'){ Object.defineProperty(target,proto,{ value:newFunction(translate_block(proto,target[proto].toString())).call(target) }) } }) }对于非类中的代码,我们需要一个容器,这里我采用了两种方式,一种以ookay脚本的方式使用,像这样
leta=a+b//a、b为对象实例
还有就是将代码作为参数传入__$$__方法,该方法编译代码并执行,如下:static__$__(fn){ if(!(fninstanceofFunction)){ throw'参数错误' } (newFunction(translate_block('function',fn.toString()))).call(window)() }这样就实现了运算符的重载