5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

arguments オブジェクト詳説 [ES2019]

Last updated at Posted at 2019-11-10

イントロダクション

関数の中では引数の値がすべて入った arguments オブジェクトという特別な変数を使用でき、可変長引数を実現したいときなどに使われていた。しかし、今や rest parameter 構文があるので arguments を使う必要はほぼなくなったと言っていいだろう(IE など一部ブラウザは対応していないが、Babel などで transpile すればいいだけの話である)。もし自分のコード上で arguments を使わざるを得ない状況になったときこの記事が役立つだろう。

なおこの記事はECMAScript 2019 Language Specification に基づいている。

arguments オブジェクトとは?

一部の関数の中では arguments という変数(予約語やキーワードではない)が自動的に作られる。その値は引数が順番に入った Array のようなものである(Array ではない)。

function getArgs() {
  return arguments
}

const args = getArgs("a", "b", "c")
console.log(args[0])  // a
console.log(args[1])  // b
console.log(args[2])  // c

arguments オブジェクトが使えない条件

以下のいずれかが成り立てば、arguments オブジェクトは使用できない。arguments を使おうとしている場所を含んでいる最も内側の関数をFとする。

① FとFを包む関数がすべてアロー関数である
② Fの引数のいずれかの名前が arguments
③ Fの中で宣言された関数と letconst で宣言された変数のいずれかの名前が arguments

②と③については変数のシャドーイングのようなことが起こり、本来の arguments オブジェクトにアクセスできなくなる(正確にはそもそも arguments オブジェクトが生成されない)。

!(function (x, y) {
  let arguments
  console.log(arguments)  // undefined
})(2, 3, 4)

一方、vararguments を宣言した場合、その変数は本来の arguments オブジェクトそのものとなる。

!(function () {
  console.log(arguments[0])  // 2
  var arguments              // <- あってもなくても同じ
  console.log(arguments[0])  // 2
})(2)

宣言時に代入すれば当然値は変わる。

!(function () {
  console.log(arguments[0])  // 2
  var arguments = "args"
  console.log(arguments)     // args
})(2)

NOTE: ③だけ成り立っている場合では、実は引数のデフォルト値を指定する部分では arguments にアクセスできる。後述するが、

!(function (x, y = arguments[0]) {
  let arguments = [100]
  console.log(y)  // 2
})(2)

arguments オブジェクトは2種類ある

arguments オブジェクトには unmapped なものと mapped なものがあり、前者は普通のオブジェクトだが、後者は引数の値と同期しているという性質がある。関数が呼び出された時、状況によってどちらか一方が作られる。

unmapped arguments オブジェクトになる条件

以下のいずれかが成り立てば unmapped arguments オブジェクトになる。逆にどれも成り立たなければ mapped arguments となる。

  • strict mode
  • rest parameter がある (e.g. function (a, b, ...args) { })
  • デフォルト値が指定されている (e.g. function (a = 0) { })
  • destructuring している (e.g. function ({ name, age }, x) { })

最近では strict mode でコーディングするのが普通なので、私達が遭遇するのはほとんど unmapped arguments オブジェクトであると思われる。

共通の性質

unmapped、mapped 共通の性質を説明する。

iterable である

[Symbol.iterator] プロパティを持っているため iterable であり、for-of でループしたり spread 構文で使用できる。

!(function () {
  for (const arg of arguments) {
    console.log(arg)
  }
})(1, 2, 3)

array-like である

arguments は Array と同じ形で要素をもっており、かつlength プロパティが引数の個数になっているため、Array のメソッドを適用すると予想通りの振る舞いをする。

!(function () {
  const arr = Array.prototype.slice.call(arguments)
  console.log(arr)  // [1, 2, 3]
})(1, 2, 3)

array-like の定義については筆者の👇の記事で詳しく説明している。

array-like object っていったい何?iterable との違いは?言語仕様に立ち返って説明する

toString メソッドは "[object Arguments]" を返す

これは独自の toString メソッドを実装しているわけではなく、Object.prototype.toString() メソッドの仕様である。

!(function () {
  console.log(arguments.toString())  // [object Arguments]
})(1, 2, 3)

共通のプロパティ

プロパティの種類1 writable? enumerable? configurable? 備考
0, 1, ... data Yes Yes Yes 各引数の値。
length data Yes No Yes 関数に実際に渡された引数の数となる。Array と違って要素を追加/削除しても変動することはない。
[Symbol.iterator] data Yes No Yes Array の values() メソッドと全く同じ。

extensible であるため、新しいプロパティを追加することもできる。

unmapped arguments の性質

callee プロパティ

callee プロパティを取得/代入するとエラーが発生する。

プロパティの種類1 writable? enumerable? configurable? 備考
callee accessor - No No get/set すると TypeError が発生する。

mapped arguments の性質

mapped arguments は exotic object と呼ばれる「通常のオブジェクトとは異なる振る舞いをするオブジェクト」の一種であり、実際、通常のオブジェクトでは実現できない振る舞いをする。

callee プロパティ

callee プロパティには、その arguments オブジェクトに対応する関数がセットされている。

プロパティの種類1 writable? enumerable? configurable? 備考
callee data Yes No Yes 呼び出されている関数

arguments の要素と引数の値が同期している

NOTE: 「要素」=「プロパティ名が 012 ...であるプロパティ」という意味である。

引数の値と arguments の対応する要素の値は同期しており、どちらかの値を変えるともう一方も変わる。

!(function (x) {
  console.log(x)             // 1
  arguments[0] = 2
  console.log(x)             // 2
  x = 3
  console.log(arguments[0])  // 3
})(1)

ただし、値が渡されていない引数については同期しない。

!(function (x, y, z) {
  arguments[2] = 10
  console.log(z)             // undefined

  z = 20
  console.log(arguments[2])  // 10
})(1)

同期しなくなる条件

同期している引数も、以下の操作をすることで同期しなくなる。なお、これは不可逆であり、一度同期が切れると(その関数呼び出しに限り)戻ることはない。

NOTE: 一つの要素に対して以下のようなことをしても、すべての要素が同期しなくなるわけではない。あくまで要素ごとに同期する/しないがある。

要素を delete する

arguments オブジェクトの要素を delete すると、たとえ再び同じインデックスの要素を作っても同期されないままになる。

!(function (x) {
  console.log(`x=${x} arguments[0]=${arguments[0]}`)

  delete arguments[0]
  arguments[0] = 2

  console.log(`x=${x} arguments[0]=${arguments[0]}`)
})(1)
x=1 arguments[0]=1
x=1 arguments[0]=2

writable 属性を false にする

Object.definePropertyarguments の要素の writable 属性 を false にすることで同期されなくなる。ただし、同時に value も指定すると、それは引数の変数に反映される。再び writable 属性を true にしても同期されるようにはならない。

!(function (x) {
  console.log(`x=${x} arguments[0]=${arguments[0]}`)

  Object.defineProperty(arguments, "0", {
    writable: false,
    value: 2,          // <- この値は x にも反映される
  })

  console.log(`x=${x} arguments[0]=${arguments[0]}`)

  Object.defineProperty(arguments, "0", {
    writable: true,
  })
  arguments[0] = 3
  
  console.log(`x=${x} arguments[0]=${arguments[0]}`)
})(1)
x=1 arguments[0]=1
x=2 arguments[0]=2
x=2 arguments[0]=3

accessor property に変える

arguments の要素はすべて data property1 であるが、それを accessor property1 に変えると同期しなくなる。

!(function (x) {
  console.log(`x=${x} arguments[0]=${arguments[0]}`)

  let inner = 2
  Object.defineProperty(arguments, "0", {
    get: () => inner,
    set: v => inner = v,
  })

  console.log(`x=${x} arguments[0]=${arguments[0]}`)

  arguments[0] = 3
  
  console.log(`x=${x} arguments[0]=${arguments[0]}`)
})(1)
x=1 arguments[0]=1
x=1 arguments[0]=2
x=1 arguments[0]=3
  1. プロパティには2種類あり、data property は普通のプロパティ、accessor property は getter と setter によって定義されるプロパティである。 2 3 4 5

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?