argumentsの挙動
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆複雑ですね。
JavaScriptで関数実行中に引数の情報が入るargumentsというのがあります
受け取らなかった引数も含めて関数実行時に送られた引数一覧が配列のようなオブジェクトに入っています
Arrayが継承されていないので配列系メソッドは残念ながら使えません
document.getElementsByClassNameの返り値やdocument.querySelectorAllの返り値のようにNodeListなどのconstructorによって作られていないのでarguments.constructorはObjectになっています
もちろんarguments.__proto__も{}です
argumentsに[a,b,c,d]の配列(のようなもの)が入っています
aとcでは直接 bとdではargumentsを通して変数を書き換えます
変数を書き換えた時もargumentsを書き換えた時も 書き換えていない方にも影響していますね
普通の配列とプリミティブ値の場合だと下のようになります
配列→プリミティブ値、配列を変更
aa:配列
ap:プリミティブ値
配列→プリミティブ値、プリミティブ値変更
プリミティブ値→配列、配列変更
プリミティブ値→配列、プリミティブ値変更
どの場合も変更したものだけ変わっていて 変更していない方はそのままです
これが普通の動きです
しかし argumentsは特殊なので両方が変わります
2は-2になるけど1はそのまま
a(1,2)の方を見ると-3は変数cの方だけで-4はargumentsだけに入っています
関数実行時にundefinedになっているものは変更しても変更した方しか反映されないようです
これをChromeでやってみると……
なにこれ!?バグった!!!
-3がcだけで-4がargumentsだけというのは一緒です
一緒なのですがそれ以降がおかしなことになってます
undefinedがいっぱい並んでます
どうしてこうなった!
最後の2はargumentsのlengthプロパティを表示したものなのでundefinedがいっぱいですが長さは2らしいです
デフォルトのargumentsだと2つだけど argumentsに書き込んでいるから値が入っているのは3つです
配列じゃないのでlengthプロパティは自動更新されないようです
これはFirefoxも一緒です
それはいいのですが このundefinedの山 こいつらをどうしてくれようか……
argumentsを書き換えること自体がやるべきことじゃないと思うので想定外のことすると実装依存なのかなということでとりあえず納得する
バージョン36~43で確認してます
1の関数ではargumentsや引数を変更しません
その代わり引数としてargumentsを2の関数に渡します
2の関数では受け取った1の関数のargumentsに編集をします
f:1ではそのままargumentsを渡して 2で編集する
g:1ではapplyで引数を渡して 2で編集する
h:gと同じだけど参照渡しになる配列をh1に渡す
fの結果は予想通りだと思います
普通に書き換えられます
gはapply使っているので変更はg2の関数内だけになります
applyは配列をそれぞれの引数に展開しているので
applyの第一引数は今回関係ないので略してます
(nullなので.bind(window)するだけです)
hはy[0]にあたる部分をプリミティブ値ではなく配列にして参照になるようにしてます
そのおかげでh2の書き換えがh1でも反映されてます
複雑ですね☆
受け取らなかった引数も含めて関数実行時に送られた引数一覧が配列のようなオブジェクトに入っています
Arrayが継承されていないので配列系メソッドは残念ながら使えません
document.getElementsByClassNameの返り値やdocument.querySelectorAllの返り値のようにNodeListなどのconstructorによって作られていないのでarguments.constructorはObjectになっています
もちろんarguments.__proto__も{}です
配列とは違うのだよ、配列とは
function a(a,b,c,d){
console.log(a,b,c,d,arguments);
a = -1;
arguments[1] = -2; //b
c = -3;
arguments[3] = -4; //d
console.log(a,b,c,d,arguments);
console.log(arguments.length);
}
argumentsに[a,b,c,d]の配列(のようなもの)が入っています
aとcでは直接 bとdではargumentsを通して変数を書き換えます
変数を書き換えた時もargumentsを書き換えた時も 書き換えていない方にも影響していますね
普通の配列とプリミティブ値の場合だと下のようになります
配列→プリミティブ値、配列を変更
aa:配列
ap:プリミティブ値
配列→プリミティブ値、プリミティブ値変更
プリミティブ値→配列、配列変更
プリミティブ値→配列、プリミティブ値変更
どの場合も変更したものだけ変わっていて 変更していない方はそのままです
これが普通の動きです
しかし argumentsは特殊なので両方が変わります
function b(a,b){
var aa = a;
console.log(a,aa,b,arguments);
aa = -1;
arguments[1] = -2; //b
console.log(a,aa,b,arguments);
}
2は-2になるけど1はそのまま
引数が足りないときは
諸事情で先にFirefoxの結果を載せますa(1,2)
undefined
1 2 undefined undefined Arguments { 0: 1, 1: 2, 他 2 個... }
-1 -2 -3 undefined Arguments { 0: -1, 1: -2, 3: -4, 他 2 個... }
2
4
2
a(1,2,3,4)
undefined
1 2 3 4 Arguments { 0: 1, 1: 2, 2: 3, 3: 4, 他 2 個... }
-1 -2 -3 -4 Arguments { 0: -1, 1: -2, 2: -3, 3: -4, 他 2 個... }4
a(1,2)の方を見ると-3は変数cの方だけで-4はargumentsだけに入っています
関数実行時にundefinedになっているものは変更しても変更した方しか反映されないようです
Chromeで謎の挙動
これをChromeでやってみると……
なにこれ!?バグった!!!
-3がcだけで-4がargumentsだけというのは一緒です
一緒なのですがそれ以降がおかしなことになってます
undefinedがいっぱい並んでます
どうしてこうなった!
最後の2はargumentsのlengthプロパティを表示したものなのでundefinedがいっぱいですが長さは2らしいです
デフォルトのargumentsだと2つだけど argumentsに書き込んでいるから値が入っているのは3つです
配列じゃないのでlengthプロパティは自動更新されないようです
これはFirefoxも一緒です
それはいいのですが このundefinedの山 こいつらをどうしてくれようか……
argumentsを書き換えること自体がやるべきことじゃないと思うので想定外のことすると実装依存なのかなということでとりあえず納得する
バージョン36~43で確認してます
argumentsを引数で渡す
function f1(a,b){
console.log("F1",a,b,arguments);
f2(arguments);
console.log("F1",a,b,arguments);
}
function f2(args){
args[1] = -1;
}
function g1(a,b){
console.log("G1",a,b,arguments);
g2.apply(null, arguments);
console.log("G1",a,b,arguments);
}
function g2(a,b){
console.log("G2", a,b,arguments);
a = -2;
arguments[1] = -3;
console.log("G2", a,b,arguments);
}
function h1(a,b){
console.log("H1",a,b,arguments);
h2.apply(null, arguments);
console.log("H1",a,b,arguments);
}
function h2(a,b){
console.log("H2", a,b,arguments);
a[0] = -2;
arguments[1][0] = -3;
console.log("H2", a,b,arguments);
}
1の関数ではargumentsや引数を変更しません
その代わり引数としてargumentsを2の関数に渡します
2の関数では受け取った1の関数のargumentsに編集をします
f:1ではそのままargumentsを渡して 2で編集する
g:1ではapplyで引数を渡して 2で編集する
h:gと同じだけど参照渡しになる配列をh1に渡す
fの結果は予想通りだと思います
普通に書き換えられます
gはapply使っているので変更はg2の関数内だけになります
applyは配列をそれぞれの引数に展開しているので
x.apply(null, y);
は
x(y[0], y[1], y[2], ....);
となっていてy[0]やy[1]がプリミティブ値なら変更されないということですapplyの第一引数は今回関係ないので略してます
(nullなので.bind(window)するだけです)
hはy[0]にあたる部分をプリミティブ値ではなく配列にして参照になるようにしてます
そのおかげでh2の書き換えがh1でも反映されてます
複雑ですね☆