htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

JScript(not JavaScript)のArrayに対するメソッド群を追加する超単純なポリフィル

今さらこんなコードを書くこと自体恥ずべきことだと思うのだけど、勤務先の業務システムがJSPで実装されており、クライアントサイドはレガシーなIEが想定されています(嘆かわしいですが、よくある光景ですね…)

そしてこれまたよくある光景なのですが、秘伝のソースであるため、

<%-- yyyy/mm/dd add someone IE11対応 --%>
<META HTTP-EQUIV=X-UA-Compatible CONTENT=IE=5>
<%-- /IE11対応 --%>

みたいなのが書いてあるわけです。
ダブルクォートが省略されているとか、タグが大文字記述であるとか、昭和の臭いがすごいです。
お前それどこがIE11対応やねん、といったツッコミはさておき。

そんな環境ですと、おもむろにF12を押してデバッグをしようにも配列やArrayLikeを扱う上で非常につらみを感じるわけです。
「じゃあlodash使えばいいじゃん!」と

(function(s){
  s.src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js";
  document.body.appendChild(s);
})(document.createElement("script"))

なんてコードを実行してみても、lodashの実行中にコケたりするんです。
そのときに知ったんですが、lodashってIE9+なんですね…。

便利なライブラリが使えないとなると、ある程度自分で書かざるをえないのでブックマークレットを作成して活用しています。

というのが前置き。
当たり前ですがコードの持ち出しは禁止なので、実際のコードとはちょっと違うと思います。

javascript:(function(d,i,l,x,a){d("indexOf",function(v){for(i=0,l=this.length;i<l;++i)if(this[i]===v)return i;return -1});d("includes",function(v){return!!~this.indexOf(v)});d("map",function(f,t){x=this,a=[];for(i=0,l=x.length;i<l;++i)a.push(f.call(t,x[i],i,x));return a});d("filter",function(f,t){x=this,a=[];for(i=0,l=x.length;i<l;++i)f.call(t,x[i],i,x)&&a.push(x[i]);return a});d("forEach",function(f,t){x=this;for(i=0,l=x.length;i<l;++i)f.call(t,x[i],i,x)});d("reduce",function(f,v){x=this,v=v!==void 0?v:x.shift();for(i=0,l=x.length;i<l;++i)v=f(v,x[i],i,x);return v});d("every",function(f,t){return!!this.reduce(function(p,c){return p&&f.call(t,c)},1)});d("some",function(f,t){return!!this.reduce(function(p,c){return p||f.call(t,c)},0)})})(function(n,f){if(![][n])Array.prototype[n]=f})

これに改行やインデントを付けるとこんな感じになり、

(function(d, i, l, x, a) {
  d("indexOf", function(v) {
    for (i = 0, l = this.length; i < l; ++i)
      if (this[i] === v) return i;
    return -1
  });
  d("includes", function(v) {
    return !!~this.indexOf(v)
  });
  d("map", function(f, t) {
    x = this, a = [];
    for (i = 0, l = x.length; i < l; ++i)
      a.push(f.call(t, x[i], i, x));
    return a
  });
  d("filter", function(f, t) {
    x = this, a = [];
    for (i = 0, l = x.length; i < l; ++i)
      f.call(t, x[i], i, x) && a.push(x[i]);
    return a
  });
  d("forEach", function(f, t) {
    x = this;
    for (i = 0, l = x.length; i < l; ++i)
      f.call(t, x[i], i, x)
  });
  d("reduce", function(f, v) {
    x = this, v = v !== void 0 ? v : x.shift();
    for (i = 0, l = x.length; i < l; ++i)
      v = f(v, x[i], i, x);
    return v
  });
  d("every", function(f, t) {
    return !!this.reduce(function(p, c) { return p && f.call(t, c) }, 1)
  });
  d("some", function(f, t) {
    return !!this.reduce(function(p, c) { return p || f.call(t, c) }, 0)
  })
})(function(n, f) {
  if (![][n]) Array.prototype[n] = f
})

変数名や構文に修正を加えるとこうなります。

(function(define) {
  var i, len, arr;
  
  define("indexOf", function(value) {
    for (i = 0, len = this.length; i < len; ++i) {
      if (this[i] === value) return i;
    }
    return -1;
  });
  
  define("includes", function(value) {
    return this.indexOf(v) !== -1;
  });
  
  define("map", function(func, thisArg) {
    arr = [];
    for (i = 0, len = this.length; i < len; ++i) {
      arr.push(func.call(thisArg, this[i], i, this));
    }
    return arr;
  });
  
  define("filter", function(func, thisArg) {
    arr = [];
    for (i = 0, len = this.length; i < len; ++i) {
      if (func.call(thisArg, this[i], i, this)) a.push(this[i]);
    }
    return arr;
  });
  
  define("forEach", function(func, thisArg) {
    for (i = 0, len = this.length; i < len; ++i) {
      func.call(thisArg, this[i], i, this);
    }
  });
  
  define("reduce", function(func, value) {
    if (value === void 0) {
      value = this.shift();
    }
    for (i = 0, len = this.length; i < len; ++i) {
      value = func(value, this[i], i, this);
    }
    return value;
  });
  
  define("every", function(func, thisArg) {
    return !!this.reduce(function(prev, curr) {
      return prev && func.call(thisArg, curr);
    }, true);
  });
  define("some", function(func, thisArg) {
    return !!this.reduce(function(prev, curr) {
      return prev || func.call(thisArg, curr);
    }, false);
  });
})(function(name, func) {
  if (Array.prototype[name] === void 0) Array.prototype[name] = func;
});

indexOfincludesに第二引数(探査を開始するインデックス)を定義していないのは必要ないからです。
アレ活用している人いるんですかね?

正直map filter forEachthisArgも使わないと言えば使わないんですが、なんとなく付けました。

lenという変数を定義してまでいちいちthis.lengthを代入している理由は、賢明な諸兄はもちろんご存知かと思いますが、forループで毎回Array#lengthにアクセスしてパフォーマンスが落ちるからです。
なので最初に長さを持ってしまいます。
その結果、高階関数に渡す関数がオブジェクト自身の要素数に副作用を起こすものである場合、意図しない動作になります。

まぁ自分しか使わないのでどうでもいいんですが。

 

 

話戻りますが、前述のJSPのコード、それはそれは本当に酷いです。

var url;

if (document.forms[0].document.forms[0].foo.value == "1") {
  document.forms[1].elem1.disabled = true;
  document.forms[1].elem2.disabled = true;
  document.forms[1].elem3.disabled = true;
  
  url = "/foo.html";
  document.forms[2].location.href = url;
  return;
}
else if (document.forms[0].document.forms[0].foo.value == "2") {
  document.forms[1].elem1.disabled = true;
  document.forms[1].elem2.disabled = true;
  document.forms[1].elem3.disabled = true;
  
  url = "/bar.html";
  document.forms[2].location.href = url;
  return;
}
else if (document.forms[0].document.forms[0].foo.value == "3") {
  document.forms[1].elem1.disabled = true;
  document.forms[1].elem2.disabled = true;
  document.forms[1].elem3.disabled = true;
  
  url = "/baz.html";
  document.forms[2].location.href = url;
  return;
}
else {
  document.forms[1].elem1.disabled = true;
  document.forms[1].elem2.disabled = true;
  document.forms[1].elem3.disabled = true;
  
  url = "/qux.html";
  document.forms[2].location.href = url;
  return;
}

みたいなJSが平気で埋め込まれていたりして、「DRYとはいったい…ウゴゴゴゴ」という気持ちでいっぱいになります。
クライアントサイドを担当した人すごい。

商業システムってこんなんばっか。
これで目立ったバグがないならまだいいんだけど、たまに動かなくなるからな…。

【参考】

unkode-mania.net