JSを使用して演算子のオーバーロードを実装する方法

php中世界最好的语言
リリース: 2018-05-26 15:36:00
オリジナル
1628 人が閲覧しました

今回は、JS を使用して演算子のオーバーロードを実装する方法と、JS を使用して演算子のオーバーロードを実装する際の注意点について説明します。以下は実際的なケースです。

最近データ処理をしていて、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 関数では、次の対応する演算子を呼び出します。オブジェクト関数、置換関数のコードは次のとおりです:

/**
 * 转换方法
 * @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.__pide__ != undefined) {
      return target.__pide__(a, b)
    }else {
      throw target.toString() + '\n未定义__pide__方法'
    }
  } 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_pide__ != undefined) {
      return target.__dot_pide__(a, b)
    }else {
      throw target.toString() + '\n未定义__dot_pide__方法'
    }
  } else if(op == '**') {
    if (target.__power__ != undefined) {
      return target.__power__(a, b)
    }else {
      throw target.toString() + '\n未定义__power__方法'
    }
  }else {
    throw op + '运算符无法识别'
  }
}
ログイン後にコピー

置換の実装は非常に簡単で、あまり説明する必要はありません。重要な部分はコードをコンパイルする方法です。大学でデータ構造を学ぶ際の四則演算の実装がこの翻訳の基礎になっていますが、若干の違いがあります。プロセスを簡単に説明します:

1. 式を分割し、変数と演算子を抽出してメタ配列 A を取得します
2. メタ配列を走査します

要素が演算子の加算、減算、乗算、除算の場合は、スタックから前の要素をポップします。そして、それは replace(last, 演算子,
要素が ')' の場合、要素はスタックからポップされ、'(' に遭遇するまで結合され、スタックにプッシュされます。ここで注意する必要があります'(' 要素の前に関数呼び出しまたは replace があるかどうかを確認します。関数呼び出しまたは replace の場合は、引き続きデータを前方にポップし、置換関数を閉じる必要があります。
それが一般的な要素の場合は、前の要素が置き換えられる場合は、')' を結合して置換関数を閉じる必要があります。それ以外の場合は、ステップ 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(/["]+[^"]*["]+|[&#39;]+[^&#39;]*[&#39;]+|\*\*|\+|-|\*|\/|\(|\)|\?|>[=]|<[=]|={2}|:|&{2}|\|{2}|\{|\}|=|%|\.\/|\.\*|,/g)
  for (let i = 0,len = ops.length; i < len; i++) {
    if (vari[i] != &#39;&#39;) {
      tmp.push(vari[i])
    }
    if (ops[i] != &#39;&#39;) {
      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 = &#39;__replace__(&#39; + top + &#39;,\&#39;&#39; + tmp[i] + &#39;\&#39;,&#39;
      data.push(trans)
    }else{
      if (&#39;)&#39; == tmp[i]) {
        let trans0 = tmp[i]
        let top0 = data.pop()
        while (top0 != &#39;(&#39;) {
          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 + &#39;)&#39;
          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 + &#39;)&#39;
            break;
          }else{
            pre = data.pop()
            trans1 = pre + trans1 + &#39;)&#39;
            pre = data[data.length - 1]
          }

        }
        data.push(trans1)

      }
    }
  }
  let result = &#39;&#39;
  data.forEach((value, key, own) => {
    result += value
  })
  return result
}
ログイン後にコピー
式のコンパイルメソッドが記述されます。次のステップは、記述されたコードをトランスレータによって翻訳する方法です。つまり、コンテナが必要です。 : 1 つはクラス コンストラクターでメソッドの属性を再定義する方法で、もう 1 つはコードをパラメーターとしてカスタム メソッドに渡す方法です。 次に、クラス コンストラクターに再定義メソッドを導入しましょう:

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(&#39;//&#39;) != -1) {
      codes[i] = codes[i].substring(0,codes[i].indexOf(&#39;//&#39;))
    }
    if(/\*\*|\+|-|\*|\/|%|\.\/|\.\*/g.test(codes[i])){
      if (codes[i].indexOf(&#39;return &#39;) != -1) {
        let ret_index = codes[i].indexOf(&#39;return &#39;) + 7
        codes[i] = codes[i].substring(0,ret_index) + translate(codes[i].substring(ret_index))
      }else {
        let eq_index = codes[i].indexOf(&#39;=&#39;) + 1
        codes[i] = codes[i].substring(0,eq_index) + translate(codes[i].substring(eq_index))
      }
    }
  }
  return &#39;return &#39; + codes.join(&#39;\n&#39;)
}
ログイン後にコピー

新しいクラスの場合は、OMany クラスを継承し、演算子のオーバーロードを使用するだけです。クラス以外のクラスから継承するものについては、次のようにインジェクションを使用できます:

/**
   * 非继承类的注入方法
   * @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)
        })
      }
    })
  }
ログイン後にコピー
クラス以外のコードの場合、コンテナが必要です。ここでは 2 つのメソッドを使用します。1 つは次のように ookay スクリプトを使用します。


もう 1 つは、コードをパラメータとして __$ に渡すことです。 $__ メソッドは、次のようにコードをコンパイルして実行します。

static __$__(fn) {
    if(!(fn instanceof Function)){
      throw '参数错误'
    }
    (new Function(translate_block('function',fn.toString()))).call(window)()
  }
ログイン後にコピー

このようにして、演算子のオーバーロードを行います
この記事の事例を読んだ後は、このメソッドを習得したと思います。さらに興味深い記事については、次の記事に注目してください。 PHP 中国語 Web サイトの他の関連記事:

Vue を使用して内部コンポーネントのカルーセル切り替え効果を実現する方法

コンポーネント タグにスタイル クラスを追加するための Angular5 の使用方法

以上がJSを使用して演算子のオーバーロードを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート