继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Javascript实现运算符重载

千岁不倒翁
关注TA
已关注
手记 362
粉丝 60
获赞 386

最近要做数据处理,自定义了一些数据结构,比如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地址:https://gitee.com/djxfire/javascript_operator_overloading.git请添加链接描述

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP