初めに
今回はSpecail valueについてまとめていきたいと思います。
part2は
You Don't Know JS: Types & Grammar - Chapter 2: Values
の勉強メモです。
part1ではundeclared
、TDZ
、undefined
を触れていますが、今回はもう少しまとめていきたいと思います。
undeclared vs. undefined
undeclared
は宣言されていないということで、
var a = 1 // declared global variable
b = 2 // undeclared global variable
console.log(b) // 2
('use strict'
なら未宣言の変数はReferenceError
になります。)
b
のような宣言使わない、しかし存在している変数がundeclared
状態です。
ではdeclared
状態と何か違うかな?
undeclared
状態の変数はどこに存在しているでしょう?
前から同じ疑問持っているので少し調べたら、同じくグローバルに置かれると、
Both exist in the global object
Effect of declared and undeclared variables - stackoverflow
両者は同じくグローバルオブジェクトに存在している、そして
両者のプロパティではconfigurable attribute
が違うことで未宣言変数はdelete
演算子がで削除できるとわかりました。
また、YDNJS
シリーズの作者によりますと、
an "undeclared" variable is one that has not been formally declared in the accessible scope.
undeclared
変数はどこのスコープにも成立していない状態に対して、
declared
変数は、自分のいるスコープにちゃんと成立しています。
declared
変数はcompile-timeではvar
ならcompilation
の解析(parsing)段階でundefined
という初期値を得て、let/const
ならuninitialized
初期化されていない状態を得る。
別々run-timeに入ったら新しい値が付与されるまで同じくundefined
(let/const
変数はrun-timeで初期値をゲットする)という値を持っていますが、let/const
変数はrun-timeまでTDZ(Temporal Dead Zone)に囲まれている。
TDZ
TDZ(Temporal Dead Zone)はlet/const
宣言のuninitialized
状態に指しています。
var
での変数宣言のundefined
は「ここには値がないからとりあえずdefault value
与えよう」ということに対して、ES6からlet/const
が現れ、let/const
での変数宣言から値が付与されるまでuninitialized
状態として「ここに変数が存在するけど初期化されていないよ、だから誰も動かせてはいけません」とされています。
typeof undefined
var x
console.log(typeof x) // undefined
let z
console.log(typeof z) // undefined
undefined
はプリミティブ型の一つとして、値はundefined
自身しかありません。
しかし上の例ではx
とy
はundefined型
でしょうか?
変数は値を保存するが、自身にはデータ型が所有しません。
x
とy
はまだ値が付与されてなく、今はno value
だからundefined
初期値を持っているということで、undefined型
と見えますが、実質上ここではundefined
という値を表しています。
なぜ例のようにconst
使うとSyntaxError
が出てくる?undefined
という初期値がついてるじゃないんですか?
const
は設計上では唯一の値を付与するために創られた宣言(Declaration)です。そしてTDZ
はそれを成立させるために創られており、const
はlet
みたい値が何度も上書きするようにできないので、値が付与されるまでTDZ Error
が投げられてくるのです。
undefined vs. null
undefined
は「ここには値が存在するけれど、(まだ定義されていないから)値がない」。
変数が宣言されたが、値が付与されていない ⇒ undefined
関数を呼び出すとき、引数が提供されていない ⇒ undefined
関数にreturn value
指定しない場合、undefined
を返す ⇒ undefined
オブジェクトのProperty value
がない ⇒ undefined
(
{Key: Value}
= {Property Name: Property Value}
Property attributes
→ [[value]], [[Writable]], [[Enumerable]], [[Configurable]]
)
var a
console.log(a) // undefined
function logStr(str) {
console.log(str)
}
logStr() // undefined
console.log(logStr())
// undefined // str is undefined
// undefined // return value is undefined
// function's return value, default value is 'undefined'
const obj = {}
logStr(obj.name) // undefined
一方、null
は「ここにはオブジェクトという値が存在しない」、あるいは「意図的に存在すべきではない」と意味しています。
null
はプリミティブ値の一つですが、歴史的な理由でnull
型ではなくobject
です。
また、null
はオブジェクトのプロトタイプチェーンの起点(何も継承していないオブジェクト)でもあります。
console.log(typeof null) // object
console.log(typeof undefined) // undefined
console.log(Object.getPrototypeOf(Object.prototype)) // null
null
はundefined
と同じくfalsy falue
としてブーリアン型に型変換するんですが、undefined
のようにtypeof
でデータ型特定できないし、とても厄介です。
でもその特性を用いて、複数の条件で洗い出すことができます。
const x = null
if (!x && typeof x === 'object') {
console.log('Yes, you got a null')
}
// note: null is the only one object value in falsy value
NaN
NaN
(not a number、非数)
演算や処理結果としてデータ型が数値が解釈できないもの(parseInt('abc')、Number(undefined))、
ゼロのゼロ除算(0/0)、無限大の除算(Infinity/Infinity)など無意味な演算結果、
これらNaN
で表現されています。
NaN - MDN
NaN
自身は数値型でありながら、自身と同等ではないという特性を持ち、
console.log(typeof NaN) // number
console.log(NaN === NaN) // false
console.log(NaN == NaN) // false
NaN
の判定は、isNaN()
とNumber.isNaN()
がありますが、
console.log(isNaN({})) // true
console.log(isNaN([])) // false
// console.log(isNaN(BigInt(9007199254740991)))
// console.log(isNaN(BigInt('9007199254740991')))
// TypeError: Cannot convert a BigInt value to a number
console.log(isNaN(123)) // false
console.log(isNaN(-1.23)) // false
console.log(isNaN(1 - 3)) // false
console.log(isNaN(0)) // false
console.log(isNaN('')) // false // *string
console.log(isNaN('123')) // false // *string
console.log(isNaN('Hello')) // true
console.log(isNaN('2022/07/05')) // true
console.log(isNaN(true)) // false // *boolean
console.log(isNaN(undefined)) // true // *undefined
console.log(isNaN(null)) // false // *object
console.log(isNaN(NaN)) // true
console.log(isNaN('NaN')) // true
console.log(isNaN(0 / 0)) // true
console.log(isNaN(1 / 0)) // false // Infinity
console.log(isNaN(Infinity / Infinity)) // true
console.log(isNaN(Infinity / -Infinity)) // true
isNaN()
はNaN
である場合だけtrue
を返すはずだが、isNaN()
ではまず引数を数値型(Number)に強制的に変換してから判定するので、
console.log(Number({})) // NaN
console.log(Number([])) // 0
console.log(Number('')) // 0
console.log(Number('123')) // 123
console.log(Number(true)) // 1
console.log(Number(undefined)) // NaN
console.log(Number(null)) // 0
変な結果につながりました。。
Number.isNaN()
はこの問題を修正するため、本当の意味のNaN
を検出するときtrue
を返します。
console.log(Number.isNaN({})) // false
console.log(Number.isNaN([])) // false
console.log(Number.isNaN(BigInt(9007199254740991))) // false
console.log(Number.isNaN(BigInt('9007199254740991'))) // false
console.log(Number.isNaN('')) // false
console.log(Number.isNaN('123')) // false
console.log(Number.isNaN(true)) // false
console.log(Number.isNaN(null)) // false
console.log(Number.isNaN(NaN)) // true
console.log(Number.isNaN('NaN')) // false // *string
console.log(Number.isNaN(0 / 0)) // true
console.log(Number.isNaN(1 / 0)) // false // Infinity
console.log(Number.isNaN(Infinity / Infinity)) // true
console.log(Number.isNaN(Infinity / -Infinity)) // true
Positive-zero(+0) vs. Negative-zero(-0)
作者によると、数学上では+0、-0は変位(displacement)で別々の方向を示すことと同じ、この二つを混同してはならない。そして+0、-0はNaN
とは全然違う概念です。しかし、
console.log(0 === -0) // true
JSの厳密等価子では、+0と-0は同じものとして扱われ、
でも、Infinity
と-Infinity
確かに存在しています。
console.log(1 / 0) // Infinity
console.log(1 / -0) // -Infinity
ならば判定方法は、
const negativeZero = -0
function NegZero(num) {
return num === 0 && (1 / num) === -Infinity
}
console.log(NegZero(negativeZero)) // true
または、同一値の判定メソッドObject.is()
を使いましょう。
console.log(Object.is(negativeZero, -0)) // true
おまけ Object.is()
console.log(Object.is(undefined, undefined)) // true
console.log(Object.is(null, null)) // true
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(+0, +0)) // true
console.log(Object.is(-0, -0)) // true