🧊

Array.prototype.slice.call(arguments)とは

Array.prototype.slice.call(arguments);

っていう、いかにもJavaScriptを使いこなしている風なこの書き方。
もちろん最初はまったく意味がわからんかったです。

そして次に、意味はわかっても、使い道がわからんかったです。

けど最近、使い道がやっと見えてきました。

というわけで、その過程をメモ。

arguments

ざっくり引数のこと。

var func1 = function(name, arg1, arg2) {
  console.log(arguments); // ["Test", 100, 200]
};
var func2 = function(name) {
  console.log(arguments); // ["Test2", true, function]
};

func1('Test', 100, 200);
func2('Test2', true, function() {});

関数の実行スコープ内で勝手に作られるローカル変数で、それぞれ引数が配列っぽく格納されてる。
「配列っぽく」というのは、実際のArrayのインスタンスではないから。
DOMのNodeListみたく、配列っぽく使いたいのに使えないコって感じ。

Array.prototype.slice.call()

Array.prototype.****.call()

そこでコレ。

var divTags = document.querySelectorAll('div');

// divTags.forEach(function(e, i) { console.log(i + ': ' + e); });
// ってしたいのにできないので

Array.prototype.forEach.call(divTags, function(e, i) { console.log(i + ': ' + e); });

これと同じようなもので、本家Arrayのちからを借りたい時に使う。

Array.slice()

参考:Array.slice - JavaScript | MDN

var arr1 = [1, 2, 3];
var arr2 = arr1.slice();
var arr3 = arr1.slice(1);

console.log(arr2); // [1, 2, 3]
console.log(arr3); // [2, 3]
arr2 = arr3 = [];
console.log(arr1); // [1, 2, 3]

配列のコピーが欲しい時に使うやつですね。

コピーといえば、Array.sliceはシャローコピーで云々かんぬんというのがある気がするけど、
本筋とはあまり関係がない気がするのと、うまく説明できる気がしないので・・気になるけど頭の隅にそっと置いておく。

というわけで、以下の謎だったコードは、

Array.prototype.slice.call(arguments);

argumentsを配列に変換してるコードということがわかりました。
あとの引数はコピーを開始する位置ってことで、そのまんまsliceですね。

ちなみに、Array.prototype.** と [].** は同じ動きなのでお好きな方を。
短く書ける方がイケてるとかいう俗説があるらしい。

つかいみち

さてさて、理屈はわかったけど使い道がわからんわ!
ってのは割とよくある気がしてて、せっかくわかったなら是非モノにしたい!というわけで調べました。

Backbone.js

// Backbone.Events.triggerの実装(関係ないとこは削ってます)
trigger: function(name) {
  var args = slice.call(arguments, 1); // つかってるー!
  if (!eventsApi(this, 'trigger', name, args)) return this;
  var events = this._events[name];
  if (events) triggerEvents(events, args);
  return this;
},

なるほど。
明示的に受けたいのは、なんてイベント(name)をtriggerするかだけ。
後の引数はよしなに処理を渡したいので・・って感じですね、ふむ。

Underscore.js

// _.delayの実装より
_.delay = function(func, wait) {
  var args = slice.call(arguments, 2); // つかってる!
  return setTimeout(function(){ return func.apply(null, args); }, wait);
};

これも同じで、本来実装したい挙動に関するものにフォーカスして関数の実装は書く。
それでも引数は渡ってくることもあるし、ただ流せばいいだけやし、みたいな時に使うんですね。

ちなみに

そもそもこれはargumentsに限った話ではなくて、
いわゆるArrayライクなオブジェクトなら・・OKっていう。

var obj = {
  '0': 'zero',
  '1': 'one',
  '2': 'two',
  '3': 'three',
  '4': 'four',
  length: 5
};

Array.prototype.slice.call(obj); // ["zero", "one", "two", "three", "four"]

うん、ひとつかしこくなりました。

以下、直接関係ないけどいろいろ見てた記事さまたち

参考:$.extend()とディープコピーを理解しよう - slowjet
参考:Backbone.Modelのattributesにオブジェクト入れるときの注意 - Webtech Walker
参考:aheckmann/sliced