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地址:https://gitee.com/djxfire/javascript_operator_overloading.git请添加链接描述

原文地址:http://blog.51cto.com/janwool/2088493

时间: 2024-11-05 14:59:03

Javascript实现运算符重载的相关文章

(4)javascript的运算符以及运算符的优先级

                                运算符的使用方法 在javascript的程序中要完成各种各样的运算,是离不开运算符的. 在javascript中,按运算符类型可以分为算术运算符.赋值运算符.比较运算符.逻辑运算符.条件运算符等. ( 图片来自于W3School离线手册) 算术运算符案例: <!doctype html> <head> <meta http-equiv="content-type" content="

关于运算符重载

运算符重载需遵循以下原则: 1.除了类所属关系运算符".".成员指针运算符".*".作用域运算符"::".sizeof运算符.三目运算符"?:"之外,C++中所有的运算符都可以进行重载 2.重载运算符限制在C++已有的运算符范围内,不允许创建新的运算符 3.重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符的操作数的个数及语法结构 4.运算符重载不能改变运算符用于内置类型的对象的含义,只能用于自定义类型对象之间,

运算符重载

关键字:operator 相见:<高质量程序设计指南> P255 如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符. 如果运算符被重载为类的成员函数,那么一元运算符没有参数(但是++和--运算符的后置版本除外),二元运算符只有右侧参数,因为对象自己成了左侧参数. 运算符重载的特殊性 如果重载为成员函数,则this对象发起对它的调用 如果重载为全局函数,则第一个参数发起对它的调用 禁止用户发明该语言运算符集合中不存在的运算符 除了函数调用运算符

C++ 运算符重载

C++中预定义的运算符的操作对象只能是基本数据类型,实际上,对于很多用户自定义类型,也需要有类似的运算操作.比如对象a和对象b, 那么 a+b 就需要用户自己定义它怎么相加,这时就用到了运算符重载. 运算符重载规则如下: ①. C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已有的运算符. ②. 重载之后运算符的优先级和结合性都不会改变. ③. 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造.一般来说,重载的功能应当与原有功能相类似,不能改变原运算符的操作对

Kotlin中复合赋值(+=,-=,……)运算符重载

本篇建立在已经了解了kotlin中运算符重载的理念以及如何实现的基础上. 来我们首先写一个简单的类,然后重载运算符+,+=,-,-=这个几个运算符.代码如下: data class Point(var x: Int, var y: Int) { operator fun plus(point: Point): Point { return Point(this.x + point.x, this.y + point.y) } operator fun plusAssign(point: Poin

第十章 运算符重载

第十章 运算符重载 1.运算符重载的方法 (实质:函数重载) *一般格式: 函数类型名operator 运算符名称(参数表){函数体:} 2.运算符重载的规则 *不允许定义新的运算符 *允许重载的运算符 双目运算符 +(加法)  - (减法)  * (乘法) / (除法)   %(取模) 关系运算符 ==(等于) != (不等)  <   >   <=   >= 逻辑运算符 ||(或)   && (与)  !(非) 单目运算符 + (正)  -(负)   *(指针)

C++运算符重载的妙用

运算符重载(Operator overloading)是C++重要特性之中的一个,本文通过列举标准库中的运算符重载实例,展示运算符重载在C++里的妙用.详细包含重载operator<<,operator>>支持cin,cout输入输出.重载operator[],实现下标运算.重载operator+=实现元素追加:重载operator()实现函数调用.假设你对C++的运算符重载掌握的游刃有余.那就无需继续往下看了. 运算符重载带来的优点就是--让代码变得简洁.以下将展示几个标准库因使

网易云课堂_C++开发入门到精通_章节4:运算符重载

课时23运算符重载 运算符重载 重载赋值运算符 Person& Person::operator=(const Person& other) { //检查自赋值 if (this == &other) { return *this; } //释放原有的内存资源 delete[]m_data; int length = strlen(other.m_data); m_data = new char[length + 1]; strcpy(m_data, other.m_data);

关于c++的运算符重载那些事

搞c++有一段时间了,今天突然要重载一个运算符,发现自己有点忘了,遂查查资料做一下c++运算符重载的小总结. 一.何为运算符重载 运算符的重载是c++语言特有的,java什么的是没有运算符重载的,所以运算符重载在笔试面试中有可能成为c++的高频考点.运算符重载就是重新定义运算符的意义,如常用的+,-,×,÷都是可以重载的.运算符重载使用的是关键字operator,表现形式是: