0
0

More than 1 year has passed since last update.

もう一度JavaScriptを理解する part2

Last updated at Posted at 2022-07-05

初めに

今回はSpecail valueについてまとめていきたいと思います。

part2は
You Don't Know JS: Types & Grammar - Chapter 2: Values
の勉強メモです。

part1ではundeclaredTDZundefinedを触れていますが、今回はもう少しまとめていきたいと思います。

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に入ったら新しい値が付与されるまで同じくundefinedlet/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自身しかありません。
しかし上の例ではxyundefined型でしょうか?

変数は値を保存するが、自身にはデータ型が所有しません。
xyはまだ値が付与されてなく、今はno valueだからundefined初期値を持っているということで、undefined型と見えますが、実質上ここではundefinedという値を表しています。

なぜ例のようにconst使うとSyntaxErrorが出てくる?undefinedという初期値がついてるじゃないんですか?

constは設計上では唯一の値を付与するために創られた宣言(Declaration)です。そしてTDZはそれを成立させるために創られており、constletみたい値が何度も上書きするようにできないので、値が付与されるまで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

nullundefinedと同じく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()がありますが、

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)に強制的に変換してから判定するので、

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を返します。

Number.isNaN()
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
0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0