📥

JavaScript の in の 闇

2023/09/30に公開

さて、あなたには この画像 が理解できますか?

そもそも in は何をしているのか?

x in array なので、配列(array)に対して指定の値が含まれている・・・と考えがちですが実は違います

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/in

in 演算子は、指定されたプロパティが指定されたオブジェクトにある場合に true を返します。

指定されたプロパティ、なのでキーが存在しているかどうかを確認するのがJavaScriptのin演算子です。

なので・・・

let l = [1, 2, 3, 4]

{
	0: 1,
	1: 2,
	2: 3,
	3: 4
}

このようにみると、0"0"trueが返り、4falseが返される理由がわかりますね。

false in [1, 2, 3, 4]は何を返すのか?

0でも"0"でもinのoperatorが使用できることがわかりました。
ここではStrictモードによる比較はされていないようなので、falseで検証してみるといかがでしょうか?

let l = [1, 2, 3, 4]
console.log(false in l)

// false

.

.

.

( ^ω^)はて?

想像していた結果と異なりました。
厳格な比較はされていないようですが、==による比較でもなさそうです・・・

なので、ECMAScriptの仕様書を追いかけてみましょう。

追跡 ECMAScript

in 演算子(ECMAScript)

https://tc39.es/ecma262/multipage/ecmascript-language-expressions.html#sec-relational-operators-runtime-semantics-evaluation

13.10 Relational Operators
13.10.1 Runtime Semantics: Evaluation
RelationalExpression : RelationalExpression in ShiftExpression

1. Let lref be ? Evaluation of RelationalExpression.
2. Let lval be ? GetValue(lref).
3. Let rref be ? Evaluation of ShiftExpression.
4. Let rval be ? GetValue(rref).
5. If rval is not an Object, throw a TypeError exception.
6. Return ? HasProperty(rval, ? ToPropertyKey(lval)).

6番の返り値部分にフォーカスして見てみましょう。

HasPropertyは、単純にプロパティが存在しているかどうかを検証しているようです。
https://tc39.es/ecma262/multipage/abstract-operations.html#sec-hasproperty

第1引数のrval(right value)は、名称からinの右側・・・オブジェクトまたは配列が該当します。
第2引数のlval(left value)は、名称からinの右側・・・存在するかどうかを確認しているキーの名称が該当します。
lvalToPropertyKeyというメソッドに渡されているので、次はこちらを追いかけてみましょう。

ToPropertyKey(ECMAScript)

https://tc39.es/ecma262/multipage/abstract-operations.html#sec-topropertykey

1. Let key be ? ToPrimitive(argument, STRING).
2. If key is a Symbol, then
	a. Return key.
3. Return ! ToString(key).

3番の返り値部分にフォーカスして見てみましょう。

最終的にはToStringに掛けられたキーが返却されているようです。
ここにfalseが渡った場合のケースを見てみましょう。

ToString(ECMAScript)

https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tostring

1. If argument is a String, return argument.
2. If argument is a Symbol, throw a TypeError exception.
3. If argument is undefined, return "undefined".
4. If argument is null, return "null".
5. If argument is true, return "true".
6. If argument is false, return "false".
7. If argument is a Number, return Number::toString(argument, 10).
8. If argument is a BigInt, return BigInt::toString(argument, 10).
9. Assert: argument is an Object.
10. Let primValue be ? ToPrimitive(argument, STRING).
11. Assert: primValue is not an Object.
12. Return ? ToString(primValue).

6番を見ると、falseが渡された際には"false"が返されていることがわかります。
この結果が最終的にHasPropertyの第2引数に渡されます。

false in [1, 2, 3, 4] の結論

array["false"] が 存在するかを検証している

例:左 in 右

  • 左が 0 の場合 -> array["0"] が存在するか
  • 左が "0" の場合 -> array["0"] が存在するか
  • 左が false の場合 -> array["false"] が存在するか

結論

不思議な挙動も、仕様を読み解いていくことで色んなことがわかりますね( ^ω^)

Discussion