JavaScriptのおかしなところについて

# 初めに
JavaScriptを書くとやっぱり完全に理解していないなあ、このエラーをどう解釈していくかとか自分にはよくある話です。今回はJavaScriptのちょっとおかしなところと、エンジンがどう処理しているか一緒に考えてみたい、そして何よりもどう対処していくかについてまとめたいと思います。

[What the... JavaScript?](https://youtu.be/2pL28CcEijU)
（Kyle Simpsons氏の映像です。一部の問題がすでに修正されている。）

# Equality (==) & Strict equality (===)
[Equality (==)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality)
[Strict equality (===)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality)
[JS Comparison Table](https://dorey.github.io/JavaScript-Equality-Table/)
```jsx
console.log([] == ![]) // true
// console.log(![] == false) // true
// console.log([] == false) // true

// console.log({} == !{}) // false

console.log([] === ![]) // false
```
Kyle Simpsons氏は映像で解釈したのですが、やはりエンジンがどうしてこのように解釈しているのかわかりません...！

Equality (==)ではなくStrict equality (===)を使えってよく言われると思います。上のように`![]`はEquality (==)でエンジンが変な判断をして`Logical NOT (!)`がついてるのに無視されて`true`を出した。
では同じロジックなら`!{}`も同じ判断をするべきだと思うが、`!{}`が`false`を出した...。Kyle Simpsons氏曰くオブジェクトは数値に変換されるようで、つまり`!{}`は`!0`と同じのように扱われ、自然と`false`になった。

別の例なら、
```jsx
console.log([] + {}) // [object Object]
console.log([] + {} == [{}]) // true
console.log({} + []) // [object Object]
console.log({} + [] == [{}]) // true

console.log([{}] == {} + []) // true
console.log([{}] == [] + {}) // true
console.log([{}] == [{}] + []) // true
console.log([{}] == [{}] + [] + []) // true
console.log([{}] == [{}] + [] + {}) // false
```
**（頭を抱えて）何これ、全然わからない！**
やはりEquality (==)やめましょうね。

（前の文章で時々[The Modern JavaScript Tutorial](https://javascript.info/)の例がEquality (==)が出てくるけど、自分はテストで書き換えると直す癖があるので修正しましたが、実際に値を許容するために敢えてEquality (==)を使うプログラマがいるらしいです。自分はできるだけEquality (==)やInequality (!=)避けたいです。）

自分の書き方としては`![]`と`!{}`とは書きません。判断を任せたいというのは値ではなく演算子です。`NOT value`じゃなく`NOT equal`ですから。

コードが思うように動かせるために、段階的に自分の書き方をテストし確認してから次のコードを書く、デバッグにも作業の効率にもいいと思います。（`console`が面倒なら`Jest`など使ってもいい。）

# coercion
型強制
[Number constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#constructor)
[String constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#constructor)
```jsx
console.log(Number('')) // 0
console.log(Number(' ')) // 0
console.log(Number('\r\n\t')) // 0 // new line Regex

console.log(Number('-0')) // -0
console.log(JSON.parse('-0')) // -0
console.log(Number('- 0')) // NaN

console.log(JSON.stringify(-0)) // 0 // string
console.log(String(-0)) // 0 // string
console.log(-0 + '') // 0 // string
```
`Number()`は文字列を数値に変換する動きで、emptyやspace、改行の正規表現を`0`にします。いざ`-`はspaceに遭うと、こんなの数字にはならない、非数です！って`NaN`に変換したのはまだ理解できる。
```jsx
console.log(Number('0.')) // 0
console.log(Number('.0')) // 0
console.log(Number('.')) // NaN

console.log(Number(undefined)) // NaN
console.log(Number(null)) // 0

console.log(Number({})) // NaN
console.log(Number([])) // 0

console.log(Number('0O0')) // 0
console.log(Number('0X0')) // 0
// console.log(Number('0a0')) // NaN
// console.log(Number('0b0')) // 0
```
`undefined`が値が未定義のままで非数、`null`が元から無しで`0`。
`{}`はオブジェクトが数値化にされるので`NaN`（`NaN`は数値型）に、`[]`は`empty value`で`0`、ふむふむ。

あれ、`Number('0O0')`、`Number('0X0')`は`0`だと？
さき`-`とspaceの組み合わせが非数なのに、`0`と文字の`O`、`X`が非数にはならないのはなぜ？もちろん考えても答えが出ませんので、どう処理していくかは自分なりに考えるしかありません。
```jsx
// only for string converting to number
function checkNaN(str) {
  if (Number(str) === NaN) return true
  if (typeof str !== 'string') return 'Invalid value'
  if (str[0] === '0' && str[1] === '.') return 'Decimals'

  let check = str.split('').filter(x => Number.isNaN(Number(x)))

  return check.length > 0
}

console.log(checkNaN('0O0')) // true
console.log(checkNaN('0X0')) // true
console.log(checkNaN('{}')) // true
console.log(checkNaN('[]')) // true
console.log(checkNaN({})) // Invalid value
console.log(checkNaN([])) // Invalid value

console.log(checkNaN('0.0314E+2')) // Decimals
console.log(checkNaN('9007199254740991n')) // true
// check with other function
```
これはデモのために書いてみたものです。小数や巨大数など正しく処理できません。もっといい判定方法や処理があります。

次は数値から文字列です。
```jsx
console.log(JSON.stringify(-0)) // 0 // string
console.log(String(-0)) // 0 // string
console.log(-0 + '') // 0 // string
```
文字列だから正数と負数の概念がないかな、でもnegative`-`時々文字列として保管しておいて必要なとき数値に扱われることもあるので、勝手に`-`が消されたら困りますね。

さらに、
```jsx
console.log(String({})) // [object Object]
console.log(String([])) // ''

console.log(String(null)) // 'null'
console.log(String([null])) // ''

console.log(String(undefined)) // 'undefined'
console.log(String([undefined])) // ''

console.log(String([null, null,])) // ','
console.log(String([undefined, undefined,])) // ','
console.log(String([, ,])) // ','
```
うん、わかりません。`undefined`が`undefined`のままでいてほしいときもあって、`null`だって`empty slot`とは違う概念だと思う。

少し時間をかけて書いてみましたが、巨大数と小数はとりあえず処理から外しています。
```jsx
// for array or number (not including Decimals and BigInt)
function NumToStr(item) {
  function isSafe(value) {
    // if (value === undefined) return undefined // optional
    // if (value === null) return null // optional
    if (Number.isSafeInteger(value)) {
      if (value === 0 && (1 / value) === -Infinity) return '-0'
      if (value === 0 && (1 / value) === Infinity) return '0'
      return String(value)
    }
    return NaN
  }

  if (typeof item === 'object') {
    if (Array.isArray(item)) {
      if (item.length === 0) return 'Invalid value'
      return item.map(x => isSafe(x))
    }
    return 'Invalid value'
  }

  if (typeof item === 'number') {
    return isSafe(item)
  }

  return 'Invalid value'
}
```
（inputが配列や数値、outputが数値と非数の分別つける状況を想定している。）
数値から文字列への処理が手順がかかると思います。これもただのデモコードで、配列と数値だけ受け入れるパターンです。一番に改善してほしいのは`-0`と`+0`の判断なので、巨大数や小数など緻密な処理が必要なので省けました。`undefined`と`null`も`NaN`として扱います。

```jsx
console.log(NumToStr({})) // Invalid value
console.log(NumToStr([])) // Invalid value
console.log(NumToStr(null)) // Invalid value
console.log(NumToStr([null])) // [ NaN ]
console.log(NumToStr(undefined)) // Invalid value
console.log(NumToStr([undefined])) // [ NaN ]
console.log(NumToStr([null, null,])) // [ NaN, NaN ]
console.log(NumToStr([undefined, undefined,])) // [ NaN, NaN ]
console.log(NumToStr([, ,])) //[ <2 empty items> ]

console.log(NumToStr([1, -1, +0, -0, 0.5, -0.5, undefined, null, ,]))
// [ '1', '-1', '0', '-0', NaN, NaN, NaN, NaN, <1 empty item> ]
console.log(NumToStr(0.0314E+2)) // NaN
console.log(NumToStr(9007199254740991n)) // Invalid value
```

また、下のようにオブジェクトの生成には`null`を使うと変な動きが発生してしまいます。
```jsx
let o1 = {
  hello: 'world'
}

let o2 = Object.create(null)
o2.hello = 'world'

console.log(o1 + '') // [object Object]
console.log(o1.toString()) // [object Object]
// console.log(o2 + '') // TypeError: Cannot convert object to primitive value
console.log(o2) // [Object: null prototype] { hello: 'world' }
console.log(null.prototype) // TypeError: Cannot read properties of null (reading 'prototype')
console.log(null.__proto__) // TypeError: Cannot read properties of null (reading '__proto__')
// note: null has no prototype, and it is the end of .__proto__
```
`null`を`{}`に変えたら上手く動いてくれるようになるが、それは`null`と`{}`根本的に違うもので、`null`自身がプリミティブとして、プロトタイプチェーンの終点として存在している時点、JavaScriptでは違う概念で扱われていると思います。

勉強すればするほどJavaScriptの型変換が不安定で、ありきたりな特殊の存在が自分のルールで動いている感じになっている。もっと統合的で予想外のないのができていたらいいなあとよく思うが、これもJavaScriptの魅力だと思います。

# empty slot
配列の**empty slot**がメソッドによってどう処理されるか、ここにまとめてみました。

https://qiita.com/hu-yu/items/21490dcb8fa5e0ce9256#undefined-vs-empty-slots

（この文章では`[]`、`{}`、`''`が`empty value`で、コンソールが`<1 empty item>`表示するところが`empty slot`と称しています。）

`empty slot`が難しい。どうやって扱うのか難しい。
私の知っている`empty slot`の特徴を少しまとめたいと思う。
- **`empty value`がlengthがないのに対し、`empty slot`はlengthにカウントされる。**
- **`undefined`と`null`と違い、型がありません。**
- **`for`ループで走査することができる。**

型がないので`typeof`で排除できない。lengthがあるので普通に`empty value`のように扱うことができない。うまく排除しても配列内のほかの要素のindexが変えられてしまいます。データ処理でとても厄介な存在だと思います。

```jsx
// empty slot
console.log(Array(3)) // [ <3 empty items> ]

let arr1 = []
arr1.length = 3
console.log(arr1) // [ <3 empty items> ]
console.log(arr1[0]) // undefined

let arr2 = [undefined, undefined, undefined]
console.log(arr2) // [ undefined, undefined, undefined ]
console.log(arr2[0]) // undefined
```
`empty item`はね、後で値を入れるから先にそこで穴を開けといて！って感覚だと私は思います。しかしその穴に入ってる値は未定義だからアクセスしても`undefined`になるわけです。

でも、それはつまりエンジンは`empty item`と`undefined`と一緒に認識しているってことですか？それはないと思います。結果としては確かに`undefined`ですが、元と言えば穴と未定義で見つからないこととは根本的に違うと思っています。

Kyle Simpsons氏が映像で提示した`join()`と`map()`の例です。
```jsx
console.log([undefined, undefined, undefined].join('+')) // ++
console.log([undefined, undefined, undefined].map(x => '+')) // [ '+', '+', '+' ]

console.log([null, null, null].join('+')) // ++
console.log([null, null, null].map(x => '+')) // [ '+', '+', '+' ]

console.log([, , ,].join('+')) // ++
console.log([, , ,].map(x => '+')) // [ <3 empty items> ]
```
`join()`と`map()`処理の違いは値の扱い方にあるとKyle Simpsons氏が言いました。あくまでも自分の考えですが、`join()`は結果としての値を処理することに対し、`map()`は値そのまま扱うのではないかと。
`join()`：`empty slot`も`undefied`も値として`undefined`、つまり存在しないというロジックで処理を行う。
`map()`：`undefined`は`undefined`、`null`は`null`、値そのままですが、`empty slot`もそのままです。

`map()`ではなく`filter()`でやると楽だと思います。
```jsx
console.log([undefined, undefined, undefined].filter(x => !x === false)) // []
console.log([null, null, null].filter(x => !x === false)) // []
console.log([, , ,].filter(x => !x === false)) // []
```
（`map()`、`filter()`、`reduce()`はES6からのメソッドです。）
[Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
[Array.prototype.filter()](https://edeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)
[Array.prototype.reduce()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)


# `finally{}` & `return`
**ここからは構文の行いの解釈の部分が多く、あまり対処方法がないのですが。**
```jsx
// finally
function foo() {
  try {
    return 2
  } finally {
    return 3
  }
}

console.log(foo()) // 3
```
`try...catch...finally`構文では確かに`finally`が必ず実行するのですが、`try`ですでに実行した`return 2`が`finally`の`return 3`に上書きされました。

```jsx
function foo() {
  try {
    return 2
  }
  finally {
    // return
  }
}

console.log(foo()) // 2
```
`return`は外側に戻る手段の一つとして使われます。普通の関数はデフォルトの返り値が`undefined`（指定の値がなければ`return`書かなくとも`undefined`）。
ここ`finally`を`return`使わない場合は正常に`try`の`return`が実行します。
```jsx
function foo() {
  bar: {
    try {
      console.log('I try!')
      return 2
    }
    finally {
      break bar
    }
  }
  console.log('oops')
}

console.log(foo())
// I try!
// oops
// undefined
```
しかし`finally`が`return`の後に実行することで`break`は`return`の実行をキャンセルして、`foo()`が`return`できる値がなく`undefined`になった。

返し値を上書きしたり、上の例のように`return`を止めることもできる`finally`の動き、映像を見て初めて知りました。しかしこれは構文自体の行いなので気を付けないといけないと思います。

# generator & `finally{}`
```jsx
// generator
function* foo() {
  console.log('a: ', yield 1)
  console.log('b: ', yield 2)
}

let generator = foo()

console.log(generator.next())
// { value: 1, done: false }

console.log(generator.next(5))
// a:  5
// { value: 2, done: false }

console.log(generator.next(10))
// b:  10
// { value: undefined, done: true }
```
最初`yield`双方向の特性にびっくりしました。（内部からだけでなく外側の値、関数もアクセスできるなんて便利すぎる...。）
[about yield](https://qiita.com/hu-yu/items/ea2164c0bb448c9c3934#about-yield) `yield`の動きなら少し前ジェネレータについて書いた文章ですが、ご参考になれば幸いです。

`yield`の動きを分かればその特性を利用していろいんなことができると思いますが、問題なのは`finally`との相互作用です。
```jsx
function* foo() {
  try {
    console.log('a: ', yield 1) // 1
    console.log('b: ', yield 2)
  }
  finally {
    console.log('finally!') // 2
    console.log('c: ', yield 3) // 2 // 3, yield 3 => 25
    // return  // 2,  undefined => 7
  }

  console.log('d: ', yield 4)
}

let generator = foo()

console.log(generator.next())
// { value: 1, done: false }

console.log(generator.return(7))
// finally! // *
// { value: 3, done: false }

console.log(generator.next(25))
// c:  25 // *
// { value: 7, done: true }

console.log(generator.next(42))
// { value: undefined, done: true }
```
全体の動きは、
`console.log(generator.next())`
　`yield 1` // { value: 1, done: false }
↓
`console.log(generator.return(7))`
　(return undefined => 7)
　`console.log('finally!')` // finally!
　`yield 3` // { value: 3, done: false }
↓
`console.log(generator.next(25))`
　(`yield` 3 => 25)
　`console.log('c: ', 25)` // c:  25
　`return 7` // { value: 7, done: true } **(state changed!)**
↓
`console.log(generator.next(42))`
　// { value: undefined, done: true }

`finally`と`return`の相互作用でジェネレータのイタレーションの順序がぐちゃぐちゃになっていて、本来なら`.return(7)`で終わるべきなのに、`finally`なかのすべての`yield`を終わらせなければ`return`は実行しません。

# object destructuring & default param values
```jsx
function foo() {
  let x = 2
  let y = 3
  return {
    x: x,
    y: y
  }
}

// let o = foo()
// let x = o.x
// let y = o.y

// console.log(x, y) // 2 3

let { x, y } = foo() // referencing the same property name
console.log(x, y) // 2 3
```
自分にはまだ慣れてない書き方です。
自分の理解では、destructuringはpropertyが単体のパズルとして扱われて、右手（Right-hand side）は値のコンテキストで、アクセスの仕方は`property name`です。

```jsx
function foo1() {
  let XX = 2
  let YY = 3
  return {
    x: XX,
    y: YY,
    z: 4
  }
}

let { x, y } = foo1()
console.log(x, y) // 2 3
```
```jsx
function foo2() {
  let XX = 2
  let YY = 3
  return {
    x: XX,
    y: YY
  }
}

let { x: BAM, y: BAZ } = foo2()

console.log(BAM, BAZ) // 2 3
// BAM -> x -> foo2() {x: XX} -> 2
// BAZ -> y -> foo2() {x: YY} -> 3

// test
BAM = 10
console.log(BAM) // 10
console.log(foo2()) // { x: 2, y: 3 }
```
ここはちょっと不思議なことが起きました。
`console.log(BAM, BAZ)`で値をアクセスできるというのは、`BAM`と`BAZ`も`let`で宣言されたんですね。
実際`BAM`にreassignしてみたら`BAM`の値が変わりましたが、`foo2()`内の値はそのままです。つまり元は、
```jsx
let { x, y } = foo2()
let BAM = x
let BAZ = y
console.log(x, y) // 2 3
```
こんな感じですね。

下からはデフォルト引数と関わる部分です。
```jsx
function foo3({ x, y }) {
  console.log(x, y)
}

foo3({ y: 10, x: 25 }) // 25 10
foo3({ y: 5 }) // undefined 5

function foo4({ x: XX, y: YY }, z) {
  console.log(XX, YY, z)
}

foo4({ y: 10, x: 25 }, 50) // 25 10 50
```
関数ならまず引数の位置、そしてオブジェクトから`property name`に沿って値をアクセス。

しかしデフォルト引数と関連したらちょっと理解しにくいところがあって、
```jsx
function foo5(x = { y: 10 }, { y = 20 } = {}) {
  console.log(x.y, y)
}

foo5() // 10 20
foo5({ y: 30 }, { y: 40 }) // 30 40

foo5({}, {}) // undefined 20
```
まず`foo5()`のデフォルト引数はこう解釈できると思います。
```jsx
let x = {
  y: 10
}
let { y = 20 } = {}

console.log(x.y, y) // 10 20
```
`foo5()`のようにデフォルト値がちゃんとアクセスできて、
`foo5({ y: 30 }, { y: 40 })`も`y`という`property name`があるから間違わない。

最初は何も思わなかったけど、`foo5({}, {})`がだめになったのは両方引数の解釈が違うことに直結していると気づきました。

つまり`x = { y: 10 }`は右手（Right-hand side）から左手（Left-hand side）、`{ y = 20 } = {}`は左手から右手。
destructuringはアサインの逆行とも言えるでしょう。

そしたら`foo5({}, {})`は`x = {}`、`{y = 20} = {}`になり、
`x.y`が存在しないので`undefined`、
`y`は同じ`property name`で上書きされてなければ元のままです。

`y`は単なるオブジェクトのプロパティではなかったの？
私も同じ疑問を持ったが、destructuringはそれだけではなかったと思います。
```jsx
let { y = 20 } = {}
let y = 10 // SyntaxError: Identifier 'y' has already been declared
```
destructuringでは`{}`中身も宣言されていますね。さきの`BAM`と同じです。
`{y = 20} = {}`をRight-hand sideのルールで書き換えてみたら、
```jsx
let y = 20
let obj = {}
obj.y = y
console.log(obj.y, y) // 20 20
```
完全ではないけれど感覚としては近いと思います。


# classes
```jsx
// classes
class Px {
  another() { // Px.prototype
    console.log('Px: another')
  }
}

class Cx extends Px {
  something() { // Cx.prototype
    console.log('Cx: something')
  }
  another() { // Cx.prototype
    this.something()
    super.another()
  }
}

let x = new Cx()

class Py {
  another() { // Py.prototype
    console.log('Py: another')
  }
}

class Cy extends Py {
  something() { // Cy.prototype
    console.log('Cy: something')
  }
  another() { // Cy.prototype
    this.something()
    super.another()
  }
}

let y = new Cy()

x.another()
// Cx: something
// Px: another
y.another()
// Cy: something
// Py: another

y.another.call(x)
// Cx: something // this = x
// Py: another // super => [[HomeObject]]: [class Py]
```
なぜ`class Cx`と`class Cy`の`another()`が各自の拡張先の`another()`を上書きしないというのは、そもそも各自の`another()`は自分の`.prototype`に保存されてるんです。

`this`指定がなければ、プロトタイプチェーンでのアクセス順は、
**親constructor()
↓
親フィールド
↓
自分のprototype
↓
親のprototype**。

（前はちょうど関連文章[overriding class field](https://qiita.com/hu-yu/items/a60c2cf282dfee398b77#overriding-class-field)を書きました。ご参考になればと。）

`this`で今いるコンテキストを指定すると、まず同じコンテキストから探し、なければプロトタイプチェーンへ。先着順みたいに最初に見つかった同じ名前のメソッドを実行する。`x.another()`と`y.another()`は問題なく自分の`.prototype`から`something()`を見つけ、そして`super`で親の`another()`へアクセス。

`y.another.call(x)`では確かに`call()`で強制的`this`の参照先が`class Cx`に変えたけれど、`super`は自フィールドと`extends`拡張先と結びつけるために[[HomeObject]]を参照するので、`call()`に影響されないんです。（逆にもし`call()`で`super`が変えられるのなら`extends`の意味がなくなると思う。）

JavaScriptの`this`は動的で、メソッド、書き方（アロー関数）、キーワード（`new`）、環境（グローバルかローカルか）に依存して、何かあっちもこっちも手伝いたくて時に自分の原点が見失うみたいな、非常に厄介だけどいとおしい存在だと思います。

# まとめ
（初投稿でなぜかこの部分がなくなった。）
最後まで読んでいただきありがとうございます。
JavaScriptの動きが確かにおかしなところがたくさんあるので扱いにくいですね。

今回の文章について感想として少し書きたいと思います。
構文上の問題ならちゃんとチェックしてから使うとか、概念の問題なら慣れれば？そつなくこなせると思いますが、自分にとって一番困るのはやはり型変換で起こすエラーと、そのたびに対処メソッドを考えることです。（デモで書いたコードは本当に雑で穴だらけなのでそのまま使っちゃだめですよ！）
それに対してTypeScriptの良さと魅力これまで別の方の文章で感じたんですが、まだもうちょっとJavaScriptと付き合いたいです。もっといいコードが書けるように頑張りたいと思います！