0
0

2.JavaScriptデータの種類

Last updated at Posted at 2024-06-15

null、undefined、ブール値

警告: 記事は機械翻訳があるため、学習メモとして保存、学習参考用のみ

nullとundefined

概要

「null」と「undefined」はどちらも「ない」という意味で使われています。ある変数を「undefined」や「null」に代入しても、正直言って文法的な効果はほとんど変わりません。

var a = undefined ;
//あるいは 
var a = null ;

上記のコードでは、変数aはそれぞれundefinednullとして割り当てられており、この2つの効果はほぼ等価 。

if文では、それらは自動的にfalseに変換され、等しい演算子(==)でさえ、直接両者が等しいことを報告します。

if( !undefined) 
console.log('undefined is false');
} 
// undefined is false 

if( !null){ 
console.log('null is false');
} 
// null is false 

undefined == null 
// true 

このコードを見ると、両者の行動がいかに似ているかがわかります。グーグル社が開発したJavaScript言語の代替言語であるDart言語は、「null」のみで「undefined」はありません!

意味も使い方も似ているのに、なぜこのような値を二つ同時に設定するのかというと、複雑さが余計に増してしまい、初心者が困るのではないでしょうか。これは歴史的な理由が関係しています。

1995年にJavaScriptが誕生したとき、最初はJavaのように無を表す「null」しかありませんでした。C言語の伝統によれば、nullは自動的に0に変換されます。

Number(null) // 0 。
5 + null // 5 

上のコードでは、nullが数字になると、自動的に0になります。

JavaScriptの設計者Brendan Eichはそれだけでは不十分だと言いましたまずJavaScriptの最初のバージョンではnullはJavaのようにオブジェクトとして扱われていましたが、Brendan Eichは「無」を表す値はオブジェクトでない方が良いと考えていました。第二に、当時のJavaScriptにはエラー処理の仕組みがありませんでした。Brendan Eichは、「null」が自動的にゼロになると、エラーを発見するのが容易ではないと考えていました。

そこで彼はundefinedを作りました違いはこう 。「null」は「空」を表す対象で、数値になると「0」になります。「undefined」は「ここでは定義されていない」を表す元の値で、数値に変換すると「NaN」となります。

Number(undefined) // NaN 
5 + undefined // NaN 

使い方と意味

「null」と「undefined」については、次のように理解することができます。

nullは空の値を表します。つまり、ここでの値は空 。関数を呼び出したとき、あるパラメータが何の値も設定されていない場合、nullを入力し、そのパラメータが空であることを示します。例えば、ある関数がエンジンから出されたエラーを引数として受け取り、実行中にエラーが発生しなければ、その引数がnullと入ってきて、エラーが発生していないことを示します。

「undefined」は「未定義」を意味します。以下は「undefined」に戻る典型的な場面 。

変数は宣言されていますが代入はありません
var i ;
i // undefined 

//関数を呼び出したとき、提供すべきパラメータが提供されていません。
function f(x){ 
return x ;
} 
f() // undefined 

//オブジェクトには値付けの属性がありません。
var o = new Object() ;
o.p // undefined 

//関数の戻り値がない場合、デフォルトでundefinedを返します。
function f(){} 
f() // undefined 

ブール値

ブール値は「true」と「false」の2つの状態を表します。「true」を「true」、「false」を「false」というキーワードで表します。ブール値はこの2つだけ

次の演算子はブール値を返します。

-前置論理演算子: !(ノット )
-等しい演算子:===, !==, , != -比較演算子 :>,>=,<,<=`

JavaScriptはある位置がブール値であることを予期している場合、その位置にある値を自動的にブール値に変換します。変換規則では次の6つの値を除いてfalseに変換されます。

  • ' undefined '
  • `null
  • `false
  • 0
  • `NaN
  • ""または''(空文字列)

ブル値はプログラムフローの制御によく使われますが、一例を見てみましょう。

if(''){ 
console.log('true');
} 
出力はありません

上のコードでは、ifコマンドの後の判定条件はブール値になると予想されているので、JavaScriptは自働的に空の文字列をブール値falseに変換します。

なお、空の配列([])と空のオブジェクト({})に対応するブール値は、どちらもtrue

if([]){ 
console.log('true');
} 
// true 

if({}){ 
console.log('true');
} 
// true 

データタイプ変換についての詳細は、データタイプ変換の章を参照してください。

リンク参照

−アクセルrauschmayer、[categorizing values in javascript] (http://www.2ality.com/2013/01/categorizing-values.html)

数値

概要

整数と浮動小数点数

JavaScriptの内部には、すべての数字が64ビットの浮動小数点数として保存されています。 から、11.0と同じ数 。

1 === 1.0 // true 

つまりJavaScript言語の基本には整数は存在せず、すべての数字は小数(浮動小数点数64ビット) 。紛らわしいことに、いくつかの演算は整数でしかできません。この場合、JavaScriptは自動的に64ビットの浮動小数点を32ビットの整数に変換して演算を行います。

浮動小数点数は正確な値ではないので、小数の比較や演算には注意が必要 。

0.1 + 0.2 
// false 

0.3 / 0.1 
// 2.99999999999996 

(0.3 - 0.2) ===(0.2 - 0.1) 
// false 

数値精度

国際標準IEEE 754によると、JavaScriptの浮動小数点数の64バイナリビットは、左端から、このように構成されています。

−1位:記号のビット,0は正数,1は負数を表します

  • 2位から12位まで(全11位):指数部門
  • 13位から64位まで(全部で52桁):小数部分(つまり有効数字 )

符号ビットは数の正負を、指数部は数の大きさを、小数部は数の精度を決定します。

指数部には11個のバイナリビットがあり、0から2047までの範囲があります。IEEE 754では、指数部の値が0から2047(2つの端点を含まない)の場合、有効数字の1桁目はデフォルトで常に1であり、64ビット浮動小数点数には格納されません。つまり,有効数字はこのとき常に1.xx…xxの形で、ここでxx.. 。xxの部分は64ビットの浮動小数点数に格納され、最長で52ビットになります。そのため、JavaScriptの有効数字は最長53バイナリビット 。

(-1)^符号位置* 1.xx …xx * 2^指数部分 

上記の式は通常の場合(指数は0から2047の間)であり、JavaScript内での数の表現 。

つまり、絶対値が2の53乗以下の整数、つまり-253 + 1から253 - 1であれば正確に表現できます。

math.pow(2, 53) 
// 9007199254740992 

math.pow(2, 53) + 1 
// 9007199254740992 

math.pow(2, 53) + 2 
// 9007199254740994 

math.pow(2, 53) + 3 
// 9007199254740996 

math.pow(2, 53) + 4 
// 9007199254740996 

上のコードでは、2の53乗より大きくなると、整数演算の結果にエラーが発生し始めます。 から、2の53乗より大きい値は、精度を保つことができません。2の53乗は16桁の10進数なのでJavaScriptは15桁の10進数を正確に処理できます

math.pow(2, 53) 
// 9007199254740992 

//最後3つの数字、保存できません。
9007199254740992111
// 9007199254740992000 

上の例では、2を53乗以上にすると、余った有効数字(最後の3桁の111)は0になってしまいます。

数値範囲

標準によれば、64ビット浮動小数点数の指数部の長さは11バイナリビットであり、指数部の最大値は2047(2の11乗から1を引く)であることを意味します。つまり、64ビットの浮動小数点数の指数部の値は最大2047で、半分を割って負の数を表すと、JavaScriptで表現できる数値の範囲は21024から2-1023(開区間)となり、それ以上の数は表現できません。

2の1024乗以上の数があると「順方向オーバーフロー」が発生します。JavaScriptではその数を表すことができず、「Infinity」に戻ります。

math.pow(2,1024)// Infinity 。

2のマイナス1075乗(指数部の最小値であるマイナス1023と小数部の52桁)以下の数は、JavaScriptでは表現できない「負のオーバーフロー」となり、そのまま0を返すことになります。

math.pow(2, -1075) // 0 。

これは実際の例

var x = 0.5 ;

for(var i = 0 ;i < 25 ;i++){ 
x = x * x ;
} 

x // 0 

上のコードでは、0.5を25回二乗すると、0に近づきすぎて表現可能な範囲を超えてしまうので、JavaScriptではそのまま0にします。

JavaScriptはNumberオブジェクトのMAX_VALUEMIN_VALUE属性を提供し、具体的に表すことができる最大値と最小値を返します。

Number.MAX_VALUE // 1.7976931348623157e+308 。
Number.MIN_VALUE // 5e-324 。

数値の表記法

JavaScriptの数値にはいくつかの表現方法があり、例えば35(十進数)や0xFF(十六進数)のように文字通り表現することができます。

数値も科学的スコアリングルールで表すことができます。ここでは科学的スコアリングルールの例をいくつか紹介します。

123e3 // 123000 
123e-3 // 0.123 。
-3.1E+12 
.1e-23 

科学スコアリングルールでは、アルファベットのeまたはeの後に、その数値の指数を表す整数が続くことが許されています。

次の2つの場合は、JavaScriptが自動的に数値を科学的なスコアリング法で表現してくれますが、それ以外の場合は文字通り表現してくれます。

(1)は小数点以下21桁以上の数字 。

1234567890123456789012
// 1.2345678901234568e+21

123456789012345678901
// 123456789012345680000 

(2)小数点以下の0が5つ以上あります。

//小数点以下5箇以上のゼロにくっついています。
//は自動的に科学スコアリングルールになります。
0.0000003 // 3e-7 

//でなければ,字面のまま 
0.000003 // 0.000003 

数値の進数

リテラルを使って直接数値を表現する場合、JavaScriptは整数を10進数、16進数、8進数、2進数の4つの進数で表現します。

-十進法:0のプリアンブルがありません。
-八進数:' 0o 'または' 0o 'の接頭辞を持つ数値、または' 0 'のプリアンブルを持ち、0-7までの8つのアラビア数字だけの数値 。
-十六進数:接頭辞0xまたは0xを持つ数 。
-二進法:接頭辞0bまたは0bの値があります。

デフォルトでは、JavaScript内部で8進数、16進数、2進数を自動的に10進数に変換します。いくつか例をご紹介します。

0xff // 255 
0o377 // 255 
0b11 // 3 

八進数、十六進数、二進数のうち、該当しない数字が出てくると間違えます。

0xzz //報錯
0o88 //エラー 
0b22 //エラー 

上のコードでは、16進数ではアルファベットz、8進数では数字8、2進数では数字2が現れるためエラーとなります。

通常、0が先行すると8進数とみなされますが、0の後に89が付くと10進数とみなされます。

0888 // 888 
0777 // 511 

プリアンブルの0は8進数を表しているため、扱いが混乱しがち 。ES5のシビアモードやES6では、この表記法は廃止されていますが、ブラウザでは従来のコードとの互換性のため、現在もこの表記法をサポートしています。

特殊数値

JavaScriptにはいくつかの特別な数値があります。

プラスゼロとマイナスゼロ

前述しましたが、JavaScriptの浮動小数点数64ビットの中に、2進数ビットが記号ビット 。これは、任意の数に対応する負の値があることを意味し、0でさえ例外ではありません。

JavaScriptの内部には2つの0が存在します。1つは+0、もう1つは-0 。それらは等価 。

-0 === +0 // true 
0 === -0 // true 
0 === +0 // true 

ほとんどの場合、正のゼロと負のゼロが正常な0として扱われます。

+0 // 0 
0 // 0 
(-0).toString() // '0' 
(+0).toString() // '0' 

唯一違いがあるのは、+0-0を分母とすると、返される値が等しくない場合 。

(1 / +0) === (1 / -0) // false 

このような結果になるのは、正の0で割ると+Infinity、負の0で割ると-Infinityとなり、両者は等しくないから (Infinityについては後述)。

NaN

(1)意味

「NaN」はJavaScriptの特殊な値で、「Not a Number」を意味します。主に文字列を数字の誤りに解析する場合に使われます。

5 - 'x' // NaN 

上記のコードが実行されると、文字列xは自動的に数値に変換されますが、xは数値ではないため、最終的にNaNとなり、非数値(NaN)であることを示します。

また、いくつかの数学的な関数の演算結果に「NaN」が現れます。

math.acos (2) // NaN 
math.log (-1) // NaN 
math.sqrt (-1) // NaN 

00で割ってもNaNになります。

0/0 // NaN

なお、NaNは独立したデータ型ではなく、特殊な数値であり、そのデータ型は依然としてNumberに属しており、typeof演算子を使ってはっきりと見ることができます。

typeof NaN // 'number' 

(2)演算規則

NaNはそれ自身を含め、いかなる値にも等しくありません。

NaN === NaN // false

配列のindexOf法は内部的に厳密な等価演算子を使用しているので、この法はNaNに対しては成立しません。

[NaN].indexOf(NaN) // -1 。

NaNはブール演算ではfalseとされます。

Boolean(NaN) // false 

' NaN 'と(それ自身を含む)任意の数を計算すれば' NaN 'が得られます。

NaN + 32 // NaN 
nan-32 // NaN 
NaN * 32 // NaN 
NaN / 32 // NaN 

Infinity

(1)意味

「Infinity」は「無限」という意味で、2種類のシーンを表します。1つは正の値が大きすぎて、あるいは負の値が小さすぎて、表すことができません;もう一つは0でない値を0で割ると「Infinity」になります。

//シーン1
math.pow(2,1024) 
インフィニティ //

シーン2 
0/0 // NaN
1/0 // Infinity 

上のコードでは、最初のシーンは1つの式の計算結果が大きすぎて、表現できる範囲を超えてしまったので、Infinityを返します。2つ目のシーンは、0を0で割るとNaNになり、0で割るとInfinityに戻ります。

Infinityは正負の区別があって、Infinityは正の無限を表し、-Infinityは負の無限を表します。

Infinity === -Infinity // false

1 / -0 // -Infinity 
-1 / -0 // Infinity 

上のコードでは、ゼロでない正の数を−0で割ると−Infinityになり、負の数を−0で割るとInfinityになります。

JavaScriptはオーバーフロー(overflow)、アンダーフロー(underflow)、0で割り切れてもエラーを返さないため、単純な計算でエラーを返すことはほとんどありません。

' Infinity 'は(' NaN 'を除く)すべての値より大きく、' -Infinity 'は(' NaN 'を除く)すべての値より小さい 。

Infinity > 1000 // true 
-Infinity < -1000 // true 

「Infinity」は「NaN」と比べて、いつも「false」を返します。

Infinity > NaN // false 
-Infinity > // false

Infinity < NaN // false > 。
-Infinity < NaN // false > 

(2)演算規則

「Infinity」の四則演算は無限の数学的計算規則に合っています。

5 * Infinity // Infinity
5 -Infinity // -Infinity 
Infinity / 5 // Infinity 
5 / Infinity // 0 

0に「Infinity」をかけると、「NaN」に戻ります。0をInfinityで割ると、0が戻ります。「Infinity」を0で割ると、「Infinity」に戻ります。

0 * Infinity // NaN 
0 / Infinity // 0 
Infinity / 0 // Infinity 

「Infinity」に「Infinity」を足したり掛けたりしても、また「Infinity」が返ってきます。

Infinity + Infinity // Infinity 
Infinity * // Infinity 

Infinityを引いたり、Infinityで割ったりすると、NaNになります。

Infinity - Infinity // NaN 
Infinity / Infinity // NaN 

「Infinity」と「null」を計算すると、「null」は0になります。「0」と計算するのと同じ 。

null * Infinity // NaN 
null / Infinity // 0 
Infinity / null / Infinity 

「Infinity」と「undefined」で計算すると、返ってくるのは「NaN」 。

undefined + Infinity // NaN 
undefined-infinity // NaN 
undefined * Infinity // NaN 
undefined / Infinity // NaN 
Infinity / undefined // NaN 

数値に関するグローバルなアプローチ

parseInt( )

(1)基本的な使い方

parseInt法は文字列を整数に変換するのに使われます。

parseInt('123') // 123 

文字列の先頭にスペースがある場合、そのスペースは自動的に削除されます。

parseInt(' 81') // 81 

「parseInt」の引数が文字列でない場合は、いったん文字列に変換してから変換します。

parseInt(1.23) // 1 。
//に等しい 
parseInt('1.23') // 1 

文字列が整数に変換されるときは、1文字ずつ順番に変換されますが、数字に変換できない文字に出会うと、それ以上は進みません。

parseInt('8a') // 8 
parseInt('12**') // 12 
parseInt('12.34') // 12 
parseInt('15e2') // 15 
parseInt('15px') // 15 

上記のコードでは、parseIntの引数はすべて文字列なので、文字列の先頭が数字に変換できる部分だけを返します。

文字列の最初の文字が数字に変換できない場合(数字に続く記号は除く)はNaNを返します。

parseInt('abc') // NaN 
parseInt('.3') // NaN 
parseInt('') // NaN 
parseInt('+') // NaN 
parseInt('+1') // 1 

よってparseIntの戻り値は10進数の整数かNaNの2つしかありません

文字列が' 0x 'や' 0x 'で始まる場合、' parseInt 'はそれを16進数で解きます。

parseInt('0x10') // 16 

文字列が0で始まる場合は、それを10進数で解きます。

parseInt('011') // 11 

自動的に科学的スコアリングに変換される数字に対して、parseIntは科学的スコアリングの表現方法を文字列として扱うため、おかしな結果をもたらします。

parseInt(1000000000000000000000.5) // 1 。
//に等しい 
parseInt('1e+21') // 1 

parseInt(0.0000008) // 8 。
//に等しい 
parseInt('8e-7') // 8 

(2)進変換

parseIntメソッドはまた、2つ目のパラメータ(2から36の間)を受け入れ、解析された値の進数を表し、その値に対応する10進数を返します。デフォルトでは、parseIntの2つ目の引数は10 。デフォルトでは、10進から10進 。

parseInt('1000') // 1000 
//に等しい 
parseInt('1000', 10) // 1000 

次は進数を指定する数を変換する例 。

parseInt('1000', 2) // 8 
parseInt('1000', 6) // 216 
parseInt('1000', 8) // 512 

上のコードでは、二進法、六進法、八進法の1000は、それぞれ、十進法の8、216、512に等しい 。これは、「parseInt」の方法で進数の変換ができることを意味します。

2つ目の引数が数値でない場合は、自動的に整数に変換されます。この整数は2から36の間でなければ意味のある結果を得られず、それ以上であればNaNを返します。2つ目の引数が0undefined nullの場合は無視します。

parseInt('10', 37) // NaN 
parseInt('10', 1) // NaN 
parseInt('10', 0) // 10 
parseInt('10', null) // 10 
parseInt('10', undefined) // 10 

進数の指定に意味のない文字を含む文字列の場合は、最上位ビットから変換可能な数値のみを返します。最高位が変換できない場合は、そのままNaNを返します。

parseInt('1546', 2) // 1
parseInt('546', 2) // NaN 

バイナリでは1は意味のある文字で546は無意味な文字なので1行目は1、2行目はNaNを返します

前述したように、parseIntの1つ目の引数が文字列でない場合は、先に文字列に変換されます。これは意外な結果をもたらします

parseInt(0 x11,36) // 43 。
parseInt(0 x11,2) // 1 。

//に等しい 
parseInt(String(0x11), 36) 
parseInt(String(0x11), 2) 

//に等しい 
parseInt('17', 36) 
parseInt('17', 2) 

上のコードでは、16進数の0x11を17に変換してから文字列に変換します。そして、文字列17を36進数や2進数で解読し、結果431を返します。

この処理は、8進数のプレフィックスである0については、特に注意が必要 。

parseInt(011, 2) // NaN 

//に等しい 
parseInt(String(011), 2) 

//に等しい 
parseInt(String(9), 2)

上のコードでは1行目の011が最初に文字列9に変換されます。9はバイナリの有効文字ではないのでNaNを返します。parseInt('011', 2)`を直接計算すると、'011'は2進数として扱われ、3が返されます。

JavaScriptでは「0」というプレフィックスが付いた数字を8進数として扱うことができなくなり、「0」を無視する必要があります。しかし、互換性を保つために、ほとんどのブラウザにはこのような規定がありません。

parseFloat( )

parseFloat法は文字列を浮動小数点数に変換する方法 。

parseFloat('3.14') // 3.14 

文字列が科学的スコアリングルールに従っていれば、それに応じた変換が行われます。

parseFloat(' 14e-2') // 3.14 
parseFloat('0.0314E+2') // 3.14 

浮動小数点に変換できない文字が文字列に含まれている場合は、後戻りはせず、すでに変換された部分に戻ります。

parseFloat('3.14more non-digit characters') // 3.14 

' parseFloat 'メソッドは、文字列プリアンブルのフレームを自動的にフィルタリングします。

parseFloat('\t\v\r12.34\n ') // 12.34

引数が文字列でない場合は、文字列に変換してから変換します。

parseFloat([1.23]) // 1.23 。
//に等しい 
parseFloat(String([1.23])) // 1.23 。

文字列の最初の文字が浮動小数点数に変換できない場合はNaNを返します。

parseFloat([]) // NaN 
parseFloat('FF2') // NaN 
parseFloat('') // NaN 

上記のコードの中でも特に注目すべきはparseFloat 'が空文字列をNaN 'に変換すること

これらの特徴により、parseFloatの変換結果はNumber関数とは異なります。

parseFloat(true) // NaN 
Number(true) // 1 

parseFloat(null) // NaN 
Number(null) // 0 。

parseFloat('') // NaN 
Number('') // 0 

parseFloat('123.45#') // 123.45 
Number('123.45#') // NaN 

isNaN( )

isNaN手法は、ある値がNaN`であるかどうかを判定するのに使われます。

isNaN(NaN) // true 
falseさん(123)// false 

ただし、「isNaN」は数値に対してのみ有効で、他の値が入ってくると先に数値に変換されてしまいます。例えば、文字列が入ってきたときに、文字列が先に「NaN」に変換され、最後に「true」が返ってくるので要注意 。つまり、' isNaN 'は' true 'の値であり、' NaN 'ではなく文字列である可能性があります。

isNaN('Hello') // true 
相当します
isNaN(Number('Hello')) // true 

同様にオブジェクトと配列に対してもisNaNは' true 'を返します。

({}) // true 
//に等しい 
isNaN(Number({})) // true 

isNaN(['xzy']) // true 
//に等しい 
isNaN(Number(['xzy'])) // true 

ただし、空の配列や数値メンバが1つしかない配列に対しては、isNaNfalseを返します。

([]) // false 
([123]) // false 
(['123']) // false 

上記のコードがfalseを返す理由は、これらの配列がNumber関数によって数値に変換できるから 。「データタイプ変換」の章を参照してください。

から、「isNaN」を使う前に、データタイプを判断しておくとよいでしょう。

function myIsNaN(value){ 
return typeof value === 'number' && isNaN(value);
} 

より確実に「NaN」を判断する方法は、「NaN」が自分と等しくない唯一の値であることを利用して判断すること 。

function myIsNaN(value){ 
return value !value ;
} 

isFinite( )

isFiniteメソッドは、ある値が正常な値であるかどうかを示すブール値を返します。

isFinite(インフィニティ)// false 
isFinite(-Infinity) // false 
isFinite(NaN) // false 
isFinite(undefined) // false 
isFinite(null) // true 
isFinite(-1) // true 

' Infinity '、' -Infinity '、' NaN '、' undefined 'は' false 'を返しますが、' isFinite 'は' true 'を返します。

リンク参照

文字列

概要

定義します

文字列とは、1つもしくは2つの括弧の中に0個以上の文字が並んだもの 。

'abc'
"abc"

1つの括弧文字列の中には、2つの括弧を使うことができます。2つの括弧文字列の中には、1つの括弧を使うことができます。

'key = "value"'
"It's a long journey"

どちらも正当な文字列

単括弧文字列の内部に単括弧を使う場合は、内部の単括弧の前に逆スラッシュをつけてエスケープする必要があります。括弧文字列の中に括弧を入れる場合も同じ 。

'Did she say \'Hello\'?'
// "Did she say 'Hello'?"

"Did she say \"Hello\"?"
// "Did she say "Hello"?"

HTML言語の属性値は二重引用符を使用しているため、JavaScript言語の文字列は一重引用符のみを使用することを約束している項目が多くあります。もちろん、カギカッコだけでも十分 。重要なのは、文字列を1つのカギカッコで表現したり、2つのカギカッコで表現したりしないスタイルにこだわること 。

文字列はデフォルトで1行までしか書けないので、複数行に分けるとエラーが発生します。

'a
b
c'
// SyntaxError: Unexpected token ILLEGAL

文字列を3行に分割したコードでJavaScriptがエラーを起こします

長い文字列を複数の行に分割しなければならない場合は、行の末尾にフリップを使います。

var longString = 'Long \
long \
long \
string';

longString
// "Long long long string"

このコードは、フリップをつけることで、1行に書かれていた文字列を複数の行に分けて書くことができることを示しています。ただし、アウトプットするときは単行行のままなので、同じ一行に書いたのと同じことになります。他の文字(スペースなど)が入っていてはいけません。そうしないとエラーが発生します。

連接演算子(+)は、複数の文字列を連接することができます。長い文字列を複数行に分割して書くと、出力するときも連接します。

var longString = 'Long ' 
+ 'long ' 
+ 'long ' 
+ 'string' ;

複数行の文字列を出力したい場合、複数行の注釈を利用する変則的な方法があります。

(function () { /*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
// "line 1
// line 2
// line 3"

先の例では、出力される文字列は複数行 。

変換標識

\は文字列内に特殊な意味を持ち、特殊な文字を表すため、エスケープとも呼ばれます。

逆スジで表現する特殊な文字には、次のようなものがあります。

  • \0: null (\u0000) 。
  • \b:バックキー(\u0008) 。
  • \f:ページ替え(\u000C) 。
  • \n:改行(\u000A) 。
  • \r:エンターキー(\u000D)
  • \t:表を作ります(\u0009)
  • \v:垂直タブ(\u000B) 。
  • \':カギカッコ(\u0027) 。
  • \":カギカッコ(\u0022) 。
  • \\:逆斜棒(\u005C) 。

これらの文字の前に斜めの棒がついているのは、特別な意味を表しています。

console.log('1\n2')
// 1
// 2

上のコードでは、\nは改行を表していますが、出力時に2行に分かれています。

フリップには、3つの特殊な使い方があります。

(1) \HHH

3つの八進数(000から377)が続くことで文字を表しますHHHはUnicodeの符号点に対応します例えば\251は著作権記号を表しますこの方法では256文字しか出力できません

(2) \xHH

\xは16進数(00からFF)が2つ続き、1文字を表します。HHは、例えば\xA9のようなUnicodeの符号点に対応して著作権記号を表します。この方法では256文字しか出力できません

(3) \uXXXX

' \u 'の後に続く4つの16進数(' 0000 'から' FFFF ')は文字を表します。XXXX対応文字のUnicode符号点、例えば\u00A9は著作権記号を表します。

3つの特殊な文字の例を紹介します

'\251' // "©"
'\xA9' // "©"
'\u00A9' // "©"

'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true

特殊でない文字の前に逆スラッシュを使うと、逆スラッシュが省略されます。

'\a'
// "a"

上記のコードでは、aは通常の文字であり、前にフリップをつけることは特に意味がなく、自動的に省略されます。

文字列の通常の内容の中に逆スラッシュを含める必要がある場合、その前にもう1つの逆スラッシュを追加して、自分自身をエスケープする必要があります。

"Prev \\ Next"
// "Prev \ Next"

文字列と配列

文字列は文字配列として扱うことができるので、配列の括弧演算子を使ってある位置の文字(位置番号は0から)を返すことができます。

var s = 'hello' ;
s[0] // "h" 
s[1] // "e" 
s[4] // "o" 

//文字列に直接括弧演算子を使います。
'hello'[1] // "e" 

括弧内の数字が文字列の長さを超えていたり、括弧内がまったく数字でない場合は「undefined」を返します。

'abc'[3] // undefined 
'abc'[-1] // undefined 
'abc'['x'] // undefined

しかし、文字列と配列の類似性はそれだけ 。実際には、文字列の中の一つの文字を変えることはできません。

var s = 'hello';

delete s[0];
s // "hello"

s[1] = 'a';
s // "hello"

s[5] = '!';
s // "hello"

上記のコードは、文字列内の1文字を変更したり削除したりすることはできず、これらの操作は黙々と失敗することを示しています。

length属性

length属性は文字列の長さを返します。この属性も変更できません。

var s = 'hello';
s.length // 5

s.length = 3;
s.length // 5

s.length = 7;
s.length // 5

上のコードは文字列のlength属性は変更できませんが、エラーは発生しません。

文字セット

JavaScriptはUnicode文字セットを使用します。JavaScriptエンジン内では、すべての文字がUnicodeで表記されています。

JavaScriptはUnicodeで文字を保存するだけでなく、プログラム内で直接Unicodeの符号点を使用して文字を表現することができます。例えば\u00A9は著作権記号

var s = '\u00A9';
s // "©"

コードを解析するとき、JavaScriptはその文字がリテラル形式なのかUnicode形式なのかを自動的に認識します。ユーザーに出力すると、すべての文字がリテラルに変換されます。

var f\u006F\u006F = 'abc';
foo // "abc"

上のコードでは、1行目の変数名「foo」がUnicode形式表現で、2行目がリテラル形式表現 。JavaScriptが自動的に認識します

各文字はJavaScript内部で16ビット(2バイト)のutf-16形式で保存されていることも知っておく必要があります。つまりJavaScriptの単位文字長は16ビット、つまり2バイトで固定されています。

しかし、utf-16には2種類の長さがあります:符号点が' U+0000 'から' U+FFFF 'の間の文字の長さは16ビット(2バイト) 。符号点がU+10000からU+10FFFFの間の文字の場合、長さは32ビット(4バイト)であり、最初の2バイトは0xD800から0xDBFFの間であり、後の2バイトは0xDC00から0xDFFFの間である。例えば、ヤード時 u + 1 d306対応の文字を 𝌆、それ书いutf—16がxd834 xdf06

JavaScriptのutf-16のサポートは不完全であり、歴史的な理由から2バイトの文字しかサポートしておらず、4バイトの文字には対応していません。これは、JavaScriptの最初のバージョンがリリースされたとき、UnicodeのコードポイントはU+FFFFにしか割り当てられていなかったため、2バイトで十分だったから 。その後、Unicodeに含まれる文字はますます増え、4バイトの符号化が登場した。しかし、JavaScriptの標準はこの時点 でに固まっており、文字の長さを2バイトに統一していたため、4バイトの文字を認識することができなかった。そのバイト4文字で1クォーターの𝌆、ブラウザーが正確な認識である文字、しかしjavascript識別できない、これは二つの文字。

'𝌆'.length // 2

と上のコードのうち、javascript 𝌆の长さを2ではなく1。

まとめると、JavaScriptは符号点が' U+10000 'から' U+10FFFF 'の間の文字を2文字(' length 'プロパティ2)と見なします。この点を考慮して処理しなければなりません。つまり、JavaScriptが返す文字列の長さが正しくない可能性があります。

Base64トランスコード

テキストの中に印刷できない記号が含まれている場合があります。ASCIIコードの0から31までの記号が印刷できない場合は、Base64コードを使って印刷できる文字に変換します。別の場面では、バイナリデータをテキスト形式で渡す必要がある場合には、Base64でエンコードすることもできます。

Base64とは,任意の値を0 ~ 9,A ~ Z, a-z, +, /の64文字からなる印刷可能な文字に変換できる符号化方法 。主な目的は、暗号化ではなく、特殊な文字が出ないようにすること、プログラムの処理を簡単にすること 。

JavaScriptネイティブはBase64に関連する2つの手法を提供しています。

  • btoa():任意の値をBase64に変換します
  • atob(): Base64コードを元の値に変換します。
var string = 'Hello World!' ;
btoa(string) // "SGVsbG8gV29ybGQh" 
atob('SGVsbG8gV29ybGQh') // "Hello World!"

なお、これら2つの方法は非ASCIIコードの文字には適しておらず、エラーが発生します。

btoa('こんにちは')//報錯

非ASCII文字をBase64に変換するには、トランスコードのプロセスを間に挿入し、この2つの方法を使います。

function b64Encode(str){ 
return btoa(encodeURIComponent(str)) 
} 

function b64Decode(str){ 
return decodeURIComponent(atob(str));
} 

// "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "こんにちは"

リンク参照

対象

概要

生成方法

オブジェクト(object)はJavaScript言語の中心概念であり、最も重要なデータ型 。

対象とは何 か?簡単に言うと、オブジェクトは「キーと値のペア」の集合であり、でたらめな複合データの集合 。

var obj = {
  foo: 'Hello',
  bar: 'World'
};

上のコードでは括弧がオブジェクトを定義し変数objに値を与えます変数objはオブジェクトを指しますこのオブジェクトには2つのキー値ペア(2つの「メンバ」とも呼ばれます)が含まれており、1つ目のキー値ペアはfoo: 'Hello'で、fooは「キー名」(メンバの名前)、文字列Helloは「キー値」(メンバの値) 。キー名とキー値はコロンで区切ります。2つ目のキー値ペアはbar: 'World'で、barはキー名、Worldはキー値 。2つのキー値ペアはコンマで区切られています。

キー名

オブジェクトのキー名はすべて文字列 (ES6ではSymbol値もキー名として使えるようになりました)ので、カギカッコをつけてもつけなくても構いません。上のコードは次のようにも書けます。

var obj = {
  'foo': 'Hello',
  'bar': 'World'
};

キー名が数値の場合は、自動的に文字列に変換されます。

var obj = {
  1: 'a',
  3.2: 'b',
  1e2: true,
  1e-2: true,
  .234: true,
  0xFF: true
};

obj
// Object {
//   1: "a",
//   3.2: "b",
//   100: true,
//   0.01: true,
//   0.234: true,
//   255: true
// }

obj['100'] // true

上のコードでは、オブジェクト「obj」のすべてのキー名が数値のように見えますが、実は自動的に文字列に変換されています。

キー名が名前を識別する条件を満たしておらず(1文字目が数字だったり、スペースや演算子が含まれていたり)、数字でもない場合は、カギカッコを入れないとエラーが発生します。

// error
var obj = {
  1p: 'Hello World'
};

// not error
var obj = {
  '1p': 'Hello World',
  'h w': 'Hello World',
  'p+q': 'Hello World'
};

上のオブジェクトの3つのキー名は、いずれも名前の条件を満たしていないので、カギカッコをつける必要があります。

オブジェクトの各キー名はプロパティ(property)とも呼ばれ、そのキー値はどのようなデータ型でもよいの 。ある属性の値が関数の場合、その属性は一般に「メソッド」と呼ばれ、関数のように呼び出すことができます。

var obj = {
  p: function (x) {
    return 2 * x;
  }
};

obj.p(1) // 2

オブジェクトobjの属性pは関数を指しています

属性の値がオブジェクトのままの場合はリンク参照となります。

var o1 = {};
var o2 = { bar: 'hello' };

o1.foo = o2;
o1.foo.bar // "hello"

上のコードでは、オブジェクトo1の属性fooがオブジェクトo2を指しているので、o2の属性を連鎖的に参照することができます。

オブジェクトの属性間はコンマで区切られており、最後の属性の後にはtrailing commaを付けても、付けなくても構いません。

var obj = {
  p: 123,
  m: function () { ... },
}

上のコードではm属性の後のコンマはあってもなくても構いません

プロパティは動的に作成でき、オブジェクト宣言時に指定する必要はありません。

var obj = {};
obj.foo = 123;
obj.foo // 123

上記のコードでは、objオブジェクトのfoo属性に直接値を付けると、実行時にfoo属性が生成されます。

対象の引用

異なる変数名が同じオブジェクトを指している場合、それらはすべてそのオブジェクトへの参照、つまり同じメモリアドレスを指しています。1つの変数を修正すると、他のすべての変数に影響を与えます。

var o1 = {};
var o2 = o1;

o1.a = 1;
o2.a // 1

o2.b = 2;
o1.b // 2

上記のコードでは、o1o2は同じオブジェクトを指しているので、どちらかの変数に属性を追加すれば、もう一方の変数がその属性を読み書きすることができます。

このとき、ある変数の元のオブジェクトへの参照をキャンセルしても、別の変数には影響しません。

var o1 = {};
var o2 = o1;

o1 = 1;
o2 // {}

上記のコードでは、o1o2は同じオブジェクトを指しています。そして、o1の値が1になります。このとき、o2は影響を受けません。o2は元のオブジェクトを指しています。

ただし、このような参照は、2つの変数が同じ元の型の値を指している場合に限定されます。変数はすべて値のコピー

var x = 1;
var y = x;

x = 2;
y // 1

上のコードでは、xの値が変わってもyの値は変わらないので、yxは同じメモリアドレスを指しているわけではありません。

式 か 文 か?

行の先頭が括弧である場合それは式なのか文なのかという問題が生じます

{foo: 123 }

JavaScriptエンジンがこのコードを読むと2つの意味があります1つ目はこれが式でfoo属性を持つオブジェクトを表しているということ 2つ目はコードのブロックを表す文でタグfooが入っていて式123を指しています

このような曖昧さを避けるため、JavaScriptエンジンでは、このような状況に遭遇した場合、オブジェクトなのかコードブロックなのか特定できず、一律にコードブロックとして解釈します。

{console.log(123)} // 123 

上の文はコードのブロックであり、コードのブロックとして解釈しなければ実行できません。

対象として解釈する場合は、括弧の前に丸括弧を入れるとよいでしょう。丸括弧の中は、式にしかならないので、必ず大括弧は対象としてしか解釈できません。

({foo: 123}) //正しい 
({console.log(123)}) //エラー 

この違いはeval文(文字列の値を求める役割)に最もよく反映されます。

eval('{foo: 123}') // 123 
eval('({foo: 123})') // {foo: 123} 

上のコードで括弧がない場合、evalはコードのブロックだと理解します。丸括弧を入れると、対象として理解します。

属性の操作

属性の読み込み

オブジェクトの属性を読み取るには、ドット演算子と四角括弧演算子の2つの方法があります。

var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
obj['p'] // "Hello World"

上のコードはそれぞれ点の演算子と角括弧の演算子を採用して、属性pを読み取ります。

カギカッコ演算子を使う場合、キーネームをカギカッコ内に入れておかないと変数として扱われてしまうので注意が必要 。

var foo = 'bar' ;

var obj ={ 
foo: 1 
bar: 2 
} ;

obj.foo // 1 
obj[foo] // 2 

上のコードでは、オブジェクトのobjfoo属性を参照する場合、点演算子を使用すれば、fooは文字列 。括弧を使ってカッコを使わない場合「foo」は変数で文字列「bar」を指します

括弧演算子の内部では、式を使用することもできます。

obj['hello' + ' world' ]
obj[3 + 3] 

テンキーは括弧を入れなくてもいい 。文字列に変換されます。

var obj = {
  0.7: 'Hello World'
};

obj['0.7'] // "Hello World"
obj[0.7] // "Hello World"

上のコードでは、オブジェクトobjのテンキー0.7は、カッコをつけてもつけなくても、自働的に文字列に変換されます。

なお、数値キー名にはドット演算子は使用できません(小数点扱いになるため)。

var obj = {
  123: 'hello world'
};

obj.123 // 报错
obj[123] // "hello world"

上のコードの1つ目の式は、数値キー名123に対して点演算子を使用し、結果エラーを返します。2つ目の式は括弧演算子を使っています

属性の割り当て

点演算子や角括弧演算子は値を読み出すだけでなく値を割り当てるのにも使えます

var obj ={} ;

obj.foo = 'Hello' ;
obj['bar'] = 'World' ;

上のコードでは、点演算子と角括弧演算子を使い分け、属性に値を与えています。

JavaScriptでは属性の「ポストバインディング」が可能 。いつでも属性を追加することができます。

var obj = {p: 1} ;

//等価 

var obj ={} ;
obj.p = 1 ;

属性の閲覧

あるオブジェクトの全ての属性を見るために、` object.keysメソッドを使うことができます。

var obj ={ 
key1: 1 
key 2:2 
} ;

object.keys (obj);
// ['key1', 'key2' ]

属性の削除:deleteコマンド

「delete」コマンドはオブジェクトの属性を削除するために使われ、削除に成功すると「true」と返されます。

var obj = {p: 1} ;
object.keys (obj) // ["p"]

delete obj.p // true 
obj.p // undefined 
object.keys (obj) //[] 。

上のコードで、deleteコマンドはオブジェクトのobjp属性を削除します。削除後、再度読み込むと「p」属性は「undefined」に戻り、「object.keys」メソッドの戻り値には属性が含まれなくなります。

なお、存在しないプロパティを削除した場合、「delete」はエラーを返さず、「true」が返されます。

var obj ={} ;
delete obj.p // true 

上記のコードでは、オブジェクトobjp属性を持っていませんが、deleteコマンドはそのままtrueを返します。よって、deleteコマンドの結果、ある属性が存在すると認めることはできません。

ただ1つ、「delete」コマンドが「false」を返す場合、その属性は存在し、削除できません。

var obj = Object.defineProperty({}, 'p', {
  value: 123,
  configurable: false
});

obj.p // 123
delete obj.p // false

上記のコードでは、オブジェクトのobjp属性は削除できませんので、deleteコマンドはfalseを返します(object.definepropertyメソッドについては、「標準ライブラリ」のObjectオブジェクトの章をご覧ください)。

なお、deleteコマンドはオブジェクト自身の属性のみを削除することができ、継承された属性を削除することはできません(継承についてはオブジェクト指向プログラミングの項を参照)。

var obj ={} ;
delete obj.tostring // true 
obj.tostring // function toString() {[native code]}

上記のコードでは、toStringはオブジェクトのobj継承の属性であり、deleteコマンドはtrueを返しますが、この属性は削除されずにそのまま残っています。この例では、たとえdeletetrue`を返しても、属性は値を読み出すことができます。

属性は存在しますか:in演算子

in演算子はオブジェクトがある属性を含んでいるかどうかをチェックします(キー値ではなくキー名をチェックします)。含まれている場合はtrue、含まれていない場合はfalseを返します。左側が属性名を表す文字列で右側がオブジェクト

var obj = {p: 1} ;
'p' in obj // true 
'toString' in obj // true 

in演算子の問題は、どの属性がオブジェクト自身のものでどの属性が継承されたものかを識別できないこと 。上記のコードのようにオブジェクトのobj自体はtoString属性を持っていませんがin演算子はtrueを返します

その場合、オブジェクトの「hasOwnProperty」というメソッドを使って、オブジェクト自身の属性かどうかを判断します。

var obj ={} ;
if ('toString' in obj){ 
console.log(obj.hasownproperty ('toString')) // false 
} 

属性的遍歴:for… inループ

for …inループはオブジェクトの全ての属性を巡回します。

var obj = {a: 1, b: 2, c: 3} ;

for (var i in obj){ 
    console.log('キー名:',i);
    console.log('キー値:',obj[i]);
} 
//キー名:a 。
//キー値:1 。
//キーネーム:b 。
//キー値:2 。
//キー名:c 。
//キー値:3 。

for …inループの使い方の注意点は2つあります。

-それはオブジェクトのすべてのenumerable属性をトラバースし、トラバースできない属性はスキップします。
-オブジェクト自身の属性をトラバースするだけでなく、継承する属性もトラバースします。

例えば、オブジェクトはすべてtoString属性を受け継ぎますが、for…inループはこの属性を通過しません。

var obj ={} ;

// toString属性は存在します
obj.tostring () {[native code]}

for (var p in obj){ 
console.log(p);
} //出力がありません

上記のコードでは、オブジェクトのobjtoString属性を継承しています。これはデフォルトで「トラバース不可」になっているため、in`循環トラバースします。オブジェクト属性のトラバース可能性については、標準ライブラリの章のObjectの章を参照されたい。

継承された属性がトラバース可能であれば、' for…in循環は遍歴します。 が、一般的には、対象自身の属性だけを遍歴したいので、for…in`の場合は、「hasOwnProperty」メソッドと組み合わせて、ループ内である属性がオブジェクト自身の属性かどうかを判断します。

var person = {name: 'さん'};

for (var key in person){ 
    if (person.hasownproperty (key)){ 
        console.log(key) ;
    } 
} 
// name 

withフレーズ 。

withフレーズのフォーマットは以下の通り 。

with(対象){ 
語句 ;
} 

この機能は、同じオブジェクトの複数の属性を操作する際に、いくつかの書きやすさを提供します。

例1 
var obj ={ 
p1: 1 
2: 2 
} ;
with (obj){ 
p1 = 4 ;
p2 = 5 ;
} 
//に等しい 
obj.p1 = 4 ;
obj.p2 = 5 ;

例2 
with (document.links[0]){
console.log(href);
console.log(title) 
console.log(style);
} 
//に等しい 
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);

なお、「with」ブロック内に変数の付値操作がある場合は、現在のオブジェクトに既に存在する属性でなければなりません。

var obj ={} ;
with (obj){ 
p1 = 4 ;
p2 = 5 ;
} 

obj.p1 // undefined 
p1 // 4 

上のコードでは、オブジェクトobjp1属性を持っていません。p1に値を付けることはグローバル変数p1を作ることになります。オブジェクトobjの属性p1を定義し、それをwithブロック内で操作するのが正しい書き方 。

なぜなら、withブロックはスコープを変えず、その内部は現在のスコープのままだから 。これは「with」文の大きな問題点の1つで、結びつける相手が明確でないこと 。

with (obj){ 
console.log(x);
} 

単に上のコードのブロックを見ているだけでは、xがグローバル変数なのか、オブジェクトのobjの属性なのかは判断できません。これはコードの除算とモジュール化に非常に不利で、コンパイラもこのコードを最適化することができません。実行時に判断することになり、実行速度が遅くなります。そこで、「with」文ではなく、「with」の代わりに仮変数を使うことをお勧めします。

with(obj2.obj2.obj3){ 
console.log(p1 + p2);
} 

//と書くことができます
var temp = obj1.obj2.obj3 
console.log(temp.p1 + temp.p2) 

リンク参照

関数

関数は繰り返し呼び出すことができるコードのブロック 関数は入力された引数も受け取り、それぞれの引数には固有の戻り値があります。

概要

関数の宣言

JavaScriptには関数を宣言する3つの方法があります。

** (1) functionコマンド** 。

functionコマンド宣言コードのブロックは、関数 。「function」コマンドの後には関数名が付いています。関数名の後には1対の括弧が付いています。関数体は括弧の中に入れます。

function print(s){ 
console.log(s) ;
} 

上のコードには「print」という関数が付いていて、後で「print()」という形式を使って、対応するコードを呼び出すことができます。これを関数の宣言(Function Declaration)と言います。

**(2)関数の式 **

「function」コマンドで関数を宣言するだけでなく、変数の付値という書き方もできます。

var print = function(s){ 
console.log(s) ;
} ;

これは匿名関数を変数に与えますこのとき、この匿名関数は関数式(Function Expression)とも呼ばれます。代入文の等号の右側には式しか置かないから 。

関数式で関数を宣言する場合、functionコマンドの後に関数名を付けません。関数名を加えると、その関数名は関数体の内部でのみ有効であり、関数体の外部では無効 。

var print = function x(){
  console.log(typeof x);
};

x
// ReferenceError: x is not defined

print()
// function

上のコードは関数式に関数名xを入れています。このxが使えるのは関数の内部だけで、関数の式そのものを指します。この書き方の有用性は2つあり、1つは関数本体内で自分自身を呼び出すことができること、もう1つはエラー除去(エラー除去ツールが関数呼び出しスタックを表示するとき、関数名が表示され、ここでは匿名関数であることを表示しなくなります)を容易にすること 。そのため、次のような形式で関数を宣言することもよくあります。

var f = function f() {};

関数の式は文の終わりにセミコロンを付けて、文の終わりを示す必要があります。関数の宣言は、終わりの括弧の後にセミコロンを付けません。要するに、この二つの関数を宣言する方式は、違いがとても細かくて、ほぼ等価だと考えることができます。

** (3) Functionコンストラクタ** 。

関数を宣言する3つ目の方法は「コンストラクタ」 。

var add = new Function(
  'x',
  'y',
  'return x + y'
);

// 等しい
function add(x, y) {
  return x + y;
}

上のコードでは、コンストラクタは3つの引数を受け入れます。最後の引数を除いては、add関数の「関数体」 。

任意の数の引数をコンストラクタに渡すことができます。最後の引数だけが関数体として扱われます。

var foo = new Function(var foo) 
'return "hello world" ;' 
) ;

//に等しい 
function foo(){ 
return 'hello world' ;
} 

' Function 'コンストラクタは' new 'コマンドを使わずに全く同じ結果を返すことができます。

要するに、この関数宣言の仕方は非常に非直感的で、ほとんど使われていません。

関数の繰り返し宣言

同じ関数が何度も宣言されると、後の宣言が前の宣言を上書きしてしまいます。

function f(){ 
console.log(1);
} 
f() // 2 

function f(){ 
console.log(2);
} 
f() // 2 

上のコードでは、次の関数宣言は前の関数をカバーしています。また、関数名の向上(以下参照)により、前回の宣言はいつでも無効になるので注意が必要 。

括弧演算子、return文、再帰

関数の呼び出しには、括弧演算子を使います。括弧の中に、関数の引数を入れます。

function add(x, y){ 
return x + y ;
} 

add(1, 1) // 2 

上のコードでは関数名の後に括弧が付いていれば関数が呼び出されます

関数体の中のreturn文は、returnを意味します。JavaScriptエンジンはreturn文に遭遇すると、その後ろにある式の値を返すので、後に文が残っていても実行されません。つまり、return文に付いている式は、関数の戻り値 。' return '文は必須ではありません。それがなければ関数は何の値も返さず、' undefined 'を返します。

関数はそれ自身を呼び出すことができます。これを再帰(recursion)といいます。フィボナッチ数列を再帰的に計算するコード

function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}

fib(6) // 8

上のコードでは、fib関数の内部で再びfibを呼び出し、フィボナッチ数列の6番目の要素は8であることを計算します。

第一等市民

JavaScriptでは関数を値として扱い、他の値(数値、文字列、ブール値など)と同じような位置づけにします。値が使えるところには関数が使えます。例えば、変数や対象の属性に値を与えたり、引数として他の関数に入ってきたり、関数の結果として返ってきたりします。関数はあくまで実行可能な値であり、それ以外に特別なことはありません。

関数は他のデータ型と同等であるため、JavaScript言語では関数のことを「第一級市民」と呼びます。

function add(x, y) {
  return x + y;
}

// 
var operator = add;

// 
function a(op){
  return op;
}
a(add)(1, 1)
// 2

関数名のアップグレード

JavaScriptエンジンでは関数名を変数名と同一とするため、「function」コマンドで関数を宣言すると、関数全体が変数宣言のようにコードの先頭に上がってきます。したがって、次のコードはエラーを返しません。

f() ;

function f(){} 

上のコードは宣言前に関数fを呼び出しているように見えますしかし実際には「変数リフティング」により関数fがコードの先頭に上がっています呼び出しの前に宣言されていますしかし、代入文で関数を定義すると、JavaScriptはエラーを返します。

f() 
var f = function (){};
// TypeError: undefined is not a function 

上のコードは下の形式と同じ 。

var f ;
f() ;
f = function(){}; 

上のコードの二番目の行、fを呼び出す時、fは宣言されただけで、まだ値を割り当てていません。undefinedに等しいので、エラーが発生します。

なお、以下の例のように、functionコマンドとvar代入文で同じ関数を宣言する場合、関数の向上があるため、最後にvar代入文の定義が採用されます。

var f = function(){ 
console.log('1');
} 

function f(){ 
console.log('2');
} 

f() // 1 

上の例では、表面上は後に述べる関数fは、前のvar代入文をカバーすべき が、関数の上昇があるので、実際にはその逆 。

関数の属性と仕様

name属性

関数の「name」属性関数の名前を返します。

function f1() {}
f1.name // "f1"

変数の付値によって定義された関数の場合、name属性は変数名を返します。

var f2 = function () {};
f2.name // "f2"

しかし、これは変数の値が匿名関数である場合に限ります。変数の値が名前付き関数の場合、「name」属性は「function」キーワードの後の関数名を返します。

var f3 = function myName() {};
f3.name // 'myName'

上記のコードでは、f3.nameは関数の名前を返します。なお、真の関数名は' f3 'であり、' myName 'という名前は関数の内部でのみ使用できます。

name属性の1つの用途は、引数関数の名前を取得すること 。

var myFunc = function () {};

function test(f) {
  console.log(f.name);
}

test(myFunc) // myFunc

上のコードでは、関数test内部にname属性を通し、入ってくるパラメータが何の関数かを知ることができます。

length属性

関数の「length」属性は、関数が予測している引数の数、すなわち関数の定義内の引数の数を返します。

function f(a, b) {}
f.length // 2

上のコードは空関数fを定義しており、そのlength属性は定義時のパラメータの個数 。いくら引数が入力されても、length属性は2になります。

「length」属性は、オブジェクト指向プログラミングの「メソッドオーバーロード」を可能にするために、定義時と呼び出し時のパラメータの違いを判定する仕組みを提供します。

toString( )

関数のtoString()メソッドは関数のソースコードを表す文字列を返します。

function f() {
  a();
  b();
  c();
}

f.toString()
// function f() {
//  a();
//  b();
//  c();
// }

上記の例では、関数fのtoString()メソッドは、改行を含むf`のソースコードを返します。

これらのネイティブ関数に対して、toString()メソッドはfunction (){[native code]}を返します。

Math.sqrt.toString()
// "function sqrt() { [native code] }"

上記のコードの中で、 math.sqrt()はJavaScriptエンジンが提供するネイティブ関数で、toString()メソッドはネイティブコードのヒントを返します。

関数内の注釈を返すこともできます。

function f() {/*
これは一つ 
複数行の注釈があります
*/}

f.toString()
// "function f(){/*
//これはこちら 
//複数行の注釈があります
// */}"

これを利用して、変則的に複数行の文字列を実装することができます。

var multiline = function (fn){ 
var arr = fn.toString().split('\n');
return arr.slice(1, arr.length-1).join('\n');
} ;

function f(){/* 
これは一つ 
複数行の注釈があります
*/} 

multiline(f) 
// "これはこれ 
//多行注釈"

上記の例では、関数fの内部に複数行の注釈がありますが、toString()メソッドでfのソースコードを取得した後、最初と最後の2行を取り除くと、複数行の文字列になります。

関数ドメイン

定義します

スコープ(scope)とは変数が存在する範囲のこと 。ES5の仕様では、JavaScriptには2つのスコープしかありません。一つはグローバルスコープで、プログラム全体に変数が常に存在し、どこでも読み込むことができます。もう一つは関数スコープで、変数は関数内にしか存在しません。ES6はブロックレベルのスコープを追加しました。

トップ関数の場合、関数の外側で宣言されている変数がグローバル変数(global variable)であり、これは関数の内側で読み取られます。

var v = 1 ;

function f(){ 
console.log(v);
} 

f( )
// 1 

上のコードは関数fが内部的にグローバル変数vを読めることを示しています

関数の内部で定義された変数を、外部からは読み取れないものを「ローカル変数」(local variable)と呼びます。

function f(){ 
var v = 1 ;
} 

v // ReferenceError: v is not defined

上のコードでは、変数vは関数の内部で定義されているので、局所変数であり、関数以外では読み取れません。

関数内で定義された変数は、そのスコープ内に同名のグローバル変数を上書きします。

var v = 1 ;

function f(){ 
var v = 2 ;
console.log(v);
} 

f() // 2 
v // 1 

上記のコードでは、変数vは関数の外側と内側の両方で定義されています。その結果、関数の内部定義では、局所変数vがグローバル変数vをカバーします。

varコマンドの場合、局所変数は関数内で、他のブロック内でのみ宣言され、すべてグローバル変数となります。

if (true){ 
var x = 5 ;
} 
console.log(x);// 5 

上記のコードでは、変数xは条件判定ブロック内で宣言されており、その結果はグローバル変数となり、ブロックの外から読み取ることができます。

関数内の変数の上昇

グローバルドメインと同様に、関数ドメイン内でも「変数の上昇」現象が起こります。' var 'コマンド宣言された変数は、変数宣言がどの位置にあっても関数体の頭の部分まで引き上げられます。

function foo(x) {
  if (x > 100) {
    var tmp = x - 100;
  }
}

// 等同于
function foo(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  };
}

関数自体のスコープ

関数自体も値であり、作用域を持っています。そのスコープは変数と同じで、それが宣言されるときのスコープであり、実行されるときのスコープとは関係ありません。

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1

上記のコードでは、関数xは関数fの外部で宣言されているので、そのスコープは外層に結び付けられており、内部変数aは関数fの内部に値を取りませんので、2ではなく1を出力します。

つまり、関数が実行されたときにあるスコープは、定義されたときのスコープであって、呼び出したときのスコープではありません。

間違いやすいのは、関数Aが関数Bを呼び出すときに、関数Bが関数Aの内部変数を参照しないことを考えていないこと 。

var x = function () {
  console.log(a);
};

function y(f) {
  var a = 2;
  f();
}

y(x)
// ReferenceError: a is not defined

上のコードは関数xを引数として、関数yを入力します。しかし、関数xは関数yの外側で宣言され、ドメインは外層と結び付けられているため、関数yの内部変数aが見つからず、エラーが発生します。

同様に、関数体の内部で宣言された関数、ドメインは関数体の内部に結合されます。

function foo(){ 
var x = 1 ;
function bar(){ 
console.log(x);
} 
return bar ;
} 

var x = 2 ;
var f = foo() ;
f() // 1 

上記のコードでは関数「foo」は関数「bar」を内部宣言しており、barのスコープは関数「foo」をバインディングしています。" foo "の外側で" bar "を取り出して実行するとき変数" x "は" foo "の外側の" x "ではなく" foo "の内側の" x "を指しますこのメカニズムこそが、後述する「閉包」という現象を構成しているの 。

パラメータ

概要

関数を実行するときに、外部のデータが必要になることがあります。外部のデータによって異なる結果が得られます。

function square(x){ 
return x * x ;
} 

square(2) // 4 
square(3) // 9 

上の式のxがスクエア関数のパラメータ 。実行するたびにこの値を与えなければ結果は得られません

パラメータの省略

関数の引数は必要ありません。JavaScriptでは引数を省略できます。

function f(a, b){ 
return a ;
} 

f(1, 2, 3) // 1 
f(1) // 1 
f() // undefined 

f.ength // 2 

上記のコードの関数「f」は2つのパラメータを定義していますが、JavaScriptは実行時にいくつのパラメータを提供しても(提供しなくても)エラーは発生しません。省略したパラメータの値は「undefined」となります。なお、関数の「length」属性は実際に入ってくるパラメータの数とは関係なく、関数が入ると予想されるパラメータの数のみを反映します。

ただし、前のパラメーターだけを省略して、後ろのパラメーターを残しておくことはできません。上位パラメータを省略しなければならない場合は、明示的にundefinedを入れます。

function f(a, b){ 
return a ;
} 

f(, 1) // SyntaxError: Unexpected token,(…)
f(undefined, 1) // undefined 

上のコードでは、1つ目のパラメータを省略するとエラーが発生します。

渡し方

引数が元の型の値(数値、文字列、ブール値)の場合はpasses by valueで渡します。これは、関数の内部でパラメータ値を変更することは、関数の外部には影響しないことを意味します。

var p = 2 ;

function f(p){ 
p = 3 ;
} 
f(p) ;

p // 2 

上記のコードでは、変数pは元の型の値であり、関数fは値を渡す形で入ってきます。したがって、関数内では、pの値は元の値のコピーであり、いくら修正しても元の値には影響しません。

ただし、関数パラメータが複合型の値(配列、オブジェクト、その他の関数)の場合は、パス・バイ・パス(pass by reference)で渡されます。つまり、入ってくる関数の元の値のアドレスなので、関数内でパラメータを変更すると元の値に影響が出ます。

var obj = {p: 1} ;

function f(o){ 
o.p = 2 ;
} 
f(obj) ;

obj.p // 2 

上のコードでは引数オブジェクトobjのアドレスを入力関数fにしますしたがって、関数内でobjの属性pを修正すると元の値に影響します。

なお、関数内でパラメータオブジェクトの属性を変更するのではなく、パラメータ全体を置き換えた場合、元の値には影響しません。

var obj =[1, 2, 3] ;

function f(o){ 
o =[2, 3, 4] ;
} 
f(obj) ;

obj //[1, 2, 3] 。

上記のコードでは、関数f()の内部で、パラメータオブジェクトobjが全体として別の値に置き換えられています。この場合、元の値には影響しません。これは、形式パラメータ(o)の値は実際にはパラメータobjのアドレスであり、新たにoに値を付けることでo`が別のアドレスを指すことになり、元のアドレスに保存された値は当然影響を受けないから 。

同名のパラメータ

同名のパラメータがあれば、最後に出てきた値を取ります。

function f(a, a){ 
console.log(a) ;
} 

f(1, 2) // 2 

上記のコードでは、関数f()には2つの引数があり、引数名はどちらもa 。値をとるときは、後ろのaを基準にします。後ろのaは値がないか省略されていても、それを基準にします。

function f(a, a){ 
console.log(a) ;
} 

f(1) // undefined 

関数f()を呼び出すとき、2つ目の引数が与えられていないので、aの取り方はundefinedになります。このとき、最初のaの値を取得する場合は、argumentsオブジェクトを使用します。

function f(a, a){ 
console.log(arguments[0]);
} 

f(1) // 1 

argumentsオブジェクト

(1)定義

JavaScriptでは関数が不定数の引数を持つことができるため、関数本体内で全ての引数を読み込む仕組みが必要 。これがargumentsというオブジェクトの由来 。

' arguments 'オブジェクトは関数実行時のパラメータを全て含んでいます' arguments[0] 'が1つ目のパラメータ' arguments[1] 'が2つ目のパラメータ'など このオブジェクトは関数体の中でのみ、使用することができます。

var f = function (one){ 
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
} 

f(1, 2, 3) 
// 1 
// 2 
// 3 

通常モードでは、argumentsオブジェクトは実行中に修正できます。

var f = function(a, b){ 
arguments[0] = 3 ;
arguments[1] = 2 ;
return a + b ;
} 

f(1, 1) // 5 

上記のコードでは、関数f()の呼び出し時に入ってくる引数が、関数内で32に修正されています。

厳密モードでは、argumentsオブジェクトは関数パラメータと連働しません。つまり、argumentsオブジェクトの修正は、実際の関数パラメータには影響しません。

var f = function(a, b){ 
'use strict' ;厳しいモードをオンにします
arguments[0] = 3 ;
arguments[1] = 2 ;
return a + b ;
} 

f(1, 1) // 2 

上のコードの中で、関数の内部は厳格なモードで、この時argumentsオブジェクトを修正して、真のパラメータabに影響しません。

argumentsオブジェクトのlength属性によって、関数呼び出し時にいくつのパラメータを持っているかがわかります。

function f(){ 
return argumens.length;
} 

f(1, 2, 3) // 3 
f(1) // 1 
f() // 0 

**(2)と配列の関係 **

argumentsは配列に似ていますがオブジェクト 配列固有の手法(例えばsliceforEach)は、argumentsオブジェクト上では使えません。

argumentsオブジェクトに配列法を使わせる場合、真の解決法はargumentsを真の配列にすること 。次は2つの一般的な変換方法 。slice方法と新しい配列を1つずつ埋めていきます。

var args = Array.prototype.slice.call(arguments);

// 或
var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}

**(3)カリ属性 **

' arguments 'オブジェクトは' callee '属性を持ち、元の関数を返します。

var f = function(){ 
console.log(argumens.callee === f);
} 

// true 

` argumens.callee 'によって、関数自体を呼び出すことができます。この属性はシビアモードでは無効になっているためお勧めできません。

関数のその他の知識

閉包

JavaScriptの特徴である閉包(closure)は、多くの高度なアプリケーションが閉包に依存しています。

閉包を理解するには、まず変数スコープを理解しなければなりません。先述したように、JavaScriptにはグローバルドメインと関数ドメインという2つのドメインがあります。関数の内部からグローバル変数を直接読み取ることができます。

var n = 999 ;

function f1(){ 
console.log(n);
} 
f1() // 999 

関数f1がグローバル変数nを読み取ります

しかし、通常、関数の内部で宣言されている変数を関数の外部から読み取ることはできません。

function f1(){ 
var n = 999 ;
} 

console.log(n) 
// Uncaught ReferenceError: n is not defined (

上記のコードでは、関数f1の内部宣言変数nは、関数以外では読み取れません。

いろいろな理由で、関数内の局所変数を求める必要がある場合 。普通はできませんが、変則的な方法でしかできません。それは、関数の内部に、もう一つの関数を定義すること 。

function f1(){ 
var n = 999 ;
function f2(){ 
console.log(n);// 999 
} 
} 

上記のコードでは、関数f2は関数f1の内部にあります。このとき、f1の内部のすべての局所変数は、f2に対して可視 。しかし逆はいけません。f2の内部の局所変数は、f1には見えません。これはJavaScript特有の「チェーンスコープ(chain scope)」構造で、子オブジェクトが親オブジェクトの変数を1段階ずつ上に探していきます。親対象のすべての変数は子対象に対して可視であり、逆は成り立たないの 。

「f2」が「f1」の局所変数を読み取れるのなら、「f2」を戻り値にすれば、「f1」の外側から内部変数を読み取れるのではないでしょうか!

function f1(){ 
var n = 999 ;
function f2(){ 
console.log(n);
} 
return f2 ;
} 

var result = f1() ;
result() ;// 999 

関数f1の戻り値が関数f2 が、f2が内部変数f1を読み取れるので、外部からf1の内部変数を得ることができます。

閉包とは関数f2、つまり、他の関数の中の変数を読み取れる関数のこと 。JavaScript言語では、関数内の副関数のみが内部変数を読み取れるため、閉包は単に「関数内に定義された関数」と理解することができます。閉包の最大の特徴は、生まれた環境を「覚える」ことができること 。例えば、f2は生まれた環境f1を覚えるので、f2からf1の内部変数を得ることができます。本質的には、閉包は関数の内部と関数の外部を結ぶ橋 。

閉包の最大の用途は2つあります。1つは外側の関数の中の変数を読み込むこと 。もう1つは変数を常にメモリに保持すること 。次の例を見てください。閉包は、内部変数が前回呼び出したときの演算結果を記憶するようにします。

function createIncrementor(start){ 
return function(){ 
return start++;
} ;
} 

var inc = createIncrementor(5);

inc() // 5 
inc() // 6 
inc() // 7 

「start」は関数「createIncrementor」の内部変数 閉包によってstartの状態が保持され、それぞれの呼び出しは前回の呼び出しに基づいて計算されます。ここからわかるように、閉包incは関数createIncrementorの内部環境が常に存在しています。従って、閉包は関数内のスコープのインタフェースと見ることができます。

なぜ閉包は外層関数の内部変数を返すことができるのでしょうか?閉包(上の例のinc)が外層変数(start)に使われてしまい、外層関数(createIncrementor)がメモリから解放されないから 。閉包がゴミ回収機構によって消去されない限り、外側の関数によって提供される実行環境も消去されず、その内部変数は閉包が読み取るための現在値を常に保持します。

閉包の別の用途は、オブジェクトのプライベート属性とプライベートメソッドをカプセル化すること 。

function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('木三');
p1.setAge(25);
p1.getAge() // 25
} 

上のコードでは、関数Personの内部変数_ageが、閉包getAgesetAgeによって、オブジェクトp1を返すプライベート変数になります。

なお、外側の関数が実行されるたびに新たな閉包が生成され、この閉包が外側の関数の内部変数を保持するため、メモリの消費が大きくなります。そのため、閉包を悪用してはいけません。さもなければ、ウェブページの性能に問題が発生します。

すぐに呼び出す関数式(IIFE) 。

JavaScriptの構文では、括弧()が関数名に続き、関数を呼び出すことを意味します。例えば、print()は`print関数を呼び出すことを意味します。

場合によっては、関数を定義した直後に、その関数を呼び出す必要があります。このとき、関数の定義の後に括弧を入れてはいけません。構文的な誤りが発生します。

function(){ /* code */ }();
// SyntaxError: Unexpected token (

このエラーの原因は、「function」というキーワードが文にも式にもなるから 。

//語句 
function f(){} 

//式 
var f = function f(){} 

式として使う場合は、関数を定義したまま括弧を入れて呼び出すことができます。

var f = function f(){return 1}();
f // 1 

上記のコードでは、関数が定義されたときに括弧を入れて呼び出すとエラーが発生しません。その理由は「function」を式とし、エンジンが関数を値として扱うから 。この場合、間違いを返すことはありません。

構文解析の曖昧さを避けるため、JavaScriptでは「function」というキーワードが行頭に出てきたら、すべて文として解釈するようになっています。そのため、行頭が「function」というキーワードになっているのを見たエンジンは、この段落はすべて関数の定義であり、括弧で終わるべきではないと勘違いしてしまいます。

関数が定義されてすぐに呼び出される解決策は、「function」を行頭に出さず、エンジンがそれを式として理解すること 。一番簡単なのは、括弧の中に入れること 。

(function(){ /* code */ }());
// 或
(function(){ /* code */ })();

どちらも括弧で始まるので、エンジンは関数の定義文ではなく、式が続くと認識するので、ミスを避けることができます。これをimmediately-invoked Function Expression(即座に呼び出される関数式)、略してIIFEと呼びます。

なお、どちらも最後のセミコロンは必須 。セミコロンを省略して、IIFEが二つ続いていると間違えてしまうかもしれません。

ミスを返します
(function(){ /* code */ }())
(function(){ /* code */ }())

上のコードの2行の間にはセミコロンがありません。JavaScriptはそれらをつなげて解釈し、2行目を1行目のパラメータとして解釈します。

一般的には、インタプリタに関数の定義を式で処理させるどんな方法でも同じ効果が得られます。例えば、次の3つの書き方 。

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

さらに、次のように書いてもいいでしょう。

!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();

通常は匿名関数に対してのみ、このような「即座に実行する関数式」が使われます。その目的は2つあります:1つは関数に名前を付ける必要がなく、グローバル変数の汚染を回避すること 。2つ目は、IIFEの内部には個々のスコープが形成されており、外部からは読み取れないプライベートな変数をカプセル化することができます。

// 書き方1
var tmp = newData;
processData(tmp);
storeData(tmp);

// 2
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());

コードの書き方2は1よりも良い グローバル変数の汚染を完全に回避できます

evalコマンド

基本的な使い方

evalコマンドは引数として文字列を受け取り、文として実行します。

eval('var a = 1;');
a // 1

上のコードは文字列を文として実行し変数aを生成します

パラメータ文字列が文として動作しない場合、エラーが発生します。

eval('3x') // Uncaught SyntaxError: Invalid or unexpected token

evalに置かれた文字列は、独自の存在意義を持つべきであり、eval以外のコマンドと併用してはいけません。例えば、次のコードはエラーが発生します。

eval('return;'); // Uncaught SyntaxError: Illegal return statement

returnは単独では使用できないので、関数内で使用する必要があります。

evalの引数が文字列でない場合は、そのまま戻ります。

eval(123) // 123 

evalは独自のスコープを持たず、現在のスコープ内で実行されるため、現在のスコープの変数の値が修正される可能性があり、セキュリティに問題があります。

var a = 1;
eval('a = 2');

a // 2

上記のコードでは、evalコマンドが外部変数aの値を変更しています。このため、evalは安全リスクがあります。

このようなリスクを防ぐため、JavaScriptでは厳格なパターンを使えば、変数の内部宣言が外部ドメインに影響を与えないように規定しています。

(function f() {
  'use strict';
  eval('var foo = 123');
  console.log(foo);  // ReferenceError: foo is not defined
})()

上のコードの中で、関数fの内部は厳格なパターンで、この時evalの内部宣言のfoo変数、外部に影響しません。

ただし、厳密なモードでもevalは現在のスコープの変数を読み書きすることができます。

(function f() {
  'use strict';
  var foo = 1;
  eval('foo = 2');
  console.log(foo);  // 2
})()

上のコードでは、厳しいモードではevalの内部が外部変数に書き換えられていますが、セキュリティリスクは依然として残っています。

つまり、evalの本質は、現在のスコープの中に、コードを注入すること 。JavaScriptエンジンはセキュリティ上のリスクや実行速度の最適化に不利なため、一般的には推奨されていません。通常、「eval」はJSONデータの文字列を解析するのが一般的 が、正しくはネイティブの「json.parse」メソッドを使います。

evalのエイリアス呼び出し

前述したようにevalは実行速度の最適化に役立ちません。さらに厄介なのは、エンジンが静的コード解析の段階で「eval」を実行しているのか見分けがつかない場合 。

var m = eval;
m('var x = 1');
x // 1

上のコードで変数mはevalの別名 静的コード解析フェーズでは、エンジンが認識できないm('var x = 1')evalコマンドを実行します。

evalのエイリアスがコードの最適化に影響しないことを保証するために、JavaScriptの標準はすべてエイリアスを使用してevalを実行することを規定して、eval内部はすべてグローバルドメイン 。

var a = 1;

function f() {
  var a = 2;
  var e = eval;
  e('console.log(a)');
}

f() // 1

上のコードでは、evalは別名呼び出しなので、関数であっても、そのスコープはグローバルスコープなので、出力のaはグローバル変数 。すると、エンジンはe()が現在の関数スコープに影響を与えないことを確認し、最適化の際にその行を除外することができます。

evalのエイリアス呼び出しには様々な形がありますが、直接呼び出しでない限り、エイリアス呼び出しに該当します。エンジンはeval()の1つだけを直接呼び出しと見分けることができます。

eval.call(null, '...')
window.eval('...')
(1, eval)('...')
(eval, eval)('...')

これらの形式はevalの別名呼び出しであり、ドメインはグローバルドメイン 。

リンク参照

ベン・アルマン [Immediately-Invoked Function Expression] 。(iife)] (http://benalman.com/news/2010/11/immediately-invoked-function-expression/)
マーク・ダゲット [Functions] explained] (https://web.archive.org/web/20160911170816/http://markdaggett.com/blog/2013/02/15/functions-explained/)
−juriy zaytsev、[named function expressions demystified] (http://kangax.github.io/nfe/)

配列

定義します

配列(array)とは、1組の値を順番に並べたもの 。それぞれの値の位置には(0から)番号が振られており、その配列全体は角括弧で表されています。

var arr = ['a', 'b', 'c'];

上のコードのabcは配列を構成し、両端の括弧は配列のフラグ 。aは0番ポジション、bは1番ポジション、cは2番ポジション 。

定義時に値を付けることに加えて、配列は定義後に値を付けることもできます。

var arr =[] ;

arr[0] = 'a' ;
arr[1] = 'b' ;
arr[2] = 'c' ;

どんな種類のデータでも、配列に入れることができます。

var arr = [
  {a: 1},
  [1, 2, 3],
  function() {return true;}
];

arr[0] // Object {a: 1}
arr[1] // [1, 2, 3]
arr[2] // function (){return true;}

上の配列arrの3つのメンバーは、対象、配列、関数の順 。

配列の要素が配列のままであれば、多次元配列を形成します。

var a = [[1, 2], [3, 4]];
a[0][1] // 2
a[1][1] // 4

配列の本質

配列は本質的に特別な対象 typeof演算子が返す配列の型は`object

typeof [1, 2, 3] // "object"

上記のコードによると、typeof演算子は配列の型をオブジェクトとして認識します。

配列の特殊性は,キー名が整数の並び(0,1,2…)であること 。 。

var arr = ['a', 'b', 'c'];

Object.keys(arr)
// ["0", "1", "2"]

上記のコードでは、` object.keysメソッドは配列のすべてのキー名を返します。配列のキー名が整数の0、1、2であることがわかります。

配列メンバーのキー名が固定されているため(デフォルトでは常に0、1、2…) 。そのため、要素ごとにキー名を指定する必要はなく、オブジェクトのメンバーごとにキー名を指定しなければなりません。JavaScript言語では、オブジェクトのキー名はすべて文字列と決まっているので、配列のキー名も文字列 。数値で読めるのは、非文字列のキー名が文字列に変換されるから 。

var arr = ['a', 'b', 'c'];

arr['0'] // 'a' 
arr[0] // 'a' 

上のコードに数値と文字列をキー名にしており、その結果、配列を読み取ることができます。その理由は、数値キー名が自動的に文字列に変換されるから 。

なお、これは代入においても成立します。1つの値はいつも文字列に変換してからキー名として割り当てます。

var a = [];

a[1.00] = 6;
a[1] // 6

上記のコードでは、1.00を文字列に変換すると1なので、テンキー1で値を読み取ることができます。

前章で、オブジェクトには点構造(object.key)と角括弧構造(object[key])の2種類のメンバを読み取る方法があると書きました。ただし、数値のキー名に点構造は使えません。

var arr = [1, 2, 3];
arr.0 // SyntaxError

arr.0の表記は正当ではありません。個々の数値を識別子(identifier)とすることはできません。よって、配列のメンバーは括弧arr[0]でしか表すことができません(括弧は演算子なので、数値を受け入れることができます)。

length属性

配列のlength属性、配列のメンバの数を返します。

['a', 'b', 'c'].length // 3

JavaScriptは32ビットの整数を使用し、配列の要素数を保持します。つまり、メンバは4294967295個(232 - 1)まで 。つまり、「length」属性の最大値は4294967295になります。

配列である限り、必ず「length」属性があります。この属性は、キー名の最大整数に1を加えた動的な値 。

var arr = ['a', 'b'] ;
arr.length // 2 

arr[2] = 'c' ;
arr.length // 3 

arr[9] = 'd' ;
arr.length // 10 

arr[1000] = 'e' ;
arr.length // 1001 

上のコードによると、配列のテンキーは連続である必要はなく、length属性の値は常に最大の整数キーより1大きい 。また、配列が動的なデータ構造であることも示しています。

「length」属性は書き込み可能 。現在のメンバの数よりも小さい値を人為的に設定すると、その配列のメンバの数は自動的にlengthが設定した値まで減少します。

var arr = ['a', 'b', 'c'];
arr.length // 3 

arr.length = 2 ;
arr // ["a", "b"]

このコードは、配列のlength属性が2に設定されている場合(つまり、最大の整数キーは1のみ)、整数キー2(値c)は配列から除外され、自働的に削除されることを示しています。

配列を空にする効果的な方法は、length属性を0にすること 。

var arr = ['a', 'b', 'c'];

arr.length = 0 ;
arr //[] 。

現在の要素の数よりもlengthが大きいように人為的に設定すると、配列のメンバの数はこの値まで増加します。

var a = ['a'] ;

a.ength = 3 ;
a[1] // undefined 

上記のコードは、「length」属性を配列数より大きく設定した場合、新たに読み込んだ位置が「undefined」に戻ることを示しています。

人為的にlengthを不正な値に設定すると、JavaScriptがエラーを返します。

//負の値を設定します。
[].length = -1 ;
// RangeError: Invalid array length 

//配列要素の数は2の32乗以上 。
[].length = math.pow(2, 32) ;
// RangeError: Invalid array length 

//文字列を設定します。
[].length = 'abc' ;
// RangeError: Invalid array length 

配列は本質的にオブジェクトなので、配列に属性を追加することができますが、これはlength属性の値には影響しません。

var a =[] ;

a['p'] = 'abc' ;
a.length // 0 

a[2.1] = 'abc' ;
a.length // 0 

上のコードは配列のキーをそれぞれ文字列と小数に設定して、結果はいずれもlength属性に影響しません。なぜなら、length属性の値は最大の数字キーに1を加えたものとなり、この配列には整数キーがないためlength属性は`0のままとなります。

配列のキー名がオーバーレンジの数値を追加している場合、そのキー名は自動的に文字列に変換されます。

var arr =[] ;
arr[-1] = 'a' ;
arr[math.pow (2, 32)] = 'b' ;

arr.length // 0 
arr[-1] // "a" 
arr[4294967296] // "b" 

上のコードでは、配列arrに2つの不適法な数字キーを追加しましたが、length属性は変わりませんでした。これらの数字キーは文字列キー名になります最後の2行が値を取るのは、キー値を取るときにテンキー名がデフォルトで文字列になるから 。

in演算子

あるキー名の演算子inが存在するかどうかをチェックします。オブジェクトにも当てはまりますし、配列にも当てはまります。

var arr = ['a', 'b', 'c'];
2 in arr // true 
'2' in arr // true 
4 in arr // false 

上のコードは、配列にキー名2があることを示しています。キー名はすべて文字列なので、数値2は自動的に文字列に変換されます。

配列のある位置が空の場合in演算子はfalseを返します

var arr =[] ;
arr[100] = 'a' ;

100 in arr // true 
1 in arr // false 

上のコードでは、配列arrは1つのメンバarr[100]だけで、他の位置のキー名はfalseを返します。

for …inループと配列の移動

for …inループはオブジェクトだけでなく複数のグループをもトラバースすることができます。

var a =[1, 2, 3] 

for (var i in a){ 
console.log(a[i]);
} 
// 1 
// 2 
// 3 

が,for…inは数組のすべてのテンキーをトラバースするだけでなく、非テンキーもトラバースします。

var a =[1, 2, 3] ;
a.oo = true ;

for (var key in a){ 
console.log(key) ;
} 
// 0 
// 1 
// 2 
// foo 

上のコードは数群をトラバースする時、非整数キーfooもトラバースします。 から、for…in数組を遍歴します。

配列のトラバースは、forループまたは`while」ループの使用が考えられます。

var a = [1, 2, 3];

// for loop
for(var i = 0; i < a.length; i++) {
  console.log(a[i]);
}

// while loop
var i = 0;
while (i < a.length) {
  console.log(a[i]);
  i++;
}

var l = a.length;
while (l--) {
  console.log(a[l]);
}

上のコードは3種類のトラバースで書かれています最後の方法は逆トラバースで、最後の元から最初の元へトラバースします。

配列のforEach法は、配列を遍在させることもできます。詳しくは標準ライブラリのArrayオブジェクトの章を参照してください。

var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
  console.log(color);
});
// red
// green
// blue

配列の空き

配列のある位置が空の元、つまり2つのコンマの間に何の値もないときは、その配列に空きがあるといいます。

var a = [1, , 1];
a.length // 3

上のコードによると、配列の空きは「length」属性に影響しません。この位置には値がありませんが、エンジンはこの位置が有効だと考えます。

ただし、最後の要素にコンマがついていても空きは生じません。つまり、このコンマがあってもなくても結果は同じなの 。

var a = [1, 2, 3,];

a.length // 3
a // [1, 2, 3]

上のコードでは、配列の最後のメンバーの後ろにコンマが付いていますが、これはlength属性の値には影響しません。

配列の空きは読み取ることができ、undefinedを返します。

var a =[,,,] 
a[1] // undefined 

deleteコマンドを使用してメンバの配列を削除すると、空きが形成され、length属性には影響しません。

var a =[1, 2, 3] 
delete a[1] 

a[1] // undefined 
a.length // 3 

上のコードはdeleteコマンドで配列の2番目の要素を削除しました。この位置は空席となりますがlength属性には影響しません。つまり、length`属性は空きをフィルタリングしません。なので、「length」属性を使った配列トラバースには十分注意が必要 。

配列のある位置が空席であることと、ある位置が「undefined」であることは違います。空席の場合は、配列のforEach法、for… 。in構造、および' object.keys 'メソッドをトラバースすると、空席はスルーされます。

var a = [, , ,];

a.forEach(function (x, i) {
  console.log(i + '. ' + x);
})
// no output

for (var i in a) {
  console.log(i);
}
// no output

Object.keys(a)
// []

ある位置が「undefined」であれば、横断の際にスキップされることはありません。

var a = [undefined, undefined, undefined] ;

a. foreach (function (x, i) {a. foreach (function (x, i))} 
console.log(i + '. ' + x);
) ;
// 0. undefined 
// 1. undefined 
// 2. undefined 

for (var i in a){ 
console.log(i);
} 
// 0 
// 1 
// 2 

object.keys (a) 
// ['0', '1', '2']

つまり、空位とは配列がその要素を持っていないのでトラバースされないことを意味し、undefinedは配列がその要素を持っていることを意味し、値はundefinedなのでトラバースされないことを意味します。

配列のようなオブジェクト

全てのキー名が正の整数または0であり、length属性を持つオブジェクトは配列に似ており、構文的には「配列に似たオブジェクト」(array-like object)と呼ばれます。

var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function

オブジェクトobjは配列のようなオブジェクト しかし、「配列に似たオブジェクト」は配列ではありません。なぜなら、それらは配列特有の手法を持っていないから 。オブジェクトのobjには配列のpushメソッドがなく、このメソッドを使うとエラーが発生します。

「配列に似たオブジェクト」の根本的な特徴は、「length」属性を持っていること 。「length」属性があれば、このオブジェクトは配列に似ていると考えることができます。ただし、この「length」属性は働作値ではなく、メンバーが変わっても変化しません。

var obj = {
  length: 0
};
obj[3] = 'd';
obj.length // 0

上のコードはオブジェクトobjにテンキーが追加されていますがlength属性は変更されていません。つまりobjは配列ではないということ

典型的な「配列に似たオブジェクト」は関数のargumentsオブジェクトであり、多くのDOM要素と文字列があります。

// argumentsオブジェクト 
function args() {return arguments} 
var arrayLike = args('a', 'b');

arrayLike[0] // 'a' 
arrayLike.length // 2 
arrayLike instanceof Array // false

DOM要素セット 
var . elts = document getelementsbytagname (' h3 ');
elts.length // 3 
elts instanceof Array // false 

//文字列 
'b' [1] // 'b' 
'abc'.length // 3 
'abc' instanceof Array // false 

上記のコードには3つの例がありますが、これらは全て配列ではありません(「instanceof」演算子は「false」を返します)。

配列の「slice」は、「配列に似たオブジェクト」を真の配列にすることができます。

var arr = Array.prototype.slice.call(arrayLike);

真の配列に変換することに加えて、「配列に類似したオブジェクト」が配列を使うもう一つの方法があります。それは、call()によって配列をオブジェクトの上に置くこと 。

function print(value, index) {
  console.log(index + ' : ' + value);
}

Array.prototype.forEach.call(arrayLike, print);

arrayLikeは配列のようなオブジェクトを表しており、本来は配列のforEach()メソッドを使うことはできませんが、call()によってforEach()arrayLikeに接ぎ木して呼び出すことができます。

以下の例は、この手法を使ってargumentsオブジェクトの上でforEachメソッドを呼び出すもの 。

// forEachメソッド 
// forEach 
function logArgs() {
  Array.prototype.forEach.call(arguments, function (elem, i) {
    console.log(i + '. ' + elem);
  });
}

// for 
function logArgs() {
  for (var i = 0; i < arguments.length; i++) {
    console.log(i + '. ' + arguments[i]);
  }
}

つづりも類似の配列の対象であり、だからも用 array . prototype . foreach .コール遍暦。

Array.prototype.forEach.call('abc', function (chr) {
  console.log(chr);
});
// a
// b
// c

この方法は、配列をそのまま使うネイティブな「forEach」よりも遅いので、「配列に似たオブジェクト」をまず真の配列に変換してから、配列を直接呼び出す「forEach」の方が良いでしょう。

var arr = Array.prototype.slice.call('abc');
arr.forEach(function (chr) {
  console.log(chr);
});
// a
// b
// c

リンク参照

−アクセルrauschmayer、[arrays in javascript] (http://www.2ality.com/2012/12/arrays.html)
−アクセルrauschmayer、[javascript: sparse arrays vs . dense arrays] (http://www.2ality.com/2012/06/dense-arrays.html)

0
0
1

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