第8章 値
目次
本書を購入する
(広告です。ブロックしないでください。)

第8章 値

JavaScriptは、プログラミング言語で期待されるほとんどのを備えています。ブール値、数値、文字列、配列などです。すべての通常のJavaScriptの値にはプロパティがあります。[9] 各プロパティにはキー(または名前)とがあります。プロパティはレコードのフィールドのように考えることができます。ドット演算子(.)を使用してプロパティにアクセスします。

> var obj = {}; // create an empty object
> obj.foo = 123;  // write property
123
> obj.foo  // read property
123
> 'abc'.toUpperCase()  // call method
'ABC'

JavaScriptの型システム

本章では、JavaScriptの型システムの概要を説明します。

JavaScriptの型

JavaScriptには、ECMAScript言語仕様の第8章に従って、6つの型しかありません。

ECMAScript言語型は、ECMAScript言語を使用してECMAScriptプログラマが直接操作する値に対応します。ECMAScript言語型は次のとおりです。

  • Undefined、Null
  • Boolean、String、Number、および
  • Object

したがって、コンストラクタはインスタンスを持つと言われている場合でも、技術的には新しい型を導入しません。

静的型付け対動的型付け

静的に型付けされた言語では、変数、パラメータ、オブジェクトのメンバー(JavaScriptではプロパティと呼びます)には、コンパイラがコンパイル時に認識する型があります。コンパイラはこの情報を使用して型チェックを実行し、コンパイル済みコードを最適化できます。

静的に型付けされた言語でも、変数には動的型(実行時の特定の時点での変数の値の型)もあります。動的型は静的型と異なる場合があります。(Javaの例)

Object foo = "abc";

fooの静的型はObjectです。動的型はStringです。

JavaScriptは動的に型付けされています。変数の型は通常、コンパイル時には認識されません。

静的型チェック対動的型チェック

型情報がある場合、演算(関数の呼び出し、演算子の適用など)で使用される値が正しい型かどうかを確認できます。静的に型チェックされた言語は、この種のチェックをコンパイル時に実行し、動的に型チェックされた言語は実行時に実行します。言語は静的に型チェックされ、動的に型チェックされることもできます。チェックが失敗すると、通常はエラーまたは例外が発生します。

JavaScriptは非常に限られた種類の動的型チェックを実行します。

> var foo = null;
> foo.prop
TypeError: Cannot read property 'prop' of null

ただし、ほとんどの場合、何も起こらずに失敗するか、機能します。たとえば、存在しないプロパティにアクセスすると、undefinedという値が返されます。

> var bar = {};
> bar.prop
undefined

型強制

JavaScriptでは、型が合わない値を処理する主な方法は、型強制することです。型強制とは、暗黙的な型変換を意味します。ほとんどのオペランドは型強制されます。

> '3' * '4'
12

JavaScriptの組み込み型変換メカニズムは、BooleanNumberStringObjectの型のみをサポートしています。あるコンストラクタのインスタンスを別のコンストラクタのインスタンスに変換する標準的な方法はありません。

警告

厳密に型付けされた弱く型付けされたという用語には、一般的に意味のある定義がありません。これらの用語は使用されますが、通常は誤って使用されています。静的に型付けされた静的に型チェックされたなどを使用する方が良いでしょう。

プリミティブ値対オブジェクト

JavaScriptはある程度恣意的な値の区別をします。

両者の主な違いは比較方法です。各オブジェクトは一意のアイデンティティを持ち、それ自体にのみ(厳密に)等しくなります。

> var obj1 = {};  // an empty object
> var obj2 = {};  // another empty object
> obj1 === obj2
false

> var obj3 = obj1;
> obj3 === obj1
true

これとは対照的に、同じ値をエンコードするすべてのプリミティブ値は同じと見なされます。

> var prim1 = 123;
> var prim2 = 123;
> prim1 === prim2
true

次の2つのセクションでは、プリミティブ値とオブジェクトを詳しく説明します。

プリミティブ値

以下は、すべてのプリミティブ値(簡単にプリミティブ)です。

  • ブール値:truefalse第10章を参照)
  • 数値:17361.351第11章を参照)
  • 文字列:'abc'"abc"第12章を参照)
  • 2つの「非値」:undefinednullundefinedとnullを参照)

プリミティブには、次の特性があります。

値で比較される

「内容」が比較される

> 3 === 3
true
> 'abc' === 'abc'
true
常に不変

プロパティは変更、追加、削除できません。

> var str = 'abc';

> str.length = 1; // try to change property `length`
> str.length      // ⇒ no effect
3

> str.foo = 3; // try to create property `foo`
> str.foo      // ⇒ no effect, unknown property
undefined

(不明なプロパティを読み取ると、常にundefinedが返されます。)

固定された型のセット
独自のプリミティブ型を定義することはできません。

オブジェクト

プリミティブでないすべての値はオブジェクトです。最も一般的なオブジェクトの種類は次のとおりです。

オブジェクトには次の特性があります。

参照によって比較される

アイデンティティが比較されます。すべてのオブジェクトには独自のアイデンティティがあります。

> ({} === {})  // two different empty objects
false

> var obj1 = {};
> var obj2 = obj1;
> obj1 === obj2
true
デフォルトで変更可能

通常、プロパティを自由に変更、追加、削除できます(ドット演算子(.):固定キーによるプロパティへのアクセスを参照)

> var obj = {};
> obj.foo = 123; // add property `foo`
> obj.foo
123
ユーザー拡張可能
コンストラクタ(レイヤー3:コンストラクタ—インスタンスのファクトリを参照)は、カスタム型のインプリメンテーション(他の言語のクラスに似ています)と見なすことができます。

undefinedとnull

JavaScriptには、不足している情報を示す2つの「非値」、undefinednullがあります。

undefinednullは、プロパティアクセスによって例外が発生する唯一の値です。

> function returnFoo(x) { return x.foo }

> returnFoo(true)
undefined
> returnFoo(0)
undefined

> returnFoo(null)
TypeError: Cannot read property 'foo' of null
> returnFoo(undefined)
TypeError: Cannot read property 'foo' of undefined

undefinedは、存在しないことを示すメタ値として使用されることもあります。これとは対照的に、nullは空を示します。たとえば、JSONノードビジター(データの変換:ノードビジターによるを参照)は、

  • オブジェクトのプロパティまたは配列要素を削除するためにundefinedを返し、
  • プロパティまたは要素をnullに設定するためにnullを返します。

undefinedとnullの発生

ここでは、undefinednullが発生するさまざまなシナリオを確認します。

undefinedの発生

初期化されていない変数はundefinedです。

> var foo;
> foo
undefined

不足しているパラメータはundefinedです。

> function f(x) { return x }
> f()
undefined

存在しないプロパティを読み取ると、undefinedが返されます。

> var obj = {}; // empty object
> obj.foo
undefined

そして、関数は、明示的に何も返されていない場合、暗黙的にundefinedを返します。

> function f() {}
> f()
undefined

> function g() { return; }
> g()
undefined

nullの発生

undefinedまたはnullのチェック

次のセクションでは、undefinednullを個別にチェックする方法、またはいずれかが存在するかどうかをチェックする方法を確認します。

undefinedまたはnullのいずれかのチェック

ほとんどの関数では、undefinedまたはnullのいずれかを使用して欠損値を示すことができます。これら両方をチェックする1つの方法は、明示的な比較です。

// Does x have a value?
if (x !== undefined && x !== null) {
    ...
}
// Is x a non-value?
if (x === undefined || x === null) {
    ...
}

もう1つの方法は、undefinednullの両方がfalseとみなされるという事実を利用することです(Truthy and Falsy Valuesを参照)。

// Does x have a value (is it truthy)?
if (x) {
    ...
}
// Is x falsy?
if (!x) {
    ...
}

警告

false0NaN、および''falseとみなされます。

undefinedとnullの歴史

単一の非値が、undefinednullの両方の役割を果たすことができます。なぜJavaScriptにはそのような値が2つ存在するのでしょうか?その理由は歴史的なものです。

JavaScriptは、Javaのアプローチを採用し、値をプリミティブとオブジェクトに分割しました。また、Javaの「オブジェクトではない」値であるnullも使用しました。C(ただしJavaではない)で設定された前例に従って、nullは数値に強制変換されると0になります。

> Number(null)
0
> 5 + null
5

最初のバージョンのJavaScriptには例外処理がありませんでした。そのため、初期化されていない変数や欠損プロパティなどの例外的なケースは、値によって示す必要がありました。nullは良い選択肢だったでしょうが、Brendan Eichは当時2つのことを避けたいと考えていました。

  • その値は参照の意味合いを持つべきではありません。なぜなら、それはオブジェクトだけに関するものではなかったからです。
  • その値は0に強制変換されるべきではありません。なぜなら、そうするとエラーを発見しにくくなるからです。

その結果、Eichはundefinedを言語に追加の非値として追加しました。これはNaNに強制変換されます。

> Number(undefined)
NaN
> 5 + undefined
NaN

undefinedの変更

undefinedグローバルオブジェクトのプロパティです(したがってグローバル変数です。The Global Objectを参照)。ECMAScript 3では、undefinedを読み取る際には注意が必要でした。なぜなら、その値を誤って変更することが簡単だったからです。ECMAScript 5では、undefinedは読み取り専用であるため、それは必要ありません。

変更されたundefinedから保護するために、2つのテクニックが人気でした(それらは古いJavaScriptエンジンにも関連しています)。

テクニック1

グローバルundefined(間違った値を持っている可能性があります)をシャドウします。

(function (undefined) {
    if (x === undefined) ...  // safe now
}());  // don’t hand in a parameter

前のコードでは、undefinedは関数の呼び出しによって値が提供されていないパラメーターであるため、正しい値を持つことが保証されています。

テクニック2

void 0と比較します。これは常に(正しい)undefinedです(The void Operatorを参照)。

if (x === void 0)  // always safe

プリミティブのラッパーオブジェクト

3つのプリミティブ型boolean、number、およびstringには、対応するコンストラクタBooleanNumberStringがあります。それらのインスタンス(いわゆるラッパーオブジェクト)は、プリミティブ値をラップします。コンストラクタは2つの方法で使用できます。

  • コンストラクタとして、それらはラップするプリミティブ値と大きく互換性のないオブジェクトを作成します。

    > typeof new String('abc')
    'object'
    > new String('abc') === 'abc'
    false
  • 関数として、それらは値を対応するプリミティブ型に変換します(Functions for Converting to Boolean, Number, String, and Objectを参照)。これは推奨される変換方法です。

    > String(123)
    '123'

ヒント

ラッパーオブジェクトを避けることがベストプラクティスと考えられています。通常、それらは必要ありません。オブジェクトができることでプリミティブができないことは(変異するのを除いて)ありません。(これは、JavaScriptがプリミティブとオブジェクトの違いを継承したJavaとは異なります!)

ラッパーオブジェクトはプリミティブとは異なります

プリミティブ値(例:'abc')は、new String('abc')などのラッパーインスタンスとは根本的に異なります。

> typeof 'abc'  // a primitive value
'string'
> typeof new String('abc')  // an object
'object'
> 'abc' instanceof String  // never true for primitives
false
> 'abc' === new String('abc')
false

ラッパーインスタンスはオブジェクトであり、JavaScriptではオブジェクトを比較する方法はありません。寛容な等価演算子==による比較もできません(Equality Operators: === Versus ==を参照)。

> var a = new String('abc');
> var b = new String('abc');
> a == b
false

プリミティブのラップとアンラップ

ラッパーオブジェクトを使用するユースケースが1つあります。プリミティブ値にプロパティを追加したい場合です。プリミティブをラップし、ラッパーオブジェクトにプロパティを追加します。値を操作する前に、それをアンラップする必要があります。

ラッパーコンストラクタを呼び出すことでプリミティブをラップします。

new Boolean(true)
new Number(123)
new String('abc')

メソッドvalueOf()を呼び出すことでプリミティブをアンラップします。すべてのオブジェクトにはこのメソッドがあります(Conversion to Primitiveで説明)。

> new Boolean(true).valueOf()
true
> new Number(123).valueOf()
123
> new String('abc').valueOf()
'abc'

ラッパーオブジェクトをプリミティブに適切に変換すると、数値と文字列は抽出されますが、ブール値は抽出されません。

> Boolean(new Boolean(false))  // does not unwrap
true
> Number(new Number(123))  // unwraps
123
> String(new String('abc'))  // unwraps
'abc'

その理由は、Converting to Booleanで説明されています。

プリミティブはラッパーからメソッドを借用します

プリミティブには独自のメソッドがなく、ラッパーから借用します。

> 'abc'.charAt === String.prototype.charAt
true

ゆるいモードと厳格モードでは、この借用の処理が異なります。ゆるいモードでは、プリミティブは動的にラッパーに変換されます。

String.prototype.sloppyMethod = function () {
    console.log(typeof this); // object
    console.log(this instanceof String); // true
};
''.sloppyMethod(); // call the above method

厳格モードでは、ラッパーのプロトタイプからのメソッドが透過的に使用されます。

String.prototype.strictMethod = function () {
    'use strict';
    console.log(typeof this); // string
    console.log(this instanceof String); // false
};
''.strictMethod(); // call the above method

型強制

型強制とは、ある型の値を別の型の値に暗黙的に変換することです。JavaScriptのほとんどの演算子、関数、メソッドは、オペランドと引数を、必要とする型に強制変換します。たとえば、乗算演算子(*)のオペランドは数値に強制変換されます。

> '3' * '4'
12

別の例として、オペランドの1つが文字列の場合、プラス演算子(+)はもう一方を文字列に変換します。

> 3 + ' times'
'3 times'

型強制はバグを隠す可能性があります

そのため、JavaScriptは値の型が間違っていることをめったに文句を言いません。たとえば、プログラムは通常、ユーザーが入力した数値であっても、オンラインフォームやGUIウィジェットからユーザー入力を文字列として受け取ります。数値としての文字列を数値として扱うと、警告は表示されず、予期しない結果になります。たとえば

var formData = { width: '100' };

// You think formData.width is a number
// and get unexpected results
var w = formData.width;
var outer = w + 20;

// You expect outer to be 120, but it’s not
console.log(outer === 120);  // false
console.log(outer === '10020');  // true

このような場合、早い段階で適切な型に変換する必要があります。

var w = Number(formData.width);

ブール値、数値、文字列、オブジェクトに変換するための関数

次の関数は、値をブール値、数値、文字列、またはオブジェクトに変換するための推奨される方法です。

Boolean()Converting to Booleanを参照)

値をブール値に変換します。次の値はfalseに変換されます。これらはいわゆる「falsy」値です。

  • undefinednull
  • false
  • 0NaN
  • ''

他のすべての値は「truthy」とみなされ、trueに変換されます(すべてのオブジェクトを含む!)。

Number()Converting to Numberを参照)

値を数値に変換します。

  • undefinedNaNになります。
  • null0になります。
  • false0になり、true1になります。
  • 文字列は解析されます。
  • オブジェクトは最初にプリミティブに変換され(すぐに説明します)、次に数値に変換されます。
String()Converting to Stringを参照)

値を文字列に変換します。すべてのプリミティブに対して明らかな結果になります。たとえば、

> String(null)
'null'
> String(123.45)
'123.45'
> String(false)
'false'

オブジェクトは最初にプリミティブに変換され(すぐに説明します)、次に文字列に変換されます。

Object()Converting Any Value to an Objectを参照)

オブジェクトをそれ自身に、undefinednullを空のオブジェクトに、プリミティブをラップされたプリミティブに変換します。たとえば、

> var obj = { foo: 123 };
> Object(obj) === obj
true

> Object(undefined)
{}
> Object('abc') instanceof String
true

Boolean()Number()String()、およびObject()は関数として呼び出されます。通常、それらをコンストラクタとして使用することはありません。そうすると、それ自身のインスタンスを作成します(Wrapper Objects for Primitivesを参照)。

アルゴリズム:ToPrimitive()—値をプリミティブに変換する

値を数値または文字列に変換するには、最初に任意のプリミティブ値に変換してから、最終的な型に変換します(Functions for Converting to Boolean, Number, String, and Objectで説明)。

ECMAScript仕様には、内部関数ToPrimitive()(JavaScriptからはアクセスできません)があり、この変換を実行します。ToPrimitive()を理解することで、オブジェクトが数値と文字列に変換される方法を構成できます。次のシグネチャがあります。

ToPrimitive(input, PreferredType?)

オプションのパラメーターPreferredTypeは、変換の最終的な型を示します。これは、ToPrimitive()の結果が数値に変換されるか文字列に変換されるかによって、NumberまたはStringのいずれかになります。

PreferredTypeNumberの場合、次の手順を実行します。

  1. inputがプリミティブの場合、それを返します(これ以上やることはありません)。
  2. それ以外の場合は、inputはオブジェクトです。input.valueOf()を呼び出します。結果がプリミティブの場合、それを返します。
  3. それ以外の場合は、input.toString()を呼び出します。結果がプリミティブの場合、それを返します。
  4. それ以外の場合は、TypeErrorをスローします(inputをプリミティブに変換できないことを示します)。

PreferredTypeStringの場合、手順2と3が入れ替えられます。PreferredTypeは省略することもできます。日付の場合はString、それ以外の値の場合はNumberとみなされます。これは、演算子+==ToPrimitive()を呼び出す方法です。

例:ToPrimitive()の動作

valueOf()のデフォルトの実装はthisを返し、toString()のデフォルトの実装は型情報を返します。

> var empty = {};
> empty.valueOf() === empty
true
> empty.toString()
'[object Object]'

したがって、Number()valueOf()をスキップし、toString()の結果を数値に変換します。つまり、'[object Object]'NaNに変換します。

> Number({})
NaN

次のオブジェクトはvalueOf()をカスタマイズしており、Number()に影響を与えますが、String()には何も変更しません。

> var n = { valueOf: function () { return 123 } };
> Number(n)
123
> String(n)
'[object Object]'

次のオブジェクトはtoString()をカスタマイズしています。その結果を数値に変換できるため、Number()は数値を返すことができます。

> var s = { toString: function () { return '7'; } };
> String(s)
'7'
> Number(s)
7



[9] 技術的には、プリミティブ値はそれ自身のプロパティを持たず、ラッパーコンストラクタからそれらを借用します。しかし、それは裏で行われていることであり、通常は目にすることはありません。

次へ: 9. 演算子