流され日記

好奇心の赴くまま、日々起こした行動の記録

withみたいなことをstrictモードで!

withってなにげに便利なんだけど、strictモードでは使えない。
似たようなことをやる方法ないかと考えてみた。

'use strict';

myWith({hoge: 'fuga'}, function(){
  console.log(hoge);
});

こんなコードが動けば嬉しい。

Functionコンストラクタを使う

のっけから黒魔法使う。
関数を文字列化 → 変数宣言加える → えばる という発想。

function myWith(obj, fn){
  var vars = [];
  for(var n in obj){
    vars.push(n + '=this.' + n);
  }
  new Function('var ' + vars.join(',') + ';(' + fn.toString() + ')()').call(obj);
}

ただこの方法はスコープチェーンぶった切るので、

var hoge = 'hoge';

myWith({}, function(){
  console.log(hoge); //=> エラー!
});

のようにmyWithより前で宣言した変数にアクセスできない。困った。

引数として渡す

無理やりスコープチェーンに突っ込むことができないので、少々冗長になるが、引数として渡すようにする。

'use strict';

myWith({hoge: 'hoge'}, function(hoge){
  console.log(hoge);
});

このように呼び出せるようにする。

実装はこんな感じ。引数を解析して必要になるパラメータだけ渡す。

function myWith(obj, fn){
  var argNames = fn.toString().match(/function[^(]*\(([^)]+)\)/)[1].split(',');
  var args = [];
  for(var i = 0, len = argNames.length; i < len; i++){
    var name = argNames[i].replace(/^\s+|\s+/, '');
    args.push(obj[name]);
  }
  fn.apply(this, args);
}

思ったより便利かも

複数のオブジェクト引数にとれて、フォールバックするように実装したら便利かも。
例えばif文を使わずに古いブラウザでだけ自前で実装したObject.createを使うとか、Array.prototypeからよく使うメソッド引っこ抜くとか、両方一緒にやっちゃうとか。

var Polyfill = {
  create: function(){
    ...
  }
};

myWith(Object, Polyfill, [], function(create, slice){
  var obj = create({});
  ...
});