初めに
今回は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