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}|\{|\}|=|%|\.\/|\.\*|,/g)
  let ops = tmp_code.match(/["]+[^"]*["]+|[']+[^']*[']+|\*\*|\+|-|\*|\/|\(|\)|\?|>[=]|<[=]|={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}|\{|=|\}|%|\.\/|\.\*/.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)()
  }

这样就实现了运算符的重载

您可能感兴趣的文章:

  • 如何实现JS函数的重载
  • js 覆盖和重载 函数
  • js中方法重载如何实现?以及函数的参数问题
  • JavaScript 的方法重载效果
  • javascript 面向对象,实现namespace,class,继承,重载
  • JS函数重载的解决方案
  • 有关于JS构造函数的重载和工厂方法
  • js 与或运算符 || && 妙用
  • javascript三元运算符用法实例
  • javascript typeof的用法与typeof运算符介绍[详细]
  • JS按位非(~)运算符与~~运算符的理解分析
(0)

相关推荐

  • JS函数重载的解决方案

    在面向对象的编程中,很多语言都支持函数重载,能根据函数传递的不同个数.类型的参数来做不同的操作,JS对它却不支持,需要我们额外做些小动作. 在JS的函数执行上下文中有一个名为arguments的有意思的变量,它以数组的形式存储了函数执行时传递过来的所有参数,即使函数定义没有定义这 么多个形参.还有一个特别之处就是跟Array类型相比,arguments变量有且只有一个length属性,Array的方法,例如push.pop 等,它并不具备,它只是一个"伪数组":具有length属性,存

  • js 与或运算符 || && 妙用

    首先出个题:如图: 假设对成长速度显示规定如下: 成长速度为5显示1个箭头: 成长速度为10显示2个箭头: 成长速度为12显示3个箭头: 成长速度为15显示4个箭头: 其他都显示都显示0各箭头. 用代码怎么实现? 差一点的if,else: Js代码 复制代码 代码如下: var add_level = 0; if(add_step == 5){ add_level = 1; } else if(add_step == 10){ add_level = 2; } else if(add_step

  • JavaScript 的方法重载效果

    在 JavaScript 的方法内,有个叫做 arguments 的变量数组,它是只读的,所有实际传入的参数变量都 放在了里面,通过它,我们可以对传入的参数进行类型检查,从而实现重载的效果. 判断一个变量的类型有两种方法. 1,用 typeof 语句: 复制代码 代码如下: function check(){ if(typeof arguments[0] == 'string') alert('你传入的参数是个字符串'); else if(typeof arguments[0] == 'numb

  • JS按位非(~)运算符与~~运算符的理解分析

    那么,对于typeof var!=="number"的类型来说,进行运算时,会尝试转化成32位整形数据,如果无法转换成整形数据,就转换为NaN: JS在位运算上用了更简便的一种方法来实现这中运算,那么它的实现原理大致上可以这样理解: 复制代码 代码如下: var testData=-2.9; var testResult=(typeof testData==="number"&&!isNaN(testData)&&testData!=

  • js中方法重载如何实现?以及函数的参数问题

    都知道在js中没有办法直接实现方法重载,因为在js中如果定义了多个名称相同,但参数个数不一样的方法,其实只有最后一个方法能被真正调用,其他的方法都被覆盖掉了. 但每一个函数都有一个特殊的参数arguments,利用它可以实现方法的重载. 例如: 复制代码 代码如下: function Add(firstnumber,sencondnumber) { return firstnumber+sencondnumber; } 只能处理两个参数,如果有多个参数,或者没有参数,一个参数的情况都是处理不了的

  • javascript 面向对象,实现namespace,class,继承,重载

    由于组里项目大多的javascript,css等客户端工作是另一同事在负责,该同事又特忙无法重构,老大也就只是提建议并未立即实施重构.但是我前些日子也改过些许客户端的小bug,确实那代码看得让人有些云里雾里,不知身在哪山,轻易不敢动代码,于是就自己动手鼓捣起我曾又爱又恨的javascript来,自己写一个简单的js实现namespace,继承,重载等面向对象的特性.欢迎拍砖灌水 .定义namespace Namesapce.js 复制代码 代码如下: Namespace = new Object

  • javascript typeof的用法与typeof运算符介绍[详细]第1/2页

    经常会在js里用到数组,比如 多个名字相同的input, 若是动态生成的, 提交时就需要判断其是否是数组. if(document.mylist.length != "undefined" ) {} 这个用法有误. 正确的是 if( typeof(document.mylist.length) != "undefined" ) {} 或 if( !isNaN(document.mylist.length) ) {} typeof的运算数未定义,返回的就是 "

  • 如何实现JS函数的重载

    javascript不能支持函数的重载,如下: 复制代码 代码如下: <script language="JavaScript"> function f(length) {     alert("高为:"+length); } function f(length,width) {     alert("高为:"+length+",宽为:"+width); } </srcipt> 上面那段代码其实是行不通

  • js 覆盖和重载 函数

    学过JAVA的人对函数的覆盖和重载肯定是再熟悉不过了. 重载指两个或多个函数的参数类型,顺序和数量以及返回值不一样. 覆盖指两个或多个函数的参数类型,顺序和数量以及返回值完全一样. 那javascript真的有这种特性么? 回答是JS中函数重名只会采用最后一个定义. 首先来看下下面的代码 复制代码 代码如下: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEA

  • javascript三元运算符用法实例

    三元运算符用法实例: 如名字表示的三元运算符需要三个操作数. 语法是 条件 ? 结果1 : 结果2;. 这里你把条件写在问号(?)的前面后面跟着用冒号(:)分隔的结果1和结果2.满足条件时结果1否则结果2. 复制代码 代码如下: <script type="text/javascript"> var b=5; (b == 5) ? a="true" : a="false"; document.write(" --------

  • 有关于JS构造函数的重载和工厂方法

    写在前面 有时候我们希望对象的初始化有多种方式.比如通过元素组成的数组来初始化一个Set对象,而不是通过传入构造函数的参数列表来初始化它 重载overload 通过重载这个构造函数方法让它根据传入参数的不同,来执行不同的初始化方法. 复制代码 代码如下: function Set() {this.values = {}; // 用这个对象的属性保存这个集合this.n = 0; // 集合中值的个数 // 如果传入一个类数组的对象,将这个元素添加到集合中// 否则,将所有的参数都添加到集合中if

随机推荐