メインコンテンツへ移動

JS 学習ノート 11_高度なテクニック

無料2015-04-13#JS#js高级技巧

本記事では、型検出、スコープ安全なコンストラクタ、遅延読み込み、関数バインディング、関数カリー化(関数のネスト)、改ざん防止オブジェクト、関数スロットリング、オブザーバーパターンなどの高度なテクニックについて紹介します

##1. 型検出

typeof は時々不合理な戻り値を返します。例えば RegExp オブジェクトは object を返します。テストコード:

var regex = /^what$/i;
regex = new RegExp('^what$');
alert(typeof regex);

instanceof はペ���ジに複数の frame がある場合に使用できません。異なる frame からのオブジェクトに対して instanceof は false を返します

Object.prototype.toString.call(value) === '[object Array/Function...]' を使用して型チェックを行うことができます。また、ネイティブオブジェクトとカスタムオブジェクトを区別するためにも使用できます。例えば:

[object JSON]//ネイティブ JSON オブジェクト
[object Object]//カスタム JSON オブジェクト

注意:IE では COM オブジェクト形式で実装された関数オブジェクトは toString で [object Function] を返しません

##2. スコープ安全なコンストラクタ

new 演算子でコンストラクタを呼び出すと、新規作成されたオブジェクトにプロパティが追加されますが、コンストラクタを直接呼び出すとグローバルオブジェクト window にプロパティが追加されます

グローバルスコープを汚染しないようにするために、以下のコンストラクタを使用できます:

/* グローバルスコープを汚染する可能性あり
function Student(name){
  this.name = name;
}
*/
//スコープ安全なコンストラクタ
function Student(name){
  if(this instanceof Student){
    this.name = name;
  }
  else{
    return new Student(name);
  }
}

上記のコンストラクタは、コンストラクタを直接呼び出してグローバルオブジェクトに意図せずプロパティを追加することを回避できますが、この方法で継承を実装すると問題が発生する可能性があります。型チェックにより継承が失敗する可能性があります

##3. 遅延読み込み(重複した分岐検出を回避)

  1. 最初の分岐検出実行時に既存の関数を上書きします。例えば:

    function detect(){
      if(...){
        detect = function(){
          //
        }
      }
      else if(...){
        detect = function(){
          //
        }
      }
      else...
    }
    
  2. 即座に実行される匿名関数を使用して匿名関数を返すことで遅延読み込みを実装できます。例えば:

    var detect = (function(){
      if(...){
        return function(){
          //
        }
      }
      else if(...){
        return function(){
          //
        }
      }
      else...
    })();
    

最初の方式は初回呼び出し時にパフォーマンスが低下しますが、以降の呼び出しではパフォーマンス低下はありません。2 番目の方式は初回呼び出し時にもパフォーマンス低下はありません。コードの初回読み込み時に時間がかかる処理を配置しているためです

##4. 関数バインディング(実行環境の指定)

以下の関数を使用して、関数の実行環境を指定し、新しい関数を作成できます:

function bind(fun, context){
  return function(){
    return fun.apply(context, arguments);
  }
}

既存の関数から新しい関数を簡単に生成できます。[IE9+] にはネイティブの bind メソッドがあります。例えば var newFun = fun.bind(obj);

注意:関数バインディングにはメモリ消費が多く、実行が遅いという欠点があります

##5. 関数カリー化(関数のネストとも呼ばれ、一部のパラメータを指定可能)

カリー化関数を作成する一般的な方法:

function curry(fun){
  var args = Array.prototype.slice.call(arguments, 1);//最初のパラメータ fun を削除し、指定されたパラメータ値を取得
  return function(){
    var innerArgs = Array.prototype.slice.call(arguments);//内部の arguments オブジェクトを配列に変換(concat メソッド使用のため)
    var finalArgs = args.concat(innerArgs);//パラメータリストを結合
    return fun.apply(null, finalArgs);//結合したパラメータリストを fun に渡す
  }
}

または bind メソッドを強化してカリー化を実装:

function bind(fun, context){
  var args = Array.prototype.slice.call(arguments, 2);//最初の 2 つのパラメータを削除
  return function(){
    var innerArgs = Array.prototype.slice.call(arguments);//curry と同様
    var finalArgs = args.concat(innerArgs);//curry と同様
    return fun.apply(context, finalArgs);//実行環境とパラメータを指定
  }
}

注意:カリー化と bind には追加のオーバーヘッドが存在します。乱用しないでください

##6. 改ざん防止オブジェクト

  1. 拡張不可オブジェクト(新しいプロパティを追加できない)

    var obj = {a : 1, b : 2};
    alert(Object.isExtensible(obj));//true
    Object.preventExtensions(obj);//obj を拡張不可に設定
    alert(Object.isExtensible(obj));//false
    obj.c = 3;
    alert(obj.c);//undefined
    

注意:拡張不可設定操作は取り消せません(元に戻せない)。設定後は新しいプロパティを追加できませんが、既存のプロパティの変更/削除は可能です

  1. シールオブジェクト(既存のプロパティのみ変更可能、削除または追加不可)

    var obj = {a : 1, b : 2};
    Object.seal(obj);//シールオブジェクトに設定
    alert(Object.isSealed(obj));//true
    obj.c = 3;
    alert(obj.c);//undefined
    delete obj.a;//厳格モードでエラー
    alert(obj.a);//1
    
  2. フリーズオブジェクト(読み取り専用、アクセサプロパティは書き込み可能)

    var obj = {a : 1, b : 2};
    Object.freeze(obj);//シールオブジェクトに設定
    alert(Object.isFrozen(obj));//true
    obj.a = 3;
    alert(obj.a);//1
    

上記の実装はすべてES5 で追加された部分です。ブラウザのサポート状況は不明です。ローカルテストでは*[IE8-] はサポートしていません*。Chrome と FF はサポートしています

##7. 関数スロットリング

時間のかかる大きなタスクを小さな部分に分割し、setTimeout で実行を制御します

利点:ページの応答速度が向上します

欠点:論理的一貫性が失われ、実装難易度が上がります。また、完全なトランザクションが分割されるため、トランザクション制御の実装が困難になります

##8. オブザーバーパターン

カスタムイベントを使用してオブザーバーパターンを実装できます:

function EventTarget(){
  this.handlers = {};    
}

EventTarget.prototype = {
  constructor: EventTarget,

  addHandler: function(type, handler){
    if (typeof this.handlers[type] == "undefined"){
      this.handlers[type] = [];
    }

    this.handlers[type].push(handler);
  },
  
  fire: function(event){
    if (!event.target){
      event.target = this;
    }
    if (this.handlers[event.type] instanceof Array){
      var handlers = this.handlers[event.type];
      for (var i=0, len=handlers.length; i < len; i++){
        handlers[i](event);
      }
    }
  },

  removeHandler: function(type, handler){
    if (this.handlers[type] instanceof Array){
      var handlers = this.handlers[type];
        for (var i=0, len=handlers.length; i < len; i++){
          if (handlers[i] === handler){
            break;
          }
        }
          
        handlers.splice(i, 1);
      }
  }
};

使用方法は以下の通り:

function handleMessage(event){
  alert("Message received: " + event.message);
}
//新しいオブジェクトを作成
var target = new EventTarget();
//イベントハンドラを追加
target.addHandler("message", handleMessage);
//イベントをトリガー
target.fire({ type: "message", message: "Hello world!"});
//イベントハンドラを削除
target.removeHandler("message", handleMessage);
//再度イベントをトリガー、イベントハンドラはないはず
target.fire({ type: "message", message: "Hello world!"});

コメント

コメントはまだありません

コメントを書く