33. You Don’t Know JS: Types & Grammar Chapter 2: Values
【所要時間】
8時間52分(2018年8月3,4,5日)
【概要】
値について
【要約・学んだこと】
Arrays
他のtype-enforced languageと比べると、JSのarrayはただのvalueの容器に過ぎない。 string, number, object, さらには他のarrayが入る。
var a = [ 1, "2", [3] ];a.length; // 3
a[0] === 1; // true
a[2][0] === 3; // true
宣言するだけでarrayにvalue は後から追加できる。
var a = [ ];a.length; // 0a[0] = 1;
a[1] = "2";
a[2] = [ 3 ];a.length; // 3
Warning: array valueを delete すると、array slotから array が除かれるが、最終elementを除いてもlength propertyは更新されない。delete operatorについてはchapter 5で扱う。
sparse にも注意が必要。
var a = [ ];a[0] = 1;
// no `a[1]` slot set here
a[2] = [ 3 ];a[1]; // undefineda.length; // 3
動作はする一方で、empty slotは混乱も招く。slotはundefined valueを持つように見えるが、slotがexplicitに設定(a[1] = undefined)している時と同様の動作はしない。詳しくは"Arrays" in Chapter 3 参照。
arrayは数的インデックスだが、ややこしいのは、string keyやpropertyを追加できるobjectでもある。(それはarrayのlengthに数えられない)
var a = [ ];a[0] = 1;
a["foobar"] = 2;a.length; // 1
a["foobar"]; // 2
a.foobar; // 2
しかし、キーとして意図されているstring valueを標準の10進数にcoercionすることができれば、stringkey としてよりも numberとして使いたいというのが想定できる。
However, a gotcha to be aware of is that if a string
value intended as a key can be coerced to a standard base-10 number
, then it is assumed that you wanted to use it as a number
index rather than as a string
key!
var a = [ ];a["13"] = 42;a.length; // 14
一般的にarrayにkey/property stringを加えるのはよくないとされている。key/property valueを持つobjectを使い、arrayを厳密に数値インデックスのvalueセーブするべきだ。
Array-Likes
array-like value(数値的にインデックスされたvalueのコレクション)を真のvalueに変換する必要がある場合があり、通常、array utilityをvalueのコレクションに対して呼ぶことができる。
例えば、様々なDOM query operationは真のarrayではなく、変換のために十分なarray-likeのDOM elementsのリストを返す。
他には、functionがリストとしてargumentにアクセスするために、argument(array-like) object(ES6では非推奨)を公開する。
とてもよくある変換方法は、valueに対してsliceを借りることだ。
function foo() {
var arr = Array.prototype.slice.call( arguments );
arr.push( "bam" );
console.log( arr );
}foo( "bar", "baz" ); // ["bar","baz","bam"]
slice()が他のparameterなしで呼ばれる場合、上記のsnippetは事実上parameterのデフォルトvalueはarrayを複製する効果を持っている。(この場合はarray-like)
var arr = Array.prototype.slice.call( arguments );
これはarray like object を true arrayに変換する element
arr.push(“bam”)は、配列の末尾に”bam”を追加する element
ES6から Array.from(..)という同様のタスクがでbuilt-in utilityが使える。
...
var arr = Array.from( arguments );
...
Note: Array.from(..)
はいくらかの強力な能力を持つ。詳しくは ES6 & Beyond 参照。
Strings
stringは本質的に文字の配列(array)に過ぎないと広く思われている。カバーの下での実装では、arrayを使うこともあれば使わないこともあるが、JSのstringは実際には文字の配列(array)とは同じではないことを認識することが重要だ。
var a = "foo";
var b = ["f","o","o"];
上記の場合、stringはarrayやarray-likesとやや似ており、どちらもlengthプロパティと、 indexOf(..)メソッド(ES5以降)、concat(..)メソッドを持つ。
a.length; // 3
b.length; // 3a.indexOf( "o" ); // 1
b.indexOf( "o" ); // 1var c = a.concat( "bar" ); // "foobar"
var d = b.concat( ["b","a","r"] ); // ["f","o","o","b","a","r"]a === c; // false
b === d; // falsea; // "foo"
b; // ["f","o","o"]
どちらも文字の配列かといえば、違う。
a; // "foo"
b; // ["f","o","o"]a[1] = "O";
b[1] = "O";a; // "foo"
b; // ["f","O","o"]
JSのstringは不変だが、arrayは可変だ。加えて、a[1] キャラクターポジションのアクセスフォームは、常に有効なJSとは限らない。古いバージョンのIEは、このシンタックスを許可しない。(今のバージョンはする)代わりに、a.charAt(1)が正しいアプローチとなる。
不変であるstringのさらなる結果は、そのコンテンツを修正するstringメソッドも変更することができず、新しいstringを作ってin-placeで返す必要がある。一方、arrayコンテンツを変えるたくさんの方法は実際にin-placeで修正する。
a; // "foo"
b; // ["f","o","o"]c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"b.push( "!" );
b; // ["f","O","o","!"]
また、stringを扱う時に役立つarrayメソッドの多くは、実際は利用できない。しかし、stringに対してnon-mutation(変化しない) arrayメソッドを借りることができる。
a;
// "foo"
b; // ["f","O","o","!"]a.join; // undefined
a.map; // undefinedvar c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
return v.toUpperCase() + ".";
} ).join( "" );c; // "f-o-o"
d; // "F.O.O."
次の例はstringの反転だ。arrayは in-place可変メソッドを持つが、stringは持たない。
a; // "foo" b; // ["f","O","o","!"]a.reverse; // undefinedb.reverse(); // ["!","o","O","f"]
b; // ["!","o","O","f"]
残念なことに、このborrowingはarray可変では動作しない。なぜならstringは不変なので、in place 修正ができないからだ。
a; // "foo"
Array.prototype.reverse.call( a );
// still returns a String object wrapper (see Chapter 3)
// for "foo" :(
他の回避策は、stringをarrayにコンバートし、望んでいる操作を実行すし、stringに戻すことだ。
a; // "foo"var c = a
// split `a` into an array of characters
.split( "" ) //["f", "o", "o"]
// reverse the array of characters
.reverse() //["o", "o", "f"]
// join the array of characters back to a string
.join( "" ); c; // "oof"
汚いやり方だが、もしすぐに処理したい場合には可能。
Warning:
このやり方は複雑な文字(unicodeやastral symbols, multibyte characters)などを持つstringでは動作しない。このようなoperationを正確に処理するために、unicode-awareで、より洗練されたライブラリユーティリティが必要だ。これに関してはMathias Bynen’sの作品を参照。(https://github.com/mathiasbynens/esrever).
これを見る他の方法は、基本的に文字のarrayとして扱うstringのタスクを行なっているなら、おそらくstringsとしてよりも、arrayとしてそれらを格納した方がよい。おそらくstringからarrayに毎回変換するという面倒な作業をしなくて済む。string表現が実際に必要な時ならいつでも、文字のarrayにjoin(“”)を呼ぶことができる。
Numbers
JSは数値typeのnumberを持つ。このタイプは整数 valueと少数以下のnumber両方を含む。正しいinteger(整数)ではないとJSが長いこと批判されていたので、将来変わるかもしれないが、今の所numberは全てを持つ。
JSではintegerは小数点以下の小数値を持たないただのvalueだ。42.0は42と同じ整数だ。
全てのscripting言語を含んだ多くの現代言語と同様に、JSのnumberの実行は、IEEE754 standardを元にしており、floating-point(浮動小数点)とよく呼ばれる。JSは特に標準のdouble precision(倍精度) format(64-bit binary)を使う。
binary floating-point numbers(バイナリ浮動小数点数)がメモリにどのように保存されるかについての詳細と、それらの選択肢の意味については、Web上で多くの素晴らしい記事がある。メモリのbitパターンを理解するのはJSのnumberの正しい使い方を理解するのに厳密には必要でないので、それはここでは触れない。詳しくは IEEE754 を参照。
Numeric Syntax
数値リテラルはJSでは一般的に10進数だ。
var a = 42;
var b = 42.3;
小数点先頭の0はオプションだ。
var a = 0.42;
var b = .42;
同様に. の後の0 はオプションだ。
var a = 42.0;
var b = 42.;
Warning:
42. という書き方は一般的ではない上に、他者が読む時に混乱を招くので使うべきではないが、有効だ。
デフォルトでは大部分の数字は基数10小数点として出力され、末尾の小数点0は削除される。
var a = 42.300;
var b = 42.0;
a; // 42.3
b; // 42
非常に大きい、または小さい数値は指数形式で出力される。toExponential()出力と同様だ。
var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11
なぜならnumber valueはnumber object wrapperで囲むことが可能なので、number valueはNumber.prototypeに組み込まれているメソッドにアクセスできる。例えば、toFixed(..)メソッドを使用すると、valueを表す小数点以下の桁数を指定できます。
var a = 42.59;
a.toFixed( 0 ); // "43"
a.toFixed( 1 ); // "42.6"
a.toFixed( 2 ); // "42.59"
a.toFixed( 3 ); // "42.590"
a.toFixed( 4 ); // "42.5900"
outputは実際はnumberのstring表現で、もしvalueが持つ以上の小数点以下を求めると、右側に0が埋め込まれているということに注意。
toPrecision(..)は似ているが、値を表すのに使われるsignificant digits(有効な桁数)を指定する。
var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"
これらのメソッドにアクセスするためにvalueを持つvariableを使う必要はない。number literalで直接これらのメソッドにアクセスができる。しかし、 . operatorに気をつける必要がある。なぜなら. は有効なnumeric characterで、property accessorとして解釈されるため、可能ならばnumber literalの一部として最初に解釈される。
// invalid syntax:
42.toFixed( 3 ); // SyntaxError
// these are all valid:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
42.toFixed(3)は無効なシンタックスだ。なぜなら 42. literalに飲み込まれるからだ。そして .toFixedアクセスを行う. property operatorは存在しない。
42..toFixed(3)は動作する。なぜなら最初の. は numberの一部で、2つ目の. はproperty operatorだ。見た目は変だが、実際のJSコードではほぼ見ることがない。実際にprimitive valueが直接メソッドにアクセスすることは一般的ではない。ただ、間違っているというわけではない。
Note:
built-in Number.prototypeを拡張するライブラリがあり、operation numberの追加を提供し、この場合、10秒のmoney raining animationのような10..makeItRain()を使うには完全に正しい。
42 .toFixed(3); // "42.000"
このような書き方も正しいが、他者を混乱させるだけだ。
numberは指数係数で指定することもでき、これは大きなnumberを表す時に使われる。
var onethousand = 1E3; // means 1 * 10^3
var onemilliononehundredthousand = 1.1E6; // means 1.1 * 10^6
number literalは他には2進数(binary)、8進数(octal)、16進数(hexadecimal)などの他の桁でも表現できる。
0xf3; // hexadecimal for: 243
0Xf3; // ditto
0363; // octal for: 243
Note:
ES6 + srice modeから始めて、0363フォームの8進数リテラルは許可されなくなった。0363フォームはnon-strict modeではまだ許可されているが、strict modeを使うべきなので、いずれにせよこれを使うのはやめるべきだ。
ES6から下記のフォームが有効になった。
0o363; // octal for: 243
0O363; // ditto
0b11110011; // binary for: 243
0B11110011; // ditto
0O363
フォームを使わないことを他のディベロッパーに指示するべき。大文字 O
の 0x
は混乱を招くだけだ。いつも 0x
, 0b
, and 0o
といった小文字を使って書くこと。
Small Decimal Values
binary floating-point numbersを使用する上での最もよくある副作用が
0.1 + 0.2 === 0.3; // false
計算としてはあっているが、これは間違っている。
ただ単にbinary floating-pointに0.1と0.2を表すのは正確ではない。それらが加えられる時、結果は正確に0.3ではなくなる。0.30000000000000004
だが、近いかどうかは無関係だ。
Note:
JSは全てのvalueの表現を持つ異なるnumber 実装に変換すべきだと思う人もいる。これまでたくさんの他の選択肢が現れている。しかし、誰も受け入れていないし、今後もないだろう。可能なら遥か昔に変わっているはずだ。
もしあるnumberが正確だと信頼できないなら、numberは全く使えないというわけではない。
小数点以下のvaueを扱う時に、特に注意が必要なアプリケーションがいくつかある。多くのアプリケーションが全ての整数のみを扱う、さらに、最大で数100万か数兆のnumberだけを扱う。これらのアプリケーションはJSでいつも数値演算で完全に安全に扱われていた。
では2つのnumberを比較する時にどうするか。
最も受け入れられている方法は、比較の許容差として、小さなrounding error valueを使うことだ。この小さなvalueはしばしば machine epsilonと呼ばれ、JSのnumberの種類としては通常2^-52
(2.220446049250313e-16
)となる。
ES6から、 Number.EPSILONはこの許容valueがあらかじめ定義されているので使いたくなるが、pre-ES6の定義を安全にポリフィルできる。
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}
Math.pow(2, -52) は 2の−52乗という意味。つまり0よりは大きいとても小さい正の数字。
Number.EPSILON
を使うことで 2つの数字のnumberの均等差を比べることができる。(rounding error許容差の範囲で)
別の言い方では、1と,Numberとして表現できる1より大きい最小の値の差を表す。
function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON;
}var a = 0.1 + 0.2;
var b = 0.3;numbersCloseEnoughToEqual( a, b ); // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false
Math.absは数値の絶対値を返す。
表現できる最大浮動小数値(floating-point value)は、およそ1.798e+308(これはとても大きい)で、Number.MAX_VALUEとしてあらかじめ定義されている。Number.MIN_VALUEはおよそ5e-324で、負(マイナス)ではないが、非常にzeroに近い。
Safe Integer Ranges
numberがどのように表現されるかにより、整数全体に安全な範囲があり、それはNumber.MAX_VALUEよりはるかに小さい。
安全に表現される最大整数(要求されたvalueが実際に明白に表現可能だという保証がある)は、要求されたvalueは2^53 - 1
, つまり9007199254740991
だ。もしコンマを入れると、9000万を超える。このnumberの範囲はかなり大きい。
このvalueは実際はES6に、Number.MAX_SAFE_INTEGER
として自動で事前に定義されている。最小valueは-9007199254740991
で、ES6にNumber.MIN_SAFE_INTEGER
として定義されている。
このような大きな数を扱うためにJSプログラムが直面する主な方法は、データベースなどから64ビットのIDを処理する場合です。64ビットの数値はnumber typeで正確に表現することができないため、string 表現を使用してJavaScriptに格納し、JavaScriptから送受信する必要がある。
このような大きなID number valueに対する数値演算(比較以外に、stringでうまくいく)は、よくあることではない。しかし、もしとても大きなvalueを計算する必要があるなら、big number utilityを使う必要がある。big number はおそらくJSの今後のバージョンでサポートされるだろう。
Testing for Integers
valueが整数かどうかテストするには、ES6-specifiedが使える。
Number.isInteger(..)
:
Number.isInteger( 42 ); // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 ); // false
pre-ES6のためのNumber.isInteger(..)
ポリフィルは
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num == "number" && num % 1 == 0;
};
}
Number.isInteger(8.00); //true
Number.isInteger(10.34);false
これは“タイプがnumber かつ、 value を 1で割った時にあまりがない”という意味。
valueがsafe integerかどうかテストするには、ES6-specifiedを使う。
safe integerとは、
Number.isSafeInteger(..)
:
正確に IEEE-754 倍精度数として表すことができます。
IEEE-754 の表現は、IEEE-754 の表現に適合するように、他の整数を丸めた結果にすることはできません。
Number.isSafeInteger( Number.MAX_SAFE_INTEGER ); // true
Number.isSafeInteger( Math.pow( 2, 53 ) ); // false
Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true
2の53乗はIEEE-754では表現できないためfalse
per-ES6ブラウザでNumber.isSafeInteger(..)
をポリフィルすると
if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger( num ) &&
Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
};
}
Number.isSafeInteger(Math.pow(2, 53 - 1));
true
Number.isSafeInteger(Math.pow(2, 53));
false
32-bit (Signed) Integers
integersはおよそ9兆を安全に範囲にできる(53ビット)が、numeric operation(bitwise operatorのようなもの)は32-bit numberしか定義できない。そのため、number の safe range ははるかに小さくなければいけない。
Math.pow(-2,31)(-2147483648、およそ-21億)から、Math.pow(2,31)-1 (2147483647、およそ21億)が範囲だ。
aのvalueを32ビットのサイン付きinteger valueに強制するには、 a | 0。これは | bitwise operatorが32-bit integer valuesのみで動作するので、動作する。(32 bitに注意を払い、他のbitは失われるという意味)。そして”or’ing”と zeroは本質的に no-op(無操作) bitwiseを話す。
Note:
NanやInfinityのようなある特別なvalueは、32-bit safeではない。それらのvalueがbitwise operatorに渡されるとき、abstract operation ToInt32を通り、bitwise operationの目的で、単に+0 valueとなる。
Special Values
alert JS developerが知る必要があり、適切に使う必要があるvarious typeにはいくつかの特別なvalueがある。
The Non-value Values
undefined typeには undefined というvalueのみがある。null typeにはnullというvalueだけがある。どちらもlabelはtypeとvalue両方である。
undefinedもnullもよくemptyまたはnon valueと交換できる。他のディベロッパーはそれらのニュアンスを区別することを好む。
- nullはempty value
- undefinedはmissing value
または
- undefinedはvalueをまだ持っていない
- nullはvalueを持っていたが、もうない
定義の選び方や2つのvalueの使い方に関係なく、nullはspecial keywordで、identifierではない。そのため、代入するvariableとして扱うことができない。しかし、undefinedはidentifierである。
Undefined
non-strict modeでは実際のところglobalに提供されているundefined identifierにvalueを割り当てることができる。(これは好ましくない)
function foo() {
undefined = 2; // really bad idea!
}foo();undefined
function foo() {
“use strict”;
undefined = 2; // really bad idea!
}foo();VM708:3 Uncaught TypeError: Cannot assign to read only property ‘undefined’ of object ‘#<Window>’
at foo (<anonymous>:3:12)
at <anonymous>:6:1
foo @ VM708:3
(anonymous) @ VM708:6
non-strict modeでもstrict modeでもundefinedというローカルvariableを作ることができるが、これはよくない。
function foo() {
“use strict”;
var undefined = 2;
console.log( undefined ); // 2
}foo();
VM710:4 2
undefinedは上書きできない。
void
Operator
undefinedがbuilt-in undefined valueをもつbuilt-in identifierである一方で、valueをとる他の方法は、void operatorだ。
void expressionの結果は、いつもundefined valueになる。それは現在のvalueを修正しない。それはvalueがoperator expressionから戻ってこないことを保証する。
var a = 42;
console.log( void a, a ); // undefined 42
慣例によると(主にC言語)、voidを使ったstand-aloneのundefined valueを表現するには、void 0 を使う。(voidがtrueでも、他のvoid expressionでも同じ)。実際にはvoid 0, void 1, undefined 全て違いはない。
しかし、もしexpressionがvalueの結果を持たない(副作用があっても)expressionだと保証する必要があるなら、void operator は他の環境でも使える場合がある。
function doSomething() {
// note: `APP.ready` is provided by our application
if (!APP.ready) {
// try again later
return void setTimeout( doSomething, 100 );
}
var result;
// do some other stuff
return result;
}
// were we able to do it right away?
if (doSomething()) {
// handle next tasks right away
}
setTimeout(..) functionはnumeric valueを返す。(キャンセルしたければ、timer intervalのunique identifier)しかし、functionのreturn valueがif statementでfalse-positiveを与えないために、voidしたい。
多くのディベロッパーはこれらのアクションを別にやることを好む。同様の動作をするが、void operatorを使わない。
if (!APP.ready) {
// try again later
setTimeout( doSomething, 100 );
return;
}
一般的に、valueが存在する場所があり、そのvalueがundefinedを見つけることが便利ならば、void operatorを使う。それはプログラムでは一般的でなく、必要なケースは少ない、そして役に立つ。
Special Numbers
number typeにはいくつかのspecial valueがある。それぞれ詳しく見てみよう。
The Not Number, Number
両方のoperand(演算数)がnumber(もしくはbase 10かbase 16の通常numberとして解釈されるvalue)なしで実行する数学 operationは、operationで有効なvalueを生成するという結果にならない。
NaNは文字通りnumberではないが、このlabel/descriptionはとても貧弱で、誤解をうむことがある。NaNがnumberでないと考えるよりも、不当なnumber、failed number、もしくはbad numberとして考える方がより正確だ。
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
言い換えると、not-a-number というtypeは number! だ。NaNはnumber セット内の特別な種類のエラー条件を表す”sentinel value”(特別な意味を割り当てられた通常のvalue)。本質的なエラー条件は、”mathematic operationを実行しようとしたが失敗した。代わりに失敗したnumberの結果がここにある。”といったものである。
そのため、もしいくらかvariableを持っていて、これがspecial failed-number NaNかどうかをテストをしたいなら、nullまたはundefinedのような他のvalueと同様に、直接NaN自身を比べられることができると考えるかもしれないが、できない。
var a = 2 / "foo";
a == NaN; // false
a === NaN; // false
Nanは他のNan valueともイコールにならないとても特別なvalueだ(自身とイコールに決してならない)。実際、それは再帰的ではない(identity characteristic x ===xなしで)唯一のvalueだ。NaN! == NaNとなる。
NaNを比べられないならどうやってテストをすればいいのか。
var a = 2 / "foo";
isNaN( a ); // true
isNaN(..)というbuilt-in global utility を使えば、valueがNaNかどうか教えてくれる。
isNan(..)utilityは致命的な血管を持つ。文字通りNaNの意味をとろうとするように見えるが、渡されたものがnumberでないか、numberであるかどうかをテストする。だが、あまり正確ではない。
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; // "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true -- ouch!
もちろん”foo”はnumberではない。このバグはJS初期からある。
ES6からは、代わりのutilityである Number.isNaN(..)が提供された。pre-ES6ブラウザで、NaN valueを安全にチェックするためのpolyfillだ。
if (!Number.isNaN) {
Number.isNaN = function(n) {
return (
typeof n === "number" &&
window.isNaN( n )
);
};
}
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false -- phew!
実際は、NaNがそれ自身と等しくないという独特の事実を使うことで、Number.isNaN(..) polyfillをより簡単に実行できる。NaNは正しい全ての言語の中で唯一のvalueで、他のvalue全ては常にそれ自体と等しい。
if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}
Number.isNaN(NaN);
true
Number.isNaN(1);
false
NaNは意図的にせよそうでないにせよ、JSの実際の世界にはたくさんあるだろう。正しく認識するためにNumber.isNaN(..)のように信頼できるテストがあるのはいいことだ。
もし現在isNaN(..)を使っているなら、そのプログラムはバグを持っているだろう。
Infinities
C言語のようなtraditional compiled languageのディベロッパーは、”0に割られた”といったoperationのために、compiler errorやruntime exceptionに慣れているだろう。
var a = 1 / 0;
しかし、JSではこのoperationがよく定義されていて、value infinity(Number.POSITIVE_INFINITY)となる。
var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity
‘-Infinity(Number.NEGATIVE_INFINITY)はdivide operandのどちらか、もしくは両方がマイナスの場合のzero除算の結果である。
JSは本物の計算とは異なり、有限の数値表現(IEEE754 floating point)を使用していて、加算や減算のようなoperationでもoverflowする可能性があり、その場合はInfinityか-Iinfinityを得る。
var a = Number.MAX_VALUE; // 1.7976931348623157e+308
a + a; // Infinity
a + Math.pow( 2, 970 ); // Infinity
a + Math.pow( 2, 969 ); // 1.7976931348623157e+308
仕様によると、もしvalueの加算の結果が表現するには大きすぎるoperationなら、IEE754 “round-to-nearest” modeが何の結果を出すべきか特定する。なので、
Number.MAX_VALUE + Math.pow( 2, 969 )
は、InfinityよりもNumber.MAX_VALUE
に近い。そのため、Number.MAX_VALUE + Math.pow( 2, 970 )
がInfinity
に近くまとめられる。
finiteからinfiniteにはいけるが、infiniteからfiniteには帰れない。
infinityをinfinityで割るとどうなるかというのは哲学的な質問だ。数学的、そしてJS内でInfinity / Infinityは定義されていない。この結果のJSでの結果はNaNだ。
しかし、Infinityでnumberを割る時にどんなメリットがあるのか。それは0だ。Infinityでnumberを割るデメリットは引き続き説明する。
ZerosZeros
JSは通常の0(+0)とマイナスの0がある。なぜ-0があるのかを説明する前に、どうやってJSがそれを処理するのかを調べるべきだ。
-0と指定されるのに加え、mathematic operationの結果が-0のこともある。
var a = 0 / -3; // -0
var b = 0 * -3; // -0
developer consoleがマイナス0を調査する時、通常は-0とする。しかし、最近までは一般的ではなかったため、古いブラウザでは0と報告されることもある。
しかし、マイナス0を文字列化しようとすると、仕様に従い”0”と報告される。
var a = 0 / -3;
// (some browser) consoles at least get it right
a; // -0
// but the spec insists on lying to you!
a.toString(); // "0"
a + ""; // "0"
String( a ); // "0"
// strangely, even JSON gets in on the deception
JSON.stringify( a ); // "0"
JSON.stringify()
はJSのvalueをJSON stringに変換する。
反対のoperation(stringからnumberにする)ことはできない。
+"-0"; // -0
Number( "-0" ); // -0
JSON.parse( "-0" ); // -0
JSON.parseは JSON stringを解析し、JS valueかstring objectを記述する。
Warning.
“0”のJSON.stringify(-0)の動作は反対に一貫していないことがわかったとき、特におかしい。 JSON.parse(“-0”)は正しく期待通りに-0を報告する。
マイナス0の文字列化に加えて、true valeを隠すのは偽りで、comparison operatorsもまた(意図的に)嘘をつくように構成されている。
var a = 0;
var b = 0 / -3;
a == b; // true
-0 == 0; // true
a === b; // true
-0 === 0; // true
0 > -0; // false
a > b; // false
明らかにコード内で-0と0を区別したいときは、developer consoleの出力に依存するわけではなく、少し巧妙にする必要がある。
function isNegZero(n) {
n = Number( n );
return (n === 0) && (1 / n === -Infinity);
}
isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false
アカデミックな理由を除きマイナス0が必要な理由は、ディベロッパーがvalueの大きさを使用して、一片の情報(アニメーションフレームあたりの移動速度など)を表現する特定のアプリケーションがあり、別の部分の情報(動きの方向など)を表すためにnumberの記号がある。
これらのアプリケーションでは、一例として、variableが0に到達し、そのサインを失う場合、そしてそれが0に到着する前にそれが移動していた方向の情報を失う。0のサインを保持することが、望んでいない情報を失うことを防ぐ。
Special Equality
上記のとおり、NaN valueと-0 valueがequality comparisonに来るとき、特別な動作をする。NaNはそれ自身とはequalにならない。そしてES6’s Number.isNaN(..)(またはpolyfill)を使う必要がある。
同様に-0は嘘をつき、通常のプラス0とequalのふり(strict equal ===を使っていても)をする。なので、isNegZero(..) utilityのようなハックを使わなければならない。
ES6からは、これらの例外を除き、絶対平等の2つのvalueをテストするために使用できる新しいutilityがある。
それはobject.is(..)だ。
var a = 2 / "foo";
var b = -3 * 0;
Object.is( a, NaN ); // true
Object.is( b, -0 ); // true
Object.is( b, 0 ); // false
object.is(..)はES6ではシンプルなporyfillだ。
if (!Object.is) {
Object.is = function(v1, v2) {
// test for `-0`
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2;
}
// test for `NaN`
if (v1 !== v1) {
return v2 !== v2;
}
// everything else
return v1 === v2;
};
}
object.is(..)はおそらく==または===が安全であることがわかっている場合にはつかわない。なぜならoperatorはより効率的で、より慣用的、一般的であるからだ。object.is(..)はこれらの特殊なequalityの場合に使う。
Value vs. Reference
多くの他の言語では、valueは使用しているシンタックスによってvalue-copyかreference-copyによって割り当てられたり、渡される。
例えば、C++で、number variableをfunctionにパスして、そのvariableのvalueを更新したいなら、int& myNumといったfunction parameterを宣言することができ、xのようなvariableをパスすると、myNumはxに参照される。referenceは特別なフォームのポインタのようなもので、他のvariable(aliasのような)へのポインタを取得する。もしreference parameterを宣言しなければ、たとえ複雑なobjectでも、valueはいつもコピーされ渡される。
JSでは、ポインタはない。そしてreferenceは少し異なる働きをする。あるJS variableから他のvariableの参照を持つことはできない。不可能である。
JSのreferenceはvalueをさす。そのため、10種類の異なるreferenceがあるなら、それらはいつも1つの共有valueを個別参照する。どれも互いにreferene/pointerではない。
さらにJSでは、control valueに対してreference assignment/passingのヒントとなるシンタックスはない。代わりに、valueのタイプはそのvalueをvalue-copyかreference copyによって割り当てられるかどうかのみをコントロールする。
var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3
var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
simple value(scalar primitives)はいつもvalue-copyによって割り当てられる。 : null, undefined, string, number, boolean, ES’6のsymbol
compound values: object と functionはいつもassignment か passingにreferenceのコピーを作る。
上記のsnippetでは、2がscalar primitiveなので、 a がそれのvalueのコピーを保持し、b はほかのvalueのコピーが割り当てられる。bに変換するとき、value aを変換する方法はない。
だが、cとdは同じshared value [1,2,3]のseparate referenceで、compound valueである。cもdも[1,2,3] valueを所有していないということが重要。どちらもただのvalueへのequal peer referenceだ。いずれかのreferenceを実際のshared array value 自体の修正に使う(.push(4))とき、一方のshared valueにだけ影響し、両方のreferenceは新たに修正された[1,2,3,4] valueを参照する。
referenceはvalue自体を指摘し、variableは指摘しないので、他のreferenceが指摘されている場所を変更するために、referenceを使うことはできない。
var a = [1,2,3];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// later
b = [4,5,6];
a; // [1,2,3]
b; // [4,5,6]
b = [4,5,6] を割り当てるとき、a はまだ[1,2,3]を参照しており、何も影響を与えない。そのため、bは arrayへのreferenceではなく、aへのポインタでなければいけない。しかし、JSにその能力はない。
最も混乱を招きやすいfunction parameterは
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// later
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // [1,2,3,4] not [4,5,6,7]
a argumentをパスするとき、a referenceのコピーをxに割り当てる。 x と aは同じ[1,2,3] valueの separate reference を指す。しかし、 x =[4,5,6]を割り当てるとき、最初のreference a が指す場所には影響を与えない。 まだ[1,2,3,4] value (修正された)をさす。
x referenceがa を指す場所を変えて使うことはない。aとx両方が指すshared valueの中身だけが変更できる。
aが[4,5,6,7] value contentsを持つように変更するには、新しいarrayやassignは作れない。現在のarray valueを変えなければいけない。
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// later
x.length = 0; // empty existing array in-place
x.push( 4, 5, 6, 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // [4,5,6,7] not [1,2,3,4]
x.length = 0 ] と x.push(4,5,6,7)は新しいarrayを作らないが、現在のshared arrayを修正する。 a は新しい[4,5,6,7] contentsを参照する。
Remember:
value copy vs. referenceを直接調整、上書きすることはできない。これらの意味論はunderlying valueのタイプによって完全にコントロールされる。
value-copyによってcompound value(arrayなど)を効果的に渡すには、referenceがoriginalを指さないようにするために、手動でコピーする必要がある。
foo( a.slice() );
デフォルトではno parameterのslice(..)は、arrayの完全に新しい(shallow)copyを作る。そのため、コピーされたarrayにのみreferenceを渡すので、foo(..)はaの内容に影響を与えることはできない。
反対に、そのvalueの更新が見れる方法でscalar primitive valueを渡すには、reference-copyによって渡すことができる他のcompound value(object, arrayなど)にvalueをラップしなければならない。
function foo(wrapper) {
wrapper.a = 42;
}
var obj = {
a: 2
};
foo( obj );
obj.a; // 42
ここでは、obj がscalar primitive property aのラップとして作用している。foo(..)に渡すとき、obj referenceのコピーがわたされ、wrapper parameterにセットされる。shared objectにアクセスするためにwrapper referenceを使うことができ、それをpropertyに更新する。functionが終わると、obj.aは更新されたvalue 42を見る。
2のようなscalar primitive valueへのreferenceのパスをしたい時には、Number object ラッパーにvalueを入れる。
このNumber objectへのreference copyがfunctionに渡されるのはあっている。しかし不運にも、shared objectのreferenceを持つことがそのshared primitive valueを修正する能力を与えるわけではない。
function foo(x) {
x = x + 1;
x; // 3
}
var a = 2;
var b = new Number( a ); // or equivalently `Object(a)`
foo( b );
console.log( b ); // 2, not 3
問題は、underlying scalar primitive valueが可変ではないことだ。(StringやBooleanと同様) もしNumber objectがscalar primitive value 2を保持するなら、正しいNumber objectは決して他のvalueを持つように変わることはない。 異なるvalueと完全に新しいNumber objectを作れるのみだ。
xがexpression x + 1 で使われる時、underlying scalar primitive value 2 はNumber objectから自動的にunboxed(抽出)され、そのためライン x = x + 1は加算 operation 2 + 1の結果として、scalar primitive value 3を保持するだけで、 x がNumber object へのshared referenceから少しx を変える。それゆえ、 外側の b はまだvalue 2 を持つオリジナルの 未変更、不変のnumber objectを参照する。
Number object(内部primitive valueは変更しない)の上部にpropertyを追加することができるので、これらの追加propertyを通して間接的に情報を交換することができる。
しかし、これらはどこでも一般的と言うわけではない、おそらく多くのディベロッパーからはいいとは思われない。
wrapper object Numberを使う代わりに、最初のsnippetのmanual object wrapper ( obj ) を使う方がはるかにいいだろう。Numberのようなボックスされたobject wrapperのための巧みな使い方がないことをいっているんではない。多くの場合、scalar primitive valueフォームを好むはずということ。
Referenceはかなり力強いが、ときどき勝手なことをする。そしてときどきそれらが存在していない場所で必要になる。over reference vs. value-copy動作よりを持つ唯一のコントロールは、value自体のtypeだ。そのため、使うことを選んだvalue typeによって、割り当て、受け渡しの動作に間接的に影響をする。
Review
JSでは、arrayは単に数値的にインデックスされたvalue-typeのコレクションだ。stringはいくらかarray-likeだが、それらははっきりした挙動を持ち、arrayとして扱いたいならば気をつける必要がある。JSのnumberはintegersとfloating-point value両方を含む。
primitive typeには、いくつかの特殊なvalueが定義されている。
null typeは1つだけvalueを持つ。nullやundefined typeはただのundefined valueだ。 undefinedは他のvalueが存在しないなら、基本的にいかなるvariableやpropertyのデフォルトvalueだ。void operatorはいかなる他のvalueから undefined valueを作ることを許可する。
numberはNaN(おそらくNumberではないが、invalid numberと言う方が適切), +Infinity, -Infinity, -0 のようないくつかのspecial valueを含む。
simple scalar primitive(string , numberなど)は、value-copyによって割り当てられる/渡される。しかし、compound value(objectなど)はreference-copyによって割り当てられる。Referenceは他の言語のreference/pointerのようではない。それらは決して他のvariableやreferenceを指さず、underlying valueのみを指す。
【わからなかったこと】
とくになし
【感想】
ここ最近の内容と比べると理解が早かった。