ES6の値(Value)について

以下の記事を参考に、値タイプについてまとめます。

Arrays(配列)

arrayタイプは、他のタイプ(文字列数字オブジェクト、配列)を単に内包するコンテナーの役割を果たします。

またいくつか注意する点がarrayタイプにあります。

var a = [1, “2”, [3] ];
delete a[0];
console.log(a); // [, “2”, [3]]
console.log(a.length); //3

上の例ではdeleteオペレーターを用いて、a配列の一つ目の値を消しています。

しかし、配列内の値それ自体は消えないので、ログ出力はこのような結果になります。

var a = [ ];

a[0] = 1;
// no `a[1]` はセットされていません
a[2] = [ 3 ];

a[1]; // undefined

a.length; // 3
console.log(a); // [1, , [3]]

また、上の例ではa[1]の値は宣言されていません。

が、実際にに未宣言を挟んだ配列をつくるつくると、このような処理動作になります。

以下の例は配列のトリッキーな挙動になります。

配列はオジュジェクトの用にstring/valueのペアで格納することも可能なのですが、その際に以下のようなになることに注意が必要です。

var a = [ ];
a[0] = 1;
a["foobar"] = 2;
//1
console.log(a.length);
//[ 1, foobar: 2 ]
console.log(a);
a["13"] = 42;
// 14
console.log(a.length);
//[ 1, , , , , , , , , , , , , 42, foobar: 2 ]
console.log(a);

こうした事から、string/valueのペアでの格納は本来通り、オブジェクト変数で宣言するのがいいですね。

arrayタイプは、数値でインデックスされた値をassignしたいときに、使われるものになります。

Array-like

よく行われる動作として、配列タイプに似たようなデータを配列に変換(convert)するものがあります。

function foo() {
var arr = Array.prototype.slice.call( arguments );
arr.push( "bam" );

// ["bar","baz","bam"]
console.log( arr );
}
foo( "bar", "baz" );

この例では、配列への変換にslice utilityを用いて引数の値を、それぞれ配列に格納しています。

また、slliceの代わりにES6からは、Array.fromも使用されます。

var arr = Array.from( arguments );

また、JSでは文字列は、配列のcharacter(文字)とは異なるものになります。

次の例を見てみます。

var a = "foo";
var b = ["f","o","o"];
a[1] = "O";
b[1] = "O";

a; // "foo"
b; // ["f","O","o"]

文字列タイプaは、 immutable var(不変変数)である一方で、配列の要素がmutable(可変)であることが分かります。

なので、文字列タイプを操作する場合は、その変数自体は変化する方法はありません。

その為、変更を加えた値を新しい文字列変数として返す必要があります。

一方で、配列に変更を加えば、その値自体が変化をしたことになります。

c = a.toUpperCase();
a === c; // false
//変数a自体に変更は加えられていない
console.log(a); // "foo"
console.log(c); // "FOO"

b.push( "!" );
//配列bそれ自体の値が変更している。
console.log(b); // ["f","O","o","!"]

また、文字列の変更を行う場合は一度、配列に格納(mutableに)してから操作を行う必要があります。

最初から、配列で格納しておくと、文字列から配列タイプに変換する処理を省くことができます。

例えば、ある文字列を反転させる関数はこうした書き方になります。

function reverseString(str) {
var arr = str.split("");
arr = arr.reverse();
arr = arr.join("");
  return arr;
}
reverseString("hello");

ここでのポイントは、mutableは変換という意味はoverwriteとは違う点です。

手順

  • 文字列は修正ができないから、配列に一旦入れる。
  • 配列から文字列に変換

Numbers

JSの数値タイプ(numbers)は、他のタイプ同様、ある型タイプのラッパー(number object wrapper)に包まれています。

つまり、number objectのmethodにアクセスができます。

例として次のコードを見てみます。

// 5 * 10^10 を表しています。
var a = 5E10;
//50000000000
console.log(a);
//5e+10
console.log(a.toExponential());
console.log(a.toPrecision( 1 ));
//42.000
console.log((42).toFixed(3))

また、次のように数値タイプは他の進数法である

  • 2進数(binary)
  • 8進数(octal)
  • 16進数(hexadecimal)

に対応しています。

以下、上の表記例にもなります。

0xf3; // hexadecimal for: 243
0o363;		// octal for: 243

0b11110011; // binary for: 243

Small Decimal Values

次に、浮動小数点を持つ数を扱う際に、気をつける点について挙げていきます。

0.1 + 0.2 === 0.3; // false

上の例では、 0.1 + 0.2 を計算した結果が、

正しく0.3(限りなく近い数字ではあります)にはならないことから起きる例になります。

では実際に、どのように対処するのか。

以下の例をみてみます。

//ES6以下に対応
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}
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

このコードは、僅かな誤差範囲ならばアクセプトする関数を作る事で浮動小数点に対応させています。

同様に、その数値がJSコード内で使用可能な(コード上最大値内かつ整数)であるか。

以下のメソッドを用います。

if (!Number.isSafeInteger) {
Number.isSafeInteger = function(num) {
return Number.isInteger( num ) &&
Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
};
}

Testing for Integers

値が整数であるかどうかは、以下のコードでテストをします。

//polyfill for pre ES6
if (!Number.isInteger) {
Number.isInteger = function(num) {
return typeof num === "number" && num % 1 == 0;
};
}
//true
console.log(Number.isInteger( 42 ));
//true
console.log(Number.isInteger( 42.000 ));
//false
console.log(Number.isInteger( 42.3 ));

Special Values

JSには、いくつか特殊な値タイプを持つものがあります。

まず、The Non-value Valuesについては以下2つがあります。

  • null
  • undefined

ここで使われているnullは特別なkeywordになるので、他のものにこの名前は使用できません。

一方、undefinedに関しては識別子にこの名前を使用出来ます。

ただし、undefinedを名前に使うのは、止めておくのがベスト・プラクティスになります。

voidオペレーター

undefined値を得る方法として、voidオペレーターを使用するものがあります。

voidを使用しても、変数値それ自体に変化は加えないので、ただundefinedを返したい時に使われます。

値は存在しているけど、その値がundefinedのほうが都合がいい時(あまりないケースみたいですが)、このvoidオペレーターは有効です。

NaN(Nor a Number)

今まで何度か見かけたNaN。

意味としては、有効な数字ではない、正しい数字ではないといった値のタイプになります。

一体どういうことなのでしょうか?

以下の例を見ていきます。

var a = 2 / "foo";		
// NaN
console.log(a);
// true
console.log(typeof a === "number");
// false
console.log(a == NaN);
// false
console.log(a === NaN);

NaNは数値セットのエラー状態を表しているため、型タイプはそのまま数値(number)となります。

また、NaNは比較出来ないため、次のような方法でNaNのテストが行われます。

var a = 2 / "foo";

isNaN( a ); // true

ただし、これだけでは不十分なテストといえます。

何故ならisNaN()は、数値かそうでないかを確認しているにすぎない為、本来NaNかどうかの正しい判断は出来ていません。

そこで、次のような方法でNaNのテストを行っていきます。

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は他のNaNとは比較が出来ない性質があることに触れていました。

この性質を使うとNumber.isNaN関数をを次のように書くこともできます。

if (!Number.isNaN) {
Number.isNaN = function(n) {
return n !== n;
};
}

たしかに、NaNタイプ以外はすべて、自分自身と比較したらtrueが返ってきますね。

パット見不思議に感じますが、NaNの性質を用いた面白いテストだと思いました。

Infinity

その字義の通り、無限タイプがあります。

例えば、0で数を割ったりした時に、JSではエラーではなくInfinityを値に入れます。

実際にコードを打って見てみます。

var a = 1 / 0; // Infinity
var b = -1 / 0; // -Infinity
//Infinity
console.log(a);
//-Infinity
console.log(b);
//Infinity
console.log(Number.MAX_VALUE*2);
//function distinguish 0, -0
function isNegZero(n) {
n = Number( n );
return (n === 0) && (1 / n === -Infinity);
}

isNegZero( -0 ); // true
isNegZero( 0 / -3 ); // true
isNegZero( 0 ); // false

また、-0, isNaN等を比較すると次のような結果になります

  • isNaN===isNaN true
  • -o ===0 true

この2つを厳密に、判断する関数がobject.isになります。

では、どのような機能になっているのか、以下例を見ていきます。

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;
};
}

次に、値の参照がJSではどのようにおこなわれているのか、以下コードをみて確認します。

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 );

// [1,2,3,4]
console.log(a);

実際にfoo関数内の処理は、引数のa変数、最初の参照している値に影響を与えていません。

つまり、aの参照ポイントを変化させることは出来ず、xとaが互いに共有する値だけを変化させています。

また、もともとの参照ポイントは渡さず、値だけをコピーすることも可能です。

foo(a.slice());
// [1,2,3]
console.log(a);

foo関数はaの値のみ渡されるので、aの参照している値には影響を与えません。

また次の例から、参照ポイントのコピーを渡す方法についても見ていきます。

function foo(wrapper) {
wrapper.a = 42;
}

var obj = {
a: 2
};

foo( obj );

//42
console.log(obj.a);

上の用にwrapperを通して、オブジェクト変数の参照している値を変化させることができます。

また、例えば変数objに新しいプロパティを追加したい場合は

 wrapper.a = 42;
wrapper.b = 12;

のようして、追加することもできますね。

感想

どの値タイプを使うかによって、参照・値の受け渡しの仕組みが異なることがわかりました。

例えば、文字列や数字などは値のコピーに、オブジェクトは参照のコピーによって渡されます。

他のプログラミング言語との違いとして、参照は他の変数を指すのではなく、その値を指すという点がJSの特徴になっています。

Show your support

Clapping shows how much you appreciated Tuyoshi Akiyama’s story.