匿名関数の名前空間での利用

概要

javascritpでのプログラミングには、常にグローバル変数や関数定義、ローカル変数などの定義場所、等で、変数名、関数名が他のライブラリやモジュールと重複するリスクがあります。

このようなリスクを回避する方法として、匿名関数を利用する方法があります。

具体的には次のように書きます。

(function() {
    //特定の処理(変数の定義と利用)
}()};

上記のコードにより、「特定の処理」の箇所に書いたコードは
他のライブラリやモジュール等に影響を受けずに処理を書くことができます。

関数プロパティ

概要

javascriptの関数の特徴として、関数プロパティと呼ばれるものがあります。

この関数プロパティについて、試してみます。

関数プロパティについて

関数プロパティは、関数に対してプロパティを持つことをいい、Functionオブジェクトに対してプロパティを付与することで実現します。

こうすることで関数の実行後にも値が保持できる静的変数を持つことができます。
また、この静的変数については関数を呼び出し時にも使うことができます。

具体的にコードを書いて試します

次のコードを書きます。


function test1()
{
	//関数プロパティの値に対して作用するコードを書く
	//(例:文字列の連結)
	return test1.teststring + "def";
}

//通常の関数呼び出し
console.log(test1());

//関数プロパティとして「teststring」を定義し、文字列を代入する
test1.teststring = "abc";

//関数プロパティに文字列を代入した後の、出力
console.log(test1());

少し荒い書き方ですが、関数test1に対して、通常の呼び出しと、関数プロパティの定義と、それに対しての処理(文字列連結)をした後の、関数呼び出しを書いてみました。

実行結果は次のようになります。

undefineddef
abcdef

最初の1行目の「undefineddef」は、通常の関数呼び出しを行うタイミングでは、関数プロパティの定義すらしていないので、ごく当たり前の出力結果といえます。

未定義のプロパティ「undefined」に対して、文字列「def」が連結されています。

次に出力された「abcdef」については、関数プロパティに定義された値が呼び出されているので、単純に文字列が連結された結果が出力されています。

グローバル変数との違い

この関数プロパティの動きを見てみると、グローバル変数ができることと同じ動きになります。
実際には、グローバル変数の使いどころを見極める必要があり、グローバル変数名の重複や、ロジックが複雑になる場合があるので、シンプルなコードを書く場面で関数プロパティを使うと良いと考えることができます。

関数を変数や、オブジェクトのプロパティに格納して利用する

概要

javascriptの言語の特徴の一つとして、関数を変数やオブジェクトのプロパティへ代入して値として使用する方法があります。

詳しい言語仕様については仕様書を確認するとして、ここでは実際にコードを書いて動作を見てみます。

まず、次のコードです。

//関数を定義
function test1(val1)
{
	return val1 * 2;
}

//通常の関数呼び出し
console.log(test1(10)); //20

出力結果

20

出力結果は「20」になりました。

これはオーソドックスな関数の定義と呼び出し方法です。

では次に、上記のtest1という関数を、オブジェクトのプロパティとして利用してみます。
具体的には次のようなコードを書きます。

//関数を定義
function test1(val1)
{
	return val1 * 2;
}

//関数を一度、変数に代入してみる
let tmp_func1 = test1;
console.log(tmp_func1(30)); //60
console.log(tmp_func1(40)); //80

出力結果

60
80

この場合は、関数を一度変数に代入し、その変数に対して引数を指定し、関数を実行しています。
見た感じでは、通常の関数を実行しているように見えます。

では、次に関数をオブジェクトのプロパティとして指定する方法を試してみます。

具体的には、次のようなコードを書きます。

//関数をオブジェクトのプロパティとして代入する
let test_obj = {
	test2: function(val2) { return val2 * 3; }
};
console.log(test_obj.test2(5)); //15

出力結果

15

出力結果は「15」になりました。

この場合は、test_obj内に定義されたメソッドtest2に対して、関数を割り当てています。

次に、変数の引数の値として、関数を指定する方法についてです。

//配列の要素に、関数を格納する
let test_arr = [function(val3) { return val3 * 8; }];

//40
console.log(test_arr[0](5));

//引数の2つ目は何もないので「TypeError: test_arr[1] is not a function」
console.log(test_arr[1](5));

出力結果

40
TypeError: test_arr[1] is not a function

二つ目の出力結果は、「TypeError: test_arr[1] is not a function」となります。
これは配列の二つ目の要素には何も指定していない為です。
関数を定義しているのは配列の1つ目であることに注意します。

javascriptの関数の引数の型の扱いについて

概要

javascriptの関数は、関数を呼び出す際の引数について、型を宣言することはできない。
また、関数宣言側の引数も型のチェックが行われず処理されます。

関数の呼び出し時、関数の受け取り時に引数の型は厳密ではないですが、
言語の特性として型は動的に変化します。

とはいえ、プログラミングを進めるうえで、引数の型についてまったく考慮しなくても良いということはありません。
できれば型を厳密にチェックして、関数内の動作を保障するようにしないと、プログラムの規模が大きくなるほど大変になってきます。

関数の引数の型をチェックする

では、具体的にどのように関数の型をチェックすればよいでしょうか。
試しに簡単なコードを書いて調べてみます。
次の例は引数の値が配列かどうかをチェックして、処理を分岐しています。

function test1(str)
{
	//ここで「str」の引数のチェックを行う
	//原則、文字列であればOKで、数値や配列等はNGとする

	//配列かどうかのチェック
	if (str instanceof Array) {
		//配列の場合はここで処理
	}
}

test1("abc");

上記の場合は、呼び出し元の関数の引数は文字列を渡しているので、呼び出し先の関数内では、配列かどうかの判定をしている為、分岐の中にははいりません。

配列のチェックは「isArray()」メソッドを使ってもOKですが、
空白の配列の判定が甘い為、上記のようにinstanceofを使っています。

また、上記は配列かどうかを判定していますが、その他、nullやNaNかどうかのチェック等のメソッドも使って、引数を判定することが有効です。

関数呼び出し時の引数にオブジェクトのプロパティを使う方法

概要

これまでの関数は、呼び出し元の引数と、呼び出される関数側の引数についてみてきました。

注意深くプログラミングすれば特に問題なく動作しますが、
引数の順序や引数の数には気を配る必要は常にあります。

そこで、少し工夫をして、関数側の引数としてオブジェクトのプロパティを使う方法があります。
(呼び出す側の引数は、必然的にオブジェクト定義された値になります)

オブジェクトのプロパティを渡すことで、引数は一つになり、
「プロパティ名.値」という形で呼び出して利用することが可能です。

具体的に次のようなコードを書いて、試してみます。

function test1(args) {

	//引数のオブジェクト内の1つ目の配列を呼び出して出力
	console.log(args.testobj1[0]);
	console.log(args.testobj1[1]);
	console.log(args.testobj1[2]);

	//引数のオブジェクト内の2つ目の配列を呼び出して出力
	console.log(args.testobj2[0]);
	console.log(args.testobj2[1]);
	console.log(args.testobj2[2]);
	console.log(args.testobj2[3]);
	console.log(args.testobj2[4]);
}

//テスト用の配列の定義
let args1 = [10, 20, 30];
let args2 = [60, 70, 80, 90];

//オブジェクトの定義
let obj_args = {
	testobj1: args1,
	testobj2: args2
};

//ここで関数を呼び出す
//引数はオブジェクトをそのまま渡す
test1(obj_args);

出力結果

10
20
30
60
70
80
90
undefined

出力結果の一番最後は、わざと2つ目のテスト用配列の存在しない要素にアクセスしています。

この場合はエラーにはならずに、引数に渡したオブジェクトに格納されている2つめの配列の要素にアクセスしようとしますが、undefinedとして出力されます。

可変長の引数リスト

概要

前回のブログ記事は関数の引数を省略した場合の挙動を試しましたが、
今回の記事は、関数の呼び出し元が引数の数を多く書いた場合の挙動を試してみます。

まずは実際のコードを書いて動きを試してみます。

function test3(a, b) {

	console.log(a);
	console.log(b);

	if (a == undefined) {
		console.log("a1");
	} else {
		console.log("a2");
	}
	
	if (b == undefined) {
		console.log("b1");
	} else {
		console.log("b2");
	}
	
}

test3(10, 20, 30);

出力結果

10
20
a2
b2

上記のような出力結果になりました。

関数test3の第三引数の「30」は引数として解釈不可能なので、
エラーにもならず、関数内では何もなかったかのような挙動になります。

関数の挙動について

それでは、上記のような関数内で、第三引数が存在する呼び出し方をした場合に処理するにはどうすればよいかというと、
javascriptのArgumentsオブジェクトを利用する方法があります。

Argmentsオブジェクトは配列のように振る舞い、引数の値はarguments[]配列に格納され、それを関数内で使うことができます。

具体的には次のようなコードを書いて試してみます。

function test4(a, b) {

	console.log(a);
	console.log(b);

	console.log(arguments[0]);
	console.log(arguments[1]);
	console.log(arguments[2]);
}

test4(10, 20, 30);

出力結果

10
20
10
20
30

出力結果を見ればわかりますが、test4の関数の引数は2つしか定義していませんが、arguments[2]という書き方をすることにより、Argmentsオブジェクトを使い、引数の3つめの値が取得できています。

現場のプログラミングでは、関数の引数の数は、設計や仕様に基づいて正確に書くことが大半ですが、
引数の数を限定しない仕様で関数を定義すると便利な場合があります。

例えばmax関数で引数があればあっただけ、その中から最大値を求める場合の関数はその例です。

また、任意の引数を受け取ることができる関数を、可変長引数関数と呼びます。
別名としてvarargs関数と呼んだりもします。

calleeプロパティ、callerプロパティ

Argmentsオブジェクトについては、配列のように使う方法に限らず、その他の動きをするプロパティもあります。

use strictの状態ではエラーになりますが、非strictモードの場合はcalleeプロパティ、callerプロパティというプロパティが使用できます。

calleeプロパティは関数を呼び出した関数を参照できます。

callerプロパティは匿名関数にて、自分自身を再帰的に呼び出せます。

引数と仮引数

概要

これまでの関数のサンプルコードでは、関数定義の際に、関数の引数の型は指定していませんでした。
また、関数の呼び出しの再にも型については、厳密にチェックをして呼び出していません。

javascriptは型の扱いが動的で、他の言語に比べると型の扱いに注意してプログラムする必要があります。

また、引数の型に対しての注意と、引数の数についても、javascriptは厳密にチェックしていません。

これから実際のコードを書いて、引数の細かい使い方についてまとめていこうと思います。

省略可能な引数

javascriptの関数の引数を省略した場合の挙動をみてみます。

下記のようなコードを書いて実行してみます。

function test2(a, b) {
	console.log(a);
	
	if (a == undefined) {
		console.log("a1");
	} else {
		console.log("a2");
	}
	
	if (b == undefined) {
		console.log("b1");
	} else {
		console.log("b2");
	}
	
	return true;
}

test2();
test2(10);
test2(10, 20);

出力結果

関数test2を引数の省略、明示した場合に分けて、3回呼んでいます。
出力結果はわかりやすいように、関数ごとに空行を入れています。

undefined
a1
b1

10
a2
b1

10
a2
b2

出力結果は上記のようになります。

最初のtest2()の関数では、引数のaもbもundefinedとして解釈されます。
javascriptの関数は、引数を省略してもエラーにはならずに実行されます。

2つめと3つめの関数はそれぞれ、引数を記述していますが、第一引数や第二引数をそれぞれ省略しても、省略した引数はundefinedとして解釈されます。

関数の呼び出し方法について

概要

javascriptの関数の呼び出し方法についてまとめます。

関数は定義しただけでは動作しません。
関数の呼び出し方法は、次のように呼び出されます。

・1.関数定義を行い、関数を呼び出す
・2.メソッドとして呼び出す
・3.コンストラクタとして呼び出す
・4.call()、apply()メソッドを使って間接的に呼び出す

以下、関数の呼び出し方法を一つ一つみていきます。

1.関数定義を行い、関数を呼び出す

最も一般的というか、プログラムを実装していて多く見かける書き方です。

前回の投稿でも書いたスタンダードな関数を定義し、それを呼び出す方法です。
具体的には下記のような形になります。

//関数の定義(戻り値はなし)
function test1 () {
    //処理
}

//呼び出し
test1();

上記の場合、関数test1には引数がないので、呼び出す時にも引数の記述はありません。
引数は状況に応じて書きます。

上記の書き方とは別に、「関数式」で関数を定義し、呼び出す方法は次のようになります。

//関数の定義(戻り値はなし)
let test1f = function () {
    //処理
};

//呼び出し
test1f();

上記の関数の書き方は関数名がない無名関数になります。

2.メソッドとして呼び出す

上記の方法とは別に2つめの呼び出し方について調べてみます。

メソッドはオブジェクトのプロパティに保存されている関数を指します。

例えば、関数test2をオブジェクトobj1のメソッドtestmとして定義する場合、次のように書きます。

obj1.testm = test2;

呼び出す時はobj1.testm();で呼び出せます。

また、呼び出し方法は上記の他、

obj1["testm"]();

という書き方をしても同様の意味になります。

3.コンストラクタとして呼び出す

関数呼び出しの前にnewキーワードをつけると、コンストラクタが呼び出されます。

コンストラクタとして呼び出す場合、具体的には次のように書きます。

let obj1 = new Object();

newキーワードを使って、オブジェクトを生成するタイミングで、コンストラクタとして関数が呼び出されます。

コンストラクタは他の言語では、オブジェクトがnewされるタイミングで必ず実行されるメソッド。が存在し、newと同時にそのメソッドが実行される特徴があります。

コンストラクタは他の関数呼び出しやメソッド呼び出しとは違う挙動になります。

コンストラクタ呼び出しの際の、括弧を書いても省略しても、動作には問題はなく、引数を省略すると、引数式が評価されずに関数に渡らない挙動になります。

4.call()、apply()メソッドを使って間接的に呼び出す

関数呼び出しの方法の中で、あまり見かけない(と思う)呼び出し方が「callによる呼び出し」「applyによる呼び出し」になります。

簡単な例をあげると、上記で書いたサンプルのコードがあるとして

//関数の定義(戻り値はなし)
let test1f = function () {
    //処理
};

//呼び出し
test1f();

この関数test1f (無名関数)をcallメソッドを使って呼び出してみます。

//関数の定義(戻り値はなし)
let test1f = function () {
    //処理
};

//呼び出し
test1f.call();

書き方はほとんど変わらないのですが、呼び出す箇所が「.call()」になっている点が異なります。

また、「applyメソッドによる呼び出し」は次のように書きます。

//関数の定義(戻り値はなし)
let test1f = function () {
    //処理
};

//呼び出し
test1f.apply();

見てわかるように、callメソッドの呼び出しと流れは変わりません。

では、この2つの呼び出し方は何が違うのでしょうか。

簡単に言い切ってしまうと、このcallとapplyの両者の違いは、引数にあります。

applyメソッドの引数は、配列で渡す必要があります。
この挙動については、言語仕様に深く関わる点なので、別途取り上げて掘り下げようと思います。

関数の入れ子について

概要

関数の入れ子について、まとめてみます。
javascriptで関数の中にさらに関数を入れ子で定義する場合についてです。

この場合、変数へのアクセスの制約に注意する必要があります。

関数内で入れ子にした関数から、大元の関数の仮引数や変数にアクセスが可能です。

簡単な例を下記に書きます。

function test1(a) {
	//処理
	console.log(a);

	//入れ子の関数を定義
	function test2() {
		//処理
		a++;
		//ここで引数aに対してアクセスができる
		console.log(a);
	}

	test2();

	console.log(a);
}

test1("10");

出力結果

10
11
11

わかりやすいように処理を一切書いていませんが、上記のように大元の関数の引数へアクセスすることができます。

出力結果は、10、11、11となり、まず最初の関数test1で呼び出された直後の引数の値が「10」として出力され、
次に関数内で定義されたtest2が呼び出された際の「11」が出力されます。
最後に、test1の関数の終わりに出力した変数aの値「11」が出力されます。

3回目の出力で値が「11」になるのは、関数test1の引数で定義されているaが、関数test2の中からアクセスされてインクリメントされている為です。

実装の方法はさらに複雑になる場合が多く、変数の有効範囲について注意して実装していきます。

関数の定義について

関数の定義

javascriptの関数の定義についてまとめます。

関数の定義については、関数定義式、関数宣言文として、「function」キーワードを使って定義します。

関数の定義の特徴は次のようになります。

・関数宣言文では、関数の名前を指定します。この名前は関数名として利用します。

・関数名は変数名として利用することもできます。

・関数の定義する際に、関数名を省略することが可能です。

・関数名を省略した関数は無名関数や、匿名関数とも呼ばれます。

・関数の引数は()の内部に記述します。

・引数は、カンマ区切りで0個以上の識別子を記述します。

・識別子は引数名として利用可能で、関数本体内でローカル変数のように利用できます。

・{}ブロックでかこまれた文は、関数の本体部分になります。

関数の定義の方法はjavascript独特の定義があるので、実際にコードを書いて試していきます。

代表的な関数の定義の仕方を下記のコードにまとめます。

//関数の定義(戻り値はなし)
function test1 () {
	//処理
}

//関数の定義、引数「value_y」を持つ(戻り値はなし)
function test2 (value_y) {
	//処理
}

//関数の定義(戻り値は有り)
function test3 () {
	//処理
	
	//決まった文字列を返す
	return "test3";
}

//関数の定義、再帰関数(関数内の条件判定が真の場合、戻り値が返される)
function test4 (value_x) {
	//処理
	if (value_x < 100) {
		return true;
	}
	
	//決まった文字列を返す
	return test4(value_x + 10);
}

//関数式を変数に代入する場合(無名関数)
let test5 = function(x) { return x * x; }

//関数式に名前をつけて、定義する
let test6 = function test7(x) { return x * x; }

//関数式を他の関数の引数として使用する
//(この場合、test8オブジェクトのtest9メソッドの引数として、無名関数を割り当てています)
test8.test9(function(a, b) { return a - b; } ));

//関数定義と同時に処理を呼び出す場合
let test10 = (function(x) { return x + x; }(100));

上記の例にあるように、式として関数を定義することができます。
その場合、関数名を省略して無名関数という呼び方をします。

また、変数に対して関数を代入する場合は、変数に関数オブジェクトを代入するという考え方ができます。

無名関数はプログラム中で1回だけ実行される関数を定義する場合に有効で、実装方法に応じて通常の関数定義と使い分けます。

関数の名前について

関数名は識別子として有効な名前であれば、文字列に制約はありません。
命名規則はプロジェクトの規約やルールにより変動するものですが、
一般的な命名ルールは「リーダブルコード」等の書籍等を参照にすると良いと思います。

関数の呼び出しについて

関数の呼び出し方法を理解するには、javascript特有の動作があることを把握する必要があります。

関数宣言文はスクリプトや関数の先頭に巻き上げられて処理されます。
関数宣言文(function)で宣言された関数は、関数定義した箇所よりも前に定義することができます。
(他のプログラム言語では、関数の宣言位置によってエラーが起こります)

また、式に代入された関数は、変数に代入された後から利用可能になります。

関数からの返し方について

関数内で処理を行い、その処理結果を関数の呼び出し元に返すには、return文を使います。

return文が定義された行から関数が返されます。

また、return文を省略した場合の関数は、呼び出し元にundefinedが返されます。

関数

概要

ここからはjavascriptの関数について、一つ一つ丁寧に掘り下げていこうと思います。

他のプログラム言語でも同様に、javascriptのプログラムにおいて同様の処理がまとまって連続する場合、それをまとめて関数にして利用します。

javascriptの関数を定義するときは、仮引数と呼ばれる識別子のリストを記することが可能です。
仮引数はローカル変数として解釈され、関数を呼び出す時に関数の引数として値を渡します。

また、関数には戻り値があります。
戻り値は関数の呼び出し式の値になります。

関数の呼び出しには、仮引数の他に、呼び出しコンテキストと呼ばれる「this」で書くものがあります。

関数はオブジェクトのプロパティとして代入可能で、代入した関数はそのオブジェクトのメソッドになり、呼び出すことが可能です。
また、そのメソッドを呼び出す場合、オブジェクトがコンテキストになりあす。
新しく生成されるオブジェクトはコンストラクタによって初期化されます。

javascriptでは、関数を変数に代入することができます。
また、関数を別の関数に渡すこともできます。
さらに関数の中で、別な関数を定義することも可能です。
関数の中で別な関数を定義した場合、定義した関数からスコープ中の変数にアクセスが可能になります。(この考えは後ほどとりあげるクロージャの考え方につながるので、別途、深く掘り下げようと思います)

上記の特長の諸々は、別途コードを書いて動作を確かめてノウハウを蓄積していこうと思います。

文字列に対して配列メソッドを使う場合

概要

javascriptの文字列に配列のメソッドを使った場合について、調べてみます。

文字列を定義し、その文字列に対して配列のメソッドを使った時、どのような動きになるでしょうか。
次のコードを書いて動かしてみます。

let test1 = abcdef;
console.log(test1.charAt(0));
console.log(test1.charAt(1));
console.log(test1.charAt(2));

出力結果は「a b c」となり、変数test1の中の値を一つづつ取得しています。

charAtの詳しい説明はこちら(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/charAt)を参照すると良いと思います。

また、上記のコードを次のように書いてみます。

let test1 = abcdef;
console.log(test1[0]);
console.log(test1[1]);
console.log(test1[2]);

これも同様に「a b c」と出力されます。

文字列を配列として扱うことも可能で、配列用のメソッドを使うことも可能です。
その際は読み出し専用の配列として処理されます。

配列ではないオブジェクトに対しての配列メソッドの扱い

概要

javascriptの配列は、以下の特徴があります。

・配列に要素が追加されると、lengthプロパティが自動的に更新される
・lengthプロパティに現在の長さより小さな値を設定することで、配列を縮められる
・Array.prototypeからメソッドを継承している
・Arrayというクラス属性を持っている

上記は配列の特徴ですが、必ずこの特徴を持たなければならないことはないようです。(まだ推測の段階なので、検証は必要)
javascriptの配列ではないオブジェクトに対し、配列として扱っても、配列同様に振舞うことができます。

また、文字列を配列にように扱うことも可能です。

文字列に対し、インデックス指定でアクセスし、内容を取得したりすることが可能です。

ES6の配列メソッド

概要

ES6の配列に使えるメソッドを簡単に試してみます。

forEach

forEachは前のブログ投稿にも出てきました。
次のようなコードを書きます。

let test1 = [1, 10, 20, 30, 60];
let sum = 0;
test1.forEach(function(value) { sum += value; });
console.log(sum);

配列test1に対して、全ての要素を加算し、最終的な合計値を出力しています。

出力結果

Array(5) [ 2, 11, 21, 31, 61 ]

map

このメソッドは配列の要素を1つづつ、関数に引数として渡します。
関数から返された値は最終的に配列として返されます。
次のようなコードを書いて試してみます。

let test2 = [1, 10, 20, 30, 60];
test3 = test2.map(function(x) { return x * x; });
console.log(test2);
console.log(test3);

出力結果

Array(5) [ 1, 10, 20, 30, 60 ]
Array(5) [ 1, 100, 400, 900, 3600 ]

filter

配列要素の部分集合となる配列を返します。
次のようなコードを書いて試します。

let test4 = [1, 10, 20, 30, 60];
test5 = test4.filter(function(x) { return x > 15; });
console.log(test4);
console.log(test5);

出力結果

Array(5) [ 1, 10, 20, 30, 60 ]
Array(3) [ 20, 30, 60 ]

every

配列の各要素に対して、指定した関数内の演算結果をみます。演算結果が配列の全ての要素が真だった場合にはtrueを返します。(演算結果が偽の場合はfalseを返します)
次のコードを書いて試してみます。

//every
let test6 = [1, 10, 20, 30, 60];
test7 = test6.every(function(x) { return x < 20; });
console.log(test6);
console.log(test7);

//some
let test8 = [1, 10, 20, 30, 60];
test9 = test8.some(function(x) { return x < 20; });
console.log(test8);
console.log(test9);

出力結果

Array(5) [ 1, 10, 20, 30, 60 ]
false
Array(5) [ 1, 10, 20, 30, 60 ]
true

indexOf

要素の中から指定した値を持つ要素を探して、見つかった場合は最初のインデックスを返す。

//indexOf
let test12 = [1, 10, 20, 30, 60];
test13 = test12.indexOf(20);
console.log(test12);
console.log(test13);

出力結果

2

javascriptの配列に対してのメソッド

概要

配列のメソッドについて試してみます。

メソッドはprototypeオブジェクトのメソッドとしてjavascriptの標準機能として実装されています。

近年ではECMAScriptのバージョンも上がってきており、最新の言語仕様に対応する必要性があります。

現在普及している言語標準化規格は「ECMAScript2015」(ES6)と「ECMAScript2016」(ES7)が大半になりますが、言語仕様が新しくなる前から実装されている基本的なメソッドについても試してみます。

join

配列の全ての要素を文字列に変換して連結するメソッドです。
実際に次のようコードを書いて動かしてみます。

let test1 = [1, 10, 20, 30, 60];
console.log(test1.join());

出力結果は次のようになります。

1,10,20,30,60

少しわかりづらいですが、出力結果はひとつづきの文字列になっています(カンマも含む)

sort

配列の全ての要素をソートするメソッドです。
実際に次のようコードを書いて動かしてみます。

let test2 = [10, 30, 1, 20, 60];
console.log(test2.sort());

出力結果は次のようになります。

Array(5) [ 1, 10, 20, 30, 60 ]

配列内の各要素を数値として比較し、小さい→大きい順にソートされています。

では、配列内の要素が数値と文字列があった場合に、どのような挙動になるか試してみます。
具体的に次のコードを書いて動作させてみます。

let test3 = ["xyz", 30, "abc", "5", 60];
console.log(test3.sort());

出力結果は次のようになります。

Array(5) [ 30, "5", 60, "abc", "xyz" ]

上記の出力結果はとても「ソートした結果」とは言えず、不完全なものです。
言語仕様にもよりますが、文字列と数値が混在した配列は、sort関数ではソート不能という結果になりました。

では、配列内の要素が全て数値である(負の数、0、正の数)場合のソート結果はどうなるでしょうか。
次のようなコードを書いて実行してみます。

let test4 = [-50, 30, -100, 0, 5, 60];
console.log(test4.sort());

出力結果は次のようになります。

Array(6) [ -100, -50, 0, 30, 5, 60 ]

これも、よく見るとソートが不完全です。
結果の後半で「30, 5, 60」のように、桁が考慮されておらず、単純に文字列の出現順(アルファベット順)として出力されています。

上記の配列test4を厳密に数値してソートするには、次のように書きます。

let test4 = [-50, 30, -100, 0, 5, 60];
//数値順としてソートする場合
console.log(test4.sort(function(a, b) { let ans = b - a; if(isNaN(ans)) { return -1; } else { return a - b; }}));

このように書くと、次のような出力結果になります。

Array(6) [ -100, -50, 0, 5, 30, 60 ]

これで正しくソートされています。
sort関数内で、無名関数を定義し、その無名関数の中で引数同士を比較して、比較結果により、引数をソートする判定を行っています。

より詳しく書くと、2つの引数「a」と「b」を比較し、

「b – a」の演算結果が
「正」になる場合は、aをbの後ろへ
「負」になる場合は、aをbの前へ

という無名関数を作り、その演算を繰り返すことで、正しいソート結果を得ています。

concat

concatメソッドは、配列に対して、要素を追加して新しい配列要素を返します。
具体的には次のように書きます。

let test5 = ["xyz", 30, "abc", "5", 60];
console.log(test5.concat(10, 20));

出力結果は

Array(7) [ "xyz", 30, "abc", "5", 60, 10, 20 ]

のようになります。
contactの引数「10, 20」の要素を、元の配列「test5」の最後に付け加えています。

slice

sliceメソッドは配列に対して、指定番目の要素をスライスして別な配列を返します。
引数が2つあり、1つ目の引数で、スライス開始の要素(○番目)を指定し、2つ目の配列で配列の末尾を指定します。
引数が1つのみの場合は、その引数で指定した要素から配列の最後までを取得します。
引数が負の数の場合は、配列の最後から数えた要素数からスライスされます。

次のようなコードを書いて試してみます。

let test6 = ["xyz", 30, "abc", "5", 60];
console.log(test6.slice(0, 2));
console.log(test6.slice(4));
console.log(test6.slice(-2));

出力結果は次のようになります。

Array [ "xyz", 30 ]
Array [ 60 ]
Array [ "5", 60 ]

splice

配列に要素を挿入したり、削除する場合に使います。
このメソッドの特徴として、メソッドを実行する配列に対し、直接要素を操作する点があります。

例えば、次のようなコードを書いて実行してみます。

let test7 = ["xyz", 30, "abc", "5", 60, 70, "80", 90];

console.log(test7.splice(3)); //出力結果 ["5", 60, 70, "80", 90]

//一旦、元の配列の要素を出力して確かめる
console.log(test7);  //出力結果 ["xyz", 30, "abc"]

//さらにspliceメソッドを使う
console.log(test7.splice(1, 2));  //出力結果 [30, "abc"]

//元の配列の要素を出力して確かめる
console.log(test7);  //出力結果 ["xyz"]

実行した際の2つめの出力を確認すると、spliceメソッドを使った後の配列は、要素が先頭の3つのみになっています。
つまり元の配列の要素数が変更され、要素が減っている状態と言えます。

さらに2回目のspliceの実行で、要素は元の配列の1番目から2つ削除されます。

最終的には元の配列の要素は1つだけになり、「”xyz”」の文字列が出力されました。

push pop

pushメソッドは配列に対して、最後に1つの要素を追加するメソッドです。
その反対にpopメソッドは、配列の最後の要素を削除します。

配列の長さは動的に変化し、pushした場合は1つ増加し、popした場合は1つ減少します。
この2つのメソッドを使うと、配列に対してのスタック操作が可能になります。

上記を踏まえて次のようなコードを試してみます。

let test8 = [];

test8.push(3)
console.log(test8);

test8.pop();
console.log(test8);

test8.push(5, 2);
console.log(test8);

test8.pop();
console.log(test8);

test8.push(4);
console.log(test8);

test8.push(6);
console.log(test8);

出力結果は次のようになります。

Array [ 3 ]
Array []
Array [ 5, 2 ]
Array [ 5 ]
Array [ 5, 4 ]
Array(3) [ 5, 4, 6 ]

pushとpopのメソッドを実行した後、それぞれ配列の最後の値が増加、減少していることがわかります。

unshift shift

unshiftメソッドとshiftメソッドは、配列の先頭の要素を操作します。
unshiftは配列の先頭に要素を追加します。
shiftは配列の先頭の要素を削除します。

先ほどpushで書いたコードに似た次のようなコードを書いて試してみます。

let test9 = [];
test9.unshift(3)
console.log(test9);
test9.shift();
console.log(test9);
test9.unshift(5, 2);
console.log(test9);
test9.shift();
console.log(test9);
test9.unshift(4);
console.log(test9);
test9.unshift(6);
console.log(test9);

出力結果は次のようになります。

Array [ 3 ]
Array []
Array [ 5, 2 ]
Array [ 2 ]
Array [ 4, 2 ]
Array(3) [ 6, 4, 2 ]

pushやpopと違い、配列の先頭に要素を追加していることがわかります。

toString

配列に対してのtoStringメソッドを試してみます。
このメソッドは配列要素の一つ一つを文字列に変換してカンマ区切りのリストを出力します。

実際に次のようなコードを書いて試してみます。

let test10 = ["xyz", 30, "abc", "5", 60, 70, "80", 90];
console.log(test10.toString());

出力結果は次のようになります。

xyz,30,abc,5,60,70,80,90

配列要素が全てカンマ区切りで連結され、全体でひとつの文字列として出力されています。

また、類似するメソッドとして、toLocaleStringメソッドがあります。
メソッドの動きはtoStringと同様の動きになりますが、区切り文字がロケール固有区切り文字で区切られます。
区切り文字はプログラム実行時の、ブラウザ環境に依存します。

javascriptの多次元配列

概要

javascriptで多次元配列を扱う場合を試してみます。

厳密に言うと、javascriptは言語仕様としての多次元配列はありません。
言語仕様としては存在しませんが、擬似的に配列と配列を組み合わせて多次元配列となるような配列を生成できます。

実際にコードを書いて多次元配列を試してみる

javascriptでコードを書いて、多次元配列のような配列を試してみます。

次のようなコードを書いて、実行してみます。

//通常の配列を定義する
let test1 = [1, 10, 20, 30, 60];

//要素の全てを回して処理
for (var i = 0; i < test1.length; i++) {
	//ループ内で、要素ごとに、さらに新規配列を定義して代入する
	test1[i] = [2, 20, 40, 60, 80];
}

//ループ後の結果を出力1(わかりやすいように、1次元目の要素を一つづつ出力する)
console.log(test1[0]);
console.log(test1[1]);
console.log(test1[2]);
console.log(test1[3]);
console.log(test1[4]);

//ループ後の結果を出力2(わかりやすいように、1次元目の要素内の2次元目の要素を出力する)
console.log(test1[0][0]);
console.log(test1[0][1]);
console.log(test1[0][2]);
console.log(test1[0][3]);
console.log(test1[0][4]);

実際に動作させた結果は、次のようになります。

Array(5) [ 2, 20, 40, 60, 80 ]
Array(5) [ 2, 20, 40, 60, 80 ]
Array(5) [ 2, 20, 40, 60, 80 ]
Array(5) [ 2, 20, 40, 60, 80 ]
Array(5) [ 2, 20, 40, 60, 80 ]
2
20
40
60
80

出力結果を確認すると、1次元目の要素には「Array(5) [ 2, 20, 40, 60, 80 ]」というように、1つの要素内にさらに配列が格納されていることがわかります。

次に、2次元目の要素を一つづつ出力しているのですが、順に、2, 20, 40, 60, 80 という形で2次元目の要素を一つづつ出力することができています。

では次に、上記のコードを少し改造して、次のようなコードを書いて実行してみます。

//通常の配列を定義する
let test2 = [1, 10, 20, 30, 60];

//要素の全てを回して処理
for (var i = 0; i < test2.length; i++) {
	//ループ内で、要素ごとに、さらに新規配列を定義して代入する
	//要素内の値に対して i を加算
	test2[i] = [2 + i, 20 + i, 40 + i, 60 + i, 80 + i];
}

//ループ後の結果を出力1(わかりやすいように、1次元目の要素の一つづつ出力する)
console.log(test2[0]);
console.log(test2[1]);
console.log(test2[2]);
console.log(test2[3]);
console.log(test2[4]);

//ループ後の結果を出力2(わかりやすいように、1次元目の要素内の2次元目の要素を出力する)
//2次元目の1つ目の要素を出力してみる
console.log(test2[0][0]);
console.log(test2[0][1]);
console.log(test2[0][2]);
console.log(test2[0][3]);
console.log(test2[0][4]);

//2次元目の2つ目の要素を出力してみる
console.log(test2[1][0]);
console.log(test2[1][1]);
console.log(test2[1][2]);
console.log(test2[1][3]);
console.log(test2[1][4]);

この場合の出力結果は次のようになります。

Array(5) [ 2, 20, 40, 60, 80 ]
Array(5) [ 3, 21, 41, 61, 81 ]
Array(5) [ 4, 22, 42, 62, 82 ]
Array(5) [ 5, 23, 43, 63, 83 ]
Array(5) [ 6, 24, 44, 64, 84 ]
2
20
40
60
80
3
21
41
61
81

最初の1次元目の出力結果を見てみると、forでループする際のカウンタ「i」の変数が、2次元目の各要素に対して加算されていることがわかります。

2次元目の2つ目の要素を出力してみると、加算された結果として「3, 21, 41, 61, 81」が出力されていることがわかります。

このようにしてjavascriptでも、多次元配列として配列を使うことができ、行列の計算や、座標の計算等に応用すること可能になると言えます。

2次元以上の配列も応用次第で活用することができます。

配列の全ての要素にアクセスする方法

概要

配列に対して、全ての要素に順にアクセスする方法を試してみます。
別な言い方をすればイテレータ(反復子とも言います)についての挙動を実際に試します。

forループによるアクセス

実際に次のようなコードを書いて、配列の全ての要素に対して出力を行ってみます。

let test1 = [1, 10, 20, 30, 60, 90];
for (var i = 0; i < test1.length; i++) {
	console.log(test1[i]);
}

上記のコードは単純に配列の全ての要素にアクセスしています。
出力結果は次のようになります。

1
10
20
30
60
90

では、この配列が、途中で値が存在しない場合の配列の場合を試してみます。
配列の3つめ5つめをnullにしてあります。

let test2 = [1, 10, , 30, , 90];
for (var i = 0; i < test2.length; i++) {
	console.log(test2[i]);
}

上記の場合の出力結果は次のようになります。

1
10
undefined
30
undefined
90

ループ処理の3番目と5番目の要素にアクセスする際、配列の要素がないので「undefined」として結果が出力されます。

上記のことから、ループ処理でイテレータの実行する時は、配列の要素が全て存在する場合か、または存在することをチェックするプログラムを書く必要があります。

例えば次のように書くことで配列の要素が存在しない場合はループをスキップすることが可能です。

let test3 = [1, 10, , 30, , 90];
for (var i = 0; i < test3.length; i++) {
	if (test3[i]) {
		console.log(test3[i]);
	}
}

上記のコードの場合は、配列の要素が存在する場合のみtrueと判定されるので、次のような出力結果になります。

1
10
30
90

if文での判定を行う際に、要件仕様によってcontinue等を使って処理をスキップする場合も有用です。

for in によるアクセス

上記のような配列に対して、for文と同じようにアクセスする構文として「for in」という構文があります。
これも同様に動きを試してみます。

次のようなコードを書きます。

let test4 = [1, 10, , 30, , 90];
for (var index in test4) {
	console.log(test4[index]);
}

出力結果は次のようになります。

1
10
30
90

for in構文の場合、値が空白やnull、undefinedの要素にはアクセスせず、次の要素にアクセスします。

for in構文で注意が必要なのは、動作するブラウザ環境により、処理される順番が担保されないという点です。(降順/昇順などがバラバラになる)

順番を重視する要件の場合にはfor 文を使ったほうが無難と言えます。

forEach()メソッド によるアクセス

では次にforEach()メソッドを使った場合について動作を確認してみます。

次のようなコードを書いて実行してみます。

let test5 = [1, 10, , 30, , 90];
test5.forEach(function(x) {
	console.log(x);
});

上記の出力結果は次のようになります。

1
10
30
90

このメソッドを使った場合も、存在しない要素については処理されず、値が存在する要素を処理しています。

また、for inメソッドとの大きな違いは、処理される順番がインデックス順になる。という点です。

上記のような書き方はjavascriptの関数型プログラムに発展することができ、それはまた別な投稿で取り上げたいと思います。

要素の追加と削除

概要

配列の要素を追加する場合と、削除する場合について、調べてみます。

要素の追加

配列の要素を追加する動きを確かめるため、次のようなサンプルコードを実行してみます。

let test1 = [];

test1[0] = "abc";
test1[1] = "def";

console.log(test1[0]);
console.log(test1[1]);

配列test1を生成し、要素の1番目と2番目に対して値を入れています。
出力結果は「abc」「def」になります。

さらに次のようなコードを実行してみます。

let test2 = [];

test2.push("abc");
test2.push("def", "ghi");

console.log(test2[0]);
console.log(test2[1]);
console.log(test2[2]);
console.log(test2.length);

test2[test2.length] = "jkl";

console.log(test2[3]);

出力結果は次のようになります。

abc
def
ghi
3
jkl

配列に対してのpushは要素を一つ追加して、値を代入する動きをします。
pushは引数が複数ある場合は、配列の一つづつの要素として代入する動きをします。

途中で配列のインデックス「test2.length」に対して値を代入していますが、
「test2.length」とすることで配列の最後の要素を示すことができます。
上記の場合は3つ目の要素に対して値を代入しています。

要素の削除

配列の要素を削除するには、delete演算子を使います。(その他の方法もあります)
次のようなコードを書いて実行してみます。

//要素の削除

let test3 = [];

test3[0] = "z";
test3[1] = "y";
test3[2] = "x";
test3[3] = "w";

console.log(test3[0]);
console.log(test3[1]);
console.log(test3[2]);
console.log(test3[3]);

console.log(test3.length); //出力結果は4

//要素のインデックス1を削除
delete test3[1];

console.log(test3[0]);
console.log(test3[1]); //出力結果はundefined
console.log(test3[2]);
console.log(test3[3]);

console.log(test3.length); //出力結果は4

上記のコードを実行すると、配列test3の1つめの要素をdelete演算子を使って削除しています。
この時、lengthの結果をみても削除前と削除後は結果は変わらないという特徴があります。

その他の配列要素の削除について

上記の例の他、要素を削除するには、popメソッドやshiftメソッドがあります。
delete演算子とは異なり、popやshiftの場合は要素の数が変動します。
この削除のされ方の違いは、プログラムを実装する際に非常に重要になるので、注意して実装します。

pop、shiftについては、また別途、ブログ記事でとりあげる予定です。

配列に対してのlengthプロパティについて

概要

配列の長さについて試してみます。
配列の長さはlengthプロパティで調べます。
ここでいう長さとは、要素数と同じことを意味します。

前回のブログ投稿で、疎な配列について書きましたが、その場合は要素数とlengthの長さの関係は、必ずしも一致はしません。

簡単なサンプル

次のような簡単なサンプルコードを書いて動かしてみます。

let test1 = new Array(10);

let test2 = [];

let test3 = [1, 10, 20, 30, 60, 90];

console.log(test1.length);
console.log(test2.length);
console.log(test3.length);

上記の出力結果は「10」「0」「6」になります。

lengthプロパティは通常、配列の要素数を出力する場面で使うことが多いですが、
反対に配列の要素に対してlengthプロパティを使って設定することもできます。

次のコードを書いて実行してみます。

let test4 = [1, 10, 20, 30, 60, 90, 100];

console.log(test4.length);

test4.length = 4;

console.log(test4.length);

最初の出力では「7」と出力されますが、次の出力では「4」と出力されます。
配列test4に対して、lengthプロパティに対して「4」を設定したので、4つ目以降の要素が削除される動きになります。

配列の要素が0から埋まっていない配列

概要

これまでのサンプルコードの中でも出てきましたが、
配列の要素が0から埋まっていない配列についてまとめます。
上記のような配列を疎な配列と呼びます。

特徴

この疎な配列の特徴は次のようになります。
この場合のlengthで取得した要素数は、実際の要素数よりは大きくなります。
疎な配列は実行速度は遅くなる代わりに、メモリ効率は上がります。

配列リテラルで値を省略した場合の挙動

配列を生成する再に、配列リテラルで生成し、その値が無い場合(省略した場合)は、疎な配列にはならないです。
この場合は、undefinedの値として配列が生成されます。

配列の要素へのアクセス方法について

概要

javascriptの配列について、要素へのアクセス方法について試してみます。

配列への代入

配列の要素にアクセスする方法は[]演算子を使います。
具体的には次のようなコードになります。

//要素が空の配列を作成
let test1 = [];

//要素の1に値を代入
//(この時、配列の0番目はemptyで空なので全体の要素数は1つ)
test1[1] = 10;

//要素の0に文字列「abc」を代入
test1[0] = "abc";

//要素へのアクセスに変数を使う(その1)
let arr_str = 4
test1[arr_str] = "efg";

//要素へのアクセスに式を使う(その2)
let arr_cal = 5;
test1[arr_cal + 10] = "hij";

便宜上、開発ツールのログ出力命令は省略していますが、出力結果は次のようになります。

Array [ <1 empty slot>, 10 ]

Array [ "abc", 10 ]

Array(5) [ "abc", 10, <2 empty slots>, "efg" ]

(16) […]
0: "abc"
1: 10
4: "efg"
15: "hij"
length: 16
<prototype>: Array []

(<>で表現している箇所は、ブログの構造上、半角文字の「<>」ですが、表示エラーになるので全角にしています)

上記の4つ目の出力結果だけは、配列を展開した形で出力を確かめています。

代入した値の取得

次に配列に代入した値を取り出してみます。

ここで注意が必要なことは、上記の例で配列で値を代入する場合、
配列のインデックスとしての「1」を使っていますが、実際は文字列の1として要素名を確定していることです。
これは[]演算子を使って配列の値を代入する場合の動作になるので注意が必要です。

値を取り出すには次のような書き方をします。

console.log(test1[0]);
console.log(test1[1]);
console.log(test1[13]);

便宜上、console.logで出力していますが、実際には「test1[0]」のように記述します。

上記の実行結果は、値が正常に取得できた場合は、配列内の値を取得、
インデックスが正しくない、存在しない場合は、undefinedが出力されます。

上記の例は次のようになります。

abc
10
undefined

当然、3番目の例は、配列のインデックスの13には要素を代入していないので、値の取得ができずにundefinedになります。
javascriptでは配列の値が取得できない場合はエラーで処理落ちすることなく、undefinedになるので注意が必要です。

配列の要素数について

配列を定義し、値を代入して利用すると、代入した数だけの要素が増えることになります。(要素を削除しない限りは)

要素数を出力するには、lengthプロパティを使います。

console.log(test1.length);

上記の出力結果は「16」となります。
ここで16となる理由を考察してみると、全ての要素に値を格納されている訳ではなく、
一番最初の値の代入の際に

let arr_cal = 5;
test1[arr_cal + 10] = "hij";

という配列の添え字に整数を使って代入した為、配列のインデックスとして解釈され、0から15までの要素が作成された為になります。
この時、途中で要素の代入をしていないインデックスは空(undefined)という形で要素が生成されます。

上記の考えにより「console.log(test1[13]);」という出力は「undefined」という形で出力されると言えます。

配列のインデックスに非整数値を使った場合

ここまでは配列のインデックスに数値を使ってきましたが、
あえて文字列を設定して代入と取得を試してみます。

次のようなコードを書いて実行してみます。

let test2 = [];

test2["z"] = 10;

console.log(test2);

出力結果は

[]
length: 0
z: 10
<prototype>: Array []

のようになります。
配列のN番目に値が格納されているわけではなく、配列のプロパティ名「z」に対して、値「10」が格納されていることになります。

試しに、この配列に対して、0番目や1番目の要素を取得してみます。

console.log(test2[0]);
console.log(test2[1]);

出力結果は、共に「undefined」になります。

また、この状態で配列test2に対してlengthを取得してみます。

console.log(test2.length);

出力結果は、「0」になります。

配列のインデックスに非整数値を使った場合(その2)

では、配列のインデックスにマイナスの値を使うとどうなるか試してみます。

次のようなコードを書いて実行します。

let test3 = [];

test3[-50] = 100;

console.log(test3);

出力結果は次のようになります。

[]
"-50": 100
length: 0
<prototype>: Array []

インデックスに「-50」と指定しましたが、この場合の動きは要素の「数」としてのインデックスではなく「-50」というプロパティ名として解釈され、「-50」の要素として「100」が配列に格納されている動きになります。

配列について

概要

これまでに何度もサンプルコードを書いてきましたが、
改めて配列の生成について、確かめてみます。

配列の生成方法(その1)

配列の生成がどのようにされるかを試してみるために、実際に次のようなコードを書いてみます。

//要素が空の配列
let test1 = [];

//要素が5つの配列
let test2 = [1, 10, 20, 30];

//要素の型が異なる配列
let test3 = [2, "20", 5, "v"];

//要素に式を使った配列
let val = 10;
let test4 = [val, val+5, val+10];

//オブジェクトリテラルを用いた配列
let test5 = {
	a: '10',
	b: '20',
	c: '20'
}

//配列リテラルで定義
let test6 = [10, , 15];

console.log(test1);
console.log(test2);
console.log(test3);
console.log(test4);
console.log(test5);
console.log(test6);

上記のコードの実行結果は次のようになります(ブラウザの開発者ツールで確認)。

Array []
Array(4) [ 1, 10, 20, 30 ]
Array(4) [ 2, "20", 5, "v" ]
Array(3) [ 10, 15, 20 ]
Object { a: "10", b: "20", c: "20" }
Array(3) [ 10, <1 empty slot>, 15 ]

また、Arrayという箇所については、内部を展開して確認できるようになっているので、
その部分を展開すると、次のようになります。

[]
length: 0
<prototype>: Array []


(4) […]
0: 1
1: 10
2: 20
3: 30
length: 4
<prototype>: Array []


(4) […]
0: 2
1: "20"
2: 5
3: "v"
length: 4
<prototype>: Array []

(3) […]
0: 10
1: 15
2: 20
length: 3
<prototype>: Array []


Object { a: "10", b: "20", c: "20" }


(3) […]
0: 10
2: 15
length: 3
<prototype>: Array []

上記の出力結果の中の「<prototype>: Array []」という箇所も、さらに展開できるので、ひとつの「<prototype>: Array []」を開いてみると次のように出力されます。

これは、配列オブジェクトに対してのprototypeオブジェクトのプロパティが出力されています。

<prototype>: []
concat: function concat()
constructor: function Array()
copyWithin: function copyWithin()
entries: function entries()
every: function every()
fill: function fill()
filter: function filter()
find: function find()
findIndex: function findIndex()
flat: function flat()
flatMap: function flatMap()
forEach: function forEach()
includes: function includes()
indexOf: function indexOf()
join: function join()
keys: function keys()
lastIndexOf: function lastIndexOf()
length: 0
map: function map()
pop: function pop()
push: function push()
reduce: function reduce()
reduceRight: function reduceRight()
reverse: function reverse()
shift: function shift()
slice: function slice()
some: function some()
sort: function sort()
splice: function splice()
toLocaleString: function toLocaleString()
toSource: function toSource()
toString: function toString()
unshift: function unshift()
values: function values()
Symbol(Symbol.iterator): function values()
Symbol(Symbol.unscopables): Object { copyWithin: true, entries: true, fill: true, … }
<prototype>: {…}
__defineGetter__: function __defineGetter__()
__defineSetter__: function __defineSetter__()
__lookupGetter__: function __lookupGetter__()
__lookupSetter__: function __lookupSetter__()
__proto__: 
constructor: function Object()
hasOwnProperty: function hasOwnProperty()
isPrototypeOf: function isPrototypeOf()
propertyIsEnumerable: function propertyIsEnumerable()
toLocaleString: function toLocaleString()
toSource: function toSource()
toString: function toString()
valueOf: function valueOf()
<get __proto__()>: function __proto__()
<set __proto__()>: __proto__()

配列の生成方法(その2)

上記の方法とは別にArray()コンストラクタを使って配列を生成する方法もあります。

その場合は次のようなコードを書きます。

let test7 = new Array();

上記のtest7をコンソールログに出力してみると、次のようになります。

[]
length: 0
<prototype>: Array []

当然のように内容が空(lengthが0)の空配列が生成されます。

生成時に予め要素数を指定する場合は次のように書きます。

let test8 = new Array(5);

上記の場合は、配列生成時に5つの要素を持つ配列を作ります。

また、要素数と、要素の値を指定して配列を生成する場合は次のように書きます。

let test9 = new Array(5, 10, 20, "abc", "def");

配列を配列リテラル(方法1)で生成する場合と、Array()コンストラクタを使って生成する場合と、それぞれ書き方が異なりますが、
どちらも配列を生成することができるので、要素数や要素内の値の有無、プログラム時の状況に合わせて生成方法を決めることがよいと思われます。

配列

概要

javascriptの配列についてまとめます。

これまでもサンプルコードで配列を多く書いてきましたが、
javascript特有の配列についての特徴をまとめてみます。

配列の特徴

・javascriptの配列には型という概念がない
・配列の要素については、どの型の値も格納できる
・配列の要素に、配列を格納することもできる
・配列の要素に、オブジェクも格納することができる
・配列インデックスは0から
・インデックスは32ビットの整数値
・最大インデックス値は4294967295
・要素数は動的に変化する
・インデックス値は要素に対して飛び飛びでも問題ない

オブジェクトに対してのメソッド

概要

javascriptのオブジェクトに対してのメソッドを調べてみます。

javascriptのオブジェクトは全てObject.prototypeからプロパティとメソッドを継承しています。
代表的なメソッドを具体的に調べてみます。

toString()

toString()メソッドはオブジェクトの値を表す文字列を返します。
オブジェクトを文字列に変換する必要がある場合、このメソッドを呼び出します。
例えば、配列を文字列に変換すれば、文字列に変換された配列要素のリストが取得できます。

toLocaleString()

このメソッドはオブジェクトを表すローカライズされた文字列を返します。

valueOf()

このメソッドは文字列以外の基本型にオブジェクトを変換する必要があるときに使います。

オブジェクトのシリアライズ

オブジェクトのシリアライズ

javascriptのオブジェクトのシリアライズについて、試してみます。

オブジェクトのシリアライズは、一度シリアライズしたオブジェクトを元に戻せるような文字列に変換します。
他のプログラム言語にもあるように、シリアライズ操作はプログラミングをするうえで要所で使う場面が出てきます。

ネイティブ関数の使用

まず、オブジェクトをシリアライズするケースからです。
javascriptのネイティブ関数として、JSON.stringify()という関数が用意されています。

逆に一度シリアライズした文字列をアンシリアライズ(元に戻す)場合は、
JSON.parse()という関数が用意されています。

実際にシリアライズを試してみます

まずは、検証用オブジェクトをシリアライズして、ブラウザの開発ツールでログ出力してみます。

//テスト用オブジェクトの用意
let test1 = {
	a: '10',
	b: '20',
	get access_a() {
		return this.a;
	}
}

//オブジェクト「test1」をシリアライズします
let s_obj = JSON.stringify(test1);

console.log(s_obj);

//出力結果は、以下のようになる
{"a":"10","b":"20","access_a":"10"}

以上のような出力結果になりました。

次にアンシリアライズを試してみます

上記の例で使用した変数を、そのままアンシリアライズしてみます。

//(先ほどシリアライズした際の変数を使用)アンシリアライズ
let us_obj = JSON.parse(s_obj);

console.log(us_obj);

//出力結果
Object { a: "10", b: "20", access_a: "10" }

出力結果は上記のようになります。
ブラウザごとに出力結果の表示方法は変わるので、見え方は異なりますが、オブジェクトの内容が列挙されます。

JSON形式について

json形式はjavascrptでプログラムする際に頻繁に使用するデータ形式になります。

正式には「Javascript Object notation」という名称になり、javascriptでオブジェクトリテラルや配列リテラルににたデータ形式でデータ構造を表したり、保持したりします。

シリアライズ可能なものと不可能なもの

シリアライズはjavascriptの値を全てサポートしているわけではないです。

次にサポートしているものと、サポートしていないものを整理してみます。

サポートしているもの

オブジェクト
文字列
配列
数値
true
false
null
NaN
Infinity
Dateオブジェクト

サポートされていないもの

Function
RegExp
Error
undefined

オブジェクトの拡張可属性について

オブジェクトの拡張可属性

オブジェクトに対して、新しくプロパティを追加できるかどうかの指定ができます。
この指定のことを拡張可属性と呼びます。

オブジェクトが拡張可かどうかは、ECMAScriptの処理系によって変わってきます。

ES5は組み込みオブジェクトとユーザ定義オブジェクトは拡張可になります。
明示的に拡張不可が設定された場合は拡張不可になります。

拡張可か拡張不可かを調べるには、Object.isExtensible()で調べます。

オブジェクトと拡張不可にするには、Object.preventExtensions()を使います。

オブジェクトの全ての独自プロパティを再定義不可にするには、Object.seal()を使います。
この場合、オブジェクトに新しいプロパティを追加することはできず、
削除することも再定義することもできません。

オブジェクトをロックするには、Object.freeze()を使います。
オブジェクトを拡張不可にしてプロパティの再定義も不可になります。
また、オブジェクトの独自データプロパティを読み出し専用にします。

オブジェクトのクラス属性について

概要

クラス属性について調べてみます。

クラス属性はオブジェクトの型情報を表す文字列です。
この属性は文字列として設定されており、取得の方法は限定的です。

オブジェクトからクラス属性を取得するにはtoString()メソッドを使います。

実際に簡単なコードを書いてみて動作させてみます。

var test1 = {
	a: '10',
	b: '20',
	get access_a() {
		return this.a;
	}
}
console.log(Object.prototype.toString.call(test1));

var test2 = new Object();
console.log(Object.prototype.toString.call(test2));

var test3 = new Array();
console.log(Object.prototype.toString.call(test3));

var test4 = new Date();
console.log(Object.prototype.toString.call(test4));

上記のコードの実行結果をブラウザの開発ツール(コンソール出力)で確認すると、次のようになります。

[object Object]
[object Object]
[object Array]
[object Date]

出力のルールとしては、先頭部分に「[object]」という文字列が必ず出力されており、その後にオブジェクトのクラス属性が出力されます。

オブジェクト属性の種類について

概要

javascriptのオブジェクトの属性には、3種類の属性があり、
オブジェクトの属性ごとに、使用方法や取得方法、設定方法が変わってきます。

いままではオブジェクトの属性を意識せずにサンプルのコードを書いていましたが、属性ごと詳しく違いを調べてみます。

プロトタイプ属性

オブジェクトが生成されるときにプロトタイプ属性が設定され、オブジェクトの生成方法によってプロトタイプの設定のされかたが変わります。オブジェクトリテラルを使って生成する場合
object.prototype
がプロトタイプ属性使われます。

newを使って生成する場合
コンストラクタ関数のprototypeプロパティの値が使われます。

Object.create()を使って生成する場合
Object.create()に渡された最初の引数が使われます。

オブジェクトのプロパティ属性について

概要

オブジェクトのプロパティには属性があります。

属性は大きく分けて「書き込み可能」「列挙可能」「再定義可能」などがあります。

また、アクセサプロパティのゲッターメソッドとセッターメソッドをプロパティ属性として考えるとすると、アクセサプロパティには書き込み可能属性がないと言えます。

データプロパティの属性は、値も属性のひとつとみなし、書き込み可能、列挙可能、再定義可能、の合計4つの属性があると言えます。
(アクセサプロパティには値の属性はないものと考えます)

まとめるとアクセサプロパティは、ゲッター、セッター、列挙可能、再定義可能、の4つの属性があると言えます。

プロパティのメタ属性の取得と設定


上記の概要で書いた4つの属性について、プロパティディスクリプタと呼ばれるオブジェクトを使います。

プロパティディスクリプタはプロパティのメタ属性と言え、データアクセスに対しての取り決めを決定しています。

データプロパティについては、次のようなメタ属性の名前を持ちます。
value
writable
enumerable
configurable
また、アクセサプロパティについては、次のようなメタ属性を持ちます。
get
set
enumerable
configurable
アクセサプロパティは、valueとwritableのメタ属性はありません。

試しに、前回の投稿で書いたサンプルコードに対して、プロパティディスクリプタオブジェクトを取得して試してみます。
var test = {
	a: '10',
	b: '20',
	get access_a() {
		return this.a;
	}
}

//プロパティディスクリプタオブジェクトを使用してtestオブジェクトのプロパティを出力
console.log(Object.getOwnPropertyDescriptor(test, 'a'));
上記のサンプルコードを実行した結果は次のようになります。

{…}

configurable: true

enumerable: true

value: "10"

writable: true

&lt;prototype&gt;: {…}

__defineGetter__: function __defineGetter__()

__defineSetter__: function __defineSetter__()

__lookupGetter__: function __lookupGetter__()

__lookupSetter__: function __lookupSetter__()

constructor: function Object()

hasOwnProperty: function hasOwnProperty()

isPrototypeOf: function isPrototypeOf()

propertyIsEnumerable: function propertyIsEnumerable()

toLocaleString: function toLocaleString()

toSource: function toSource()

toString: function toString()

valueOf: valueOf()

ブラウザの開発ツールの出力なので、出力結果がまとめられている箇所があり、出力結果のオープン/クローズで表示されるので一部抜粋しています。

上記の出力結果では
configurable: true

enumerable: true

value: "10"

writable: true
という先ほどあげたメタ属性についての記述があります。
また、上記の結果から、testオブジェクトのaプロパティは、データプロパティであることがわかります。(get、setが無く、valueやwritable属性が表示されています)

また、getOwnPropertyDescriptor()の処理対象はオブジェクトに対して独自プロパティのみが対象となります。
継承されたプロパティ等は対象ではない為に、継承オブジェクトには明示的にgetOwnPropertyDescriptor()を書く必要があります。

definePropertyについて

ここまでは、getOwnPropertyDescriptorを使ってオブジェクトのプロパティを調べる形でしたが、
オブジェクトのプロパティに対して、属性を設定(変更)するメソッドについても調べてみます。

オブジェクトに対してプロパティの属性を変更するには、
defineProperty
を使います。

書き方は
Object.defineProperty()
という形になり、()内で設定したいオブジェクトと、プロパティ名、それと属性(上記でいうメタ属性)を設定します。

具体的には、次のようになります。
var test = {
	a: '10',
	b: '20',
	get access_a() {
		return this.a;
	}
}

//definePropertyを使用して属性の変更
Object.defineProperty(test, 'a', {
	value: '30',
	writable: false,
});

//testオブジェクトのプロパティaに対し、文字列40を代入する
test.a = '40';

//内容を確認
console.log(test.a); //出力結果「30」
上記の例はtestオブジェクトのプロパティaに対して、メタ属性を変更しています。また、値の設定についても同時に行っています。

出力結果については、「30」と出力され、writableでfalseにしている為、値の代入を行っても書き変わらない挙動になります。

以上のように、オブジェクトのプロパティに対しての属性の設定を簡単に試してみました。

上記以外の命令や書き方があるので、ブログに追記する形で更新いたします。

ゲッターメソッドとセッターメソッド

概要

javascriptのオブジェクトについては、前回の投稿で簡単なオブジェクトの書き方、使い方を例にあげました。

オブジェクトについて、もう少し入り組んだ概念について詰めます。

オブジェクトに対してゲッターとセッターと呼ぶメソッドを指定でき、それぞれプロパティという位置づけで解釈することができます。

また、ゲッターとセッターについては、それぞれアクセサプロパティと、データプロパティという名前で区別して考えることができます。

アクセサプロパティについて

アクセサプロパティは書き込み不可の属性です。

プロパティがゲッターとセッターの両方のメソッドを持つ場合、 読み書き可のプロパティになります。

ゲッターしかない場合は、読み出し専用のプロパティになり、
セッターしかない場合は、書き込み専用のプロパティになります。

また、データプロパティは単純に値のみを持ちます。

簡単な例

具体的なゲッターメソッドは次のように定義します。

//ゲッター
var test = {
	a: '10',
	b: '20',
	get access_a() {
		return this.a;
	}
}

//呼び出す時の書き方
console.log(test.access_a);
この場合は、変数testに対して、ゲッターメソッド「access_a」を定義しています。

access_aを呼び出すと、メソッド内でプロパティaの値が、返されます。

文字通り「get」キーワードを下記、その後にメソッド名と処理を書きます。

次にセッターメソッドの場合は次のように定義します。

//セッター
var test = {
	c: '30',
	d: '40',
	get access_c() {
		return this.c;
	},
	set access_c(value) {

		//わかりやすいように10倍に計算する
		var sub_c = value * 10;

		this.c = sub_c;
	},
}

//呼び出す時の書き方
console.log(test.access_c); //出力結果「30」

//testオブジェクトのセッタプロパティに対して値を代入
test.access_c = 50;

console.log(test.access_c); //出力結果「500」

上記のようにセッターメソッドを使う時は、オブジェクトのプロパティに対して値を代入するように書きます。

例ではわかりやすいように、セッターメソッド内で値を10倍にして返しています。

セッターに値を入れた後に、改めてゲッターメソッドを使うと、出力結果は計算された結果が返ってきます。

オブジェクトのプロパティにアクセスする方法

概要

javascriptのオブジェクトのプロパティにアクセスする方法を試してみます。 簡単な方法として、for in を使ってオブジェクトにアクセスしてみます。 具体的には下記のように書きます。
var test = {a:10, b:20, c:30}

for (hoge in test) {
	console.log("hoge -> " + hoge);
}

//出力結果は次のようになります
hoge -> a
hoge -> b
hoge -> c
上記のようにfor inを使うとオブジェクト内の「プロパティ」にアクセスすることができます。 では、オブジェクト内の「値」にアクセスするにはどうすればよいか、試してみます。
var test = {a:10, b:20, c:30}

for (hoge in test) {
	console.log("hoge -> " + test[hoge]);
}

//出力結果は次のようになります
hoge -> 10
hoge -> 20
hoge -> 30
最初の例と似ていますが、for inで回した値を参照するタイミングで「オブジェクト[プロパティ]」という記述することで内容を取得することが可能になります。 上記の例は必ずしもこう書かないといけないわけではないので、参考例として書いています。 また、オブジェクトが継承されているケースもあるので、プログラムの実装時には挙動に注意が必要です。


オブジェクトのプロパティの削除

概要

javascriptでの、オブジェクトのプロパティの削除についてです。

これまでにもこのブログで出てきましたが、オブジェクトのプロパティを削除するには、delete演算子を使います。

前回のオブジェクト生成の例をもとにdelete演算子の挙動をみてみます。

//新規でオブジェクトを生成する
var test = {a:1, b:2}
  
//.で読み出す
console.log(test.a);
  
//[]で読み出す
console.log(test["a"]);

//delete演算子を使って、プロパティ「a」を削除する
delete test.a;

//delete演算子を使って、プロパティ「b」を削除する
delete test[b];

上記の場合、前者の削除の方法は.を使ってプロパティを指定しています。
後者の削除の方法は[]を使ってプロパティを指定しています。
どちらも同じ同様の動きになります。

注意する点としては、継承されたオブジェクトの継承プロパティは削除されないことです。

delete演算子はプロパティの削除が成功した場合にtrueを返します。
delete演算子は、再定義可の属性がfalseになっているプロパティは削除しないです。

また、注意しなければならないのは、use strictモードの場合と非use strictモードの場合で、挙動が異なる点です。
use strictモードでは再定義不可のプロパティを削除するとtype error例外が発生します。
非use strictモードでは同様の削除をするとfalseを返します。

オブジェクトのプロパティ呼び出しについて

概要

オブジェクトのプロパティの呼び出しについてです。

プロパティを呼び出す時の注意点としては、次のような点があります。

・存在しないプロパティから値を取得する
・独自プロパティから値を取得する
・継承したオブジェクトのプロパティから値を取得する
・存在しないオブジェクトからプロパティの値を取得する

上記の点では、存在しないプロパティから値を取得しようとしてもエラーにはならず、undefinedになります。
また、独自プロパティ、継承したオブジェクトのプロパティから値を取得しようとしても存在しないプロパティの場合はundefinedになります。

ただし、存在しないオブジェクトからプロパティを読み出すとエラーになります。(上記4つめのケース)

オブジェクトの継承とプロトタイプオブジェクト、プロトタイプへの参照と代入の挙動について

概要

javascriptのオブジェクトと、オブジェクトの継承、そしてオブジェクトのプロパティへのアクセスについてまとめます。

javascriptのオブジェクトの独自プロパティについては、そのオブジェクトとプロトタイプオブジェクトから継承したプロパティがあります。

オブジェクトのプロパティへのアクセスについて

例としてオブジェクトAの独自プロパティpを参照しようとする場合、
オブジェクトAの独自プロパティpにアクセスします。

この際、独自プロパティpがオブジェクトAに存在しなければ、
オブジェクトAのプロトタイプオブジェクトA'(ここでは便宜上A’とします)のプロパティpがあるかチェックします。

プロトタイプオブジェクトA’が、プロトタイプチェーンにより、さらにプロトタイプオブジェクトが継承されている場合は、その継承しているプロトタイプオブジェクト(オブジェクトA”とします)のプロパティpがあるかチェックします。

上記のような流れで任意のオブジェクトの任意のプロパティへアクセスする挙動になります。

文章にするとわかりにくい面があるので、簡単なイメージ図にしていみました。

オブジェクトのプロパティへ値を代入する場合

上記の例とは反対に、オブジェクトAのプロパティpに対して、値を代入する場合の挙動をまとめます。

参照する場合とは反対に代入する場合は、動きが変わってきます。

まず、オブジェクトAにプロパティpが存在する場合は、
プロパティpの値がそのまま変更されて終了します。
この場合、プロトタイプチェーンで継承されているオブジェクトのプロパティは無関係で影響を受けません。

次に、オブジェクトAにプロパティpが存在しない場合は、
オブジェクトAに対して、プロパティpが新規追加されます。
この場合も、プロトタイプチェーンで継承されているオブジェクトのプロパティは無関係で影響を受けません。

オブジェクトのプロパティを連想配列として解釈する

概要

前回の投稿では、生成したオブジェクトのプロパティにアクセスする方法を書きました。

//新規でオブジェクトを生成する
var test = {a:1, b:2}
 
//.で読み出す
console.log(test.a);
 
//[]で読み出す
console.log(test["a"]);

上記の方法で、2つ目に書いた[]で読み出す方法について、詳しくみてみます。
.で呼び出す方法と[]で呼び出す方法は、どちらも同じ結果を得ることができますが、[]で呼び出す方法については、文字列を使って呼び出していることが特徴的です。

文字列で呼び出すことが特徴なので、オブジェクトを配列と見立てると連想配列として考えることができます。

連想配列としてのオブジェクト

連想配列としてのオブジェクトとして考えると、javascript特有の柔軟な配列操作が可能になります。

例えば以下のように、オブジェクトのプロパティ名が動的に変わる場合でも処理がされます。

var test = {a:1, b:2}

//新たなプロパティを指定して値をセット
//例1
test["c"] = 3;

//例2
test["d"] = 4;


//プロパティ名に、動的な変数を使用
var o = 1;
//例3
test["e" + o] = 5;

//例4
test["e" + (o + 1)] = 6;


//プロパティ名に、動的な変数を使用(その2)
var p = 3;
//例5
test["e" + p] = 7;

//例6
test[p] = 8;


//プロパティ名に、動的な変数を使用(その3)
var q = 4;

//例7
test[q] = 9;

//例8
test[q + 4] = 10;


//確認の為、呼び出し
console.log(test["a"]);   //出力結果:1
console.log(test["b"]);   //出力結果:2
console.log(test["c"]);   //出力結果:3
console.log(test["d"]);   //出力結果:4
console.log(test["e1"]);  //出力結果:5
console.log(test["e2"]);  //出力結果:6
console.log(test["e3"]);  //出力結果:7
console.log(test[p]);     //出力結果:8
console.log(test["p"]);   //出力結果:undefined
console.log(test[q]);     //出力結果:9
console.log(test[4]);     //出力結果:9
console.log(test["q"]);   //出力結果:undefined
console.log(test[8]);     //出力結果:10

後半の例6、例7、例8は少し紛らわしく書きました。

例6について

オブジェクトのプロパティを文字列を示すダブルクォートで囲っていないので、
これは単純に配列のn番目に値を代入する動きになります。

その証拠として、例8ではプロパティを単なる数値として直接指定して値を呼び出しても出力結果が「10」という形で取り出せます。

また、例6に対して、

test["p"]

とした場合の出力結果はundefinedになります。

これはtest[p]という(pは数値の3が代入されている)オブジェクトのプロパティへ代入していますが、pは数値の3なので配列の3番目(0はじまりだと4番目)に値を代入している解釈になるので、
文字列としてのtest[“p”]という形で値を取り出そうとしても存在しない為にundefinedになります。

例7について

オブジェクトのプロパティに対して数値を指定している為、
値を参照する際にも、test[“q”]という形で文字列として取り出そうとするとundefinedになります。

例8について

オブジェクトのプロパティが数値の加算になっているので、
配列の添え字として解釈され、問題なく値が取得できます。

オブジェクトのプロパティ読み出しについて

オブジェクトのプロパティを読み出す方法

オブジェクトのプロパティを読み出す方法は、
以前の投稿でも取り上げましたが、次のように書きます。

var test = {a:1, b:2}

//.で読み出す
console.log(test.a);

//[]で読み出す
console.log(test["a"]);

上記はtestオブジェクトを生成し、その中からaというプロパティの値を読み出しています。

オブジェクトのプロパティに値を代入する方法

上記の例とは逆にオブジェクトのプロパティに値を代入する方法は次のように書きます。

//上記で生成したオブジェクトを使用
test.a = 10;

//.で読み出す
console.log(test.a);

test["b"] = 20
//[]で読み出す
console.log(test["b"]);

オブジェクトの生成

Object.create()について

javascriptでは新しいオブジェクトを生成するタイミングでメソッドが定義される仕組みがあります。
Object.create()メソッドがそれにあたります。
引数の指定の仕方によって、生成されるオブジェクトに対してのプロトタイプとしての扱いが変わるので、注意が必要です。

Object.create()の挙動について

Object.create()はプロトタイプオブジェクトを引数にして呼び出します。
nullを渡すとプロトタイプを持たないオブジェクトを生成します。
空のオブジェクトを生成する場合は、Object.prototypeを引数にします。

プロトタイプを持つオブジェクト生成

オブジェクトを生成するタイミングで、任意のプロトタイプを生成することができます。

プロトタイプ

概要

javascriptにはプロトタイプという概念があります。
このプロトタイプについて、調べてみます。

プロトタイプは、全てのオブジェクトに関連つけられているオブジェクという考え方ができます。

ここで言う全てのオブジェクトというのは、javascriptが標準で利用できるオブジェクトで、それらのオブジェクトに対して、プロトタイプと関連づいていると言えます。

オブジェクトをnewする

オブジェクトをnewするタイミングで、プロトタイプオブジェクトを持っている状態になります。
newで生成されたオブジェクトは、Object.prototypeを継承しています。
新規に生成されるオブジェクトの種類によって、継承するプロトタイプも異なってきます。
例えば、

new Array()

で生成するオブジェクトに対しては、Array.prototypeの値(コンストラクタも)を継承します。

プロトタイプチェーンについて

上記のnew Array()の場合、オブジェクトを生成したタイミングで、Array.prototypeとObject.prototypeの両方を継承します。
この両方を継承したオブジェクトの形をプロトタイプチェーンと呼び、両方のプロパティとメソッドが利用可能になる特徴があります。

オブジェクトの生成

概要

javascript内でオブジェクトを生成する方法を試してみます。

オブジェクトの生成の方法は、次のとおりです。

①オブジェクトリテラルを使って生成
②newキーワードを使って生成
③Object.create()を使って生成

③の方法はECMAScript5の言語仕様に則ってプログラミングする場合に有効です。

オブジェクトリテラル

オブジェクトリテラルを使った生成を試してみます。
変数を宣言し、その変数に対して、新規のオブジェクトを生成し、代入します。

オブジェクトリテラルは、中括弧を使い、内部には「プロパティ名」と「値」をペアでカンマ区切りで記述します。
プロパティ名と値はコロンで区切ります。

//オブジェクトリテラルによる生成

//空のオブジェクトを生成する
var testobject = {};
console.log(testobject); //出力結果「Object {  }」

//要素を2つ持つオブジェクトを生成する
var testobject2 = {a:10, b:20};
console.log(testobject2); //出力結果「Object { a: 10, b: 20 }」

//オブジェクト内の要素が他のオブジェクトのデータを扱う場合
var testobject3 = {c:testobject2.a, d:testobject2.b};
console.log(testobject3); //出力結果「Object { c: 10, d: 20 }」

リテラルを使ったオブジェクトで、プロパティ名に予約語を使う場合は注意が必要です。
ECMAScriptのバージョンによって挙動が変わるので、
各バージョンにより、挙動を確かめてプログラムする必要があります。
一般的にはプロパティ名には予約語ではない文字列を割り当てたほうがプログラミングしやすいかもしれないです。

newキーワードを使った生成

new演算子を使ってオブジェクトを生成します。
new演算子のあとに関数呼び出しを記述して、オブジェクトを生成します。
オブジェクトが生成されるときに必ず実行される関数をコンストラクタと呼び、内部で初期化等の動作をします(各関数によって動作が違うので、詳しくはリファレンスを調べる必要があります)

実際の動きについて、簡単なコードを書いて出力結果を確認してみます。

//newキーワードで生成

//空のオブジェクト生成
var testobj1 = new Object();
console.log(testobj1); //出力結果「Object {  }」

//空の配列オブジェクトを生成
var testobj2 = new Array();
console.log(testobj2); //出力結果「Array []」

//日時を扱うオブジェクトを生成
var testobj3 = new Date();
console.log(testobj3); //出力結果「Date 2018-11-14T12:30:02.403Z」

オブジェクト

概要

javascriptのオブジェクトについての概要です。

・オブジェクトは複合型のデータ型になります。
・複数のデータをまとめて保持し、読み書きが可能です。
・データをプロパティとして呼ぶこともある。
・プロパティは原則、文字列として指定する。
・各プロパティ(文字列)に対し、値をマッピングする。
・マッピングしたものには呼び名がつけられる(ハッシュ、連想配列等)

・オブジェクトは、文字列、数値、真偽値(true/false)、null、undefined以外の値になる。
・オブジェクトは可変の振る舞いをする。(値を参照で操作する)

・オブジェクトの操作は、生成、プロパティへの値の代入、読み出し、削除など。
・同名のプロパティはNG。
・プロパティは「名前と値」の組み合わせ。その他「プロパティ属性」という値を持つ。
・プロパティ属性は「書き込み可」「列挙可」「再定義可」がある。

・オブジェクトはオブジェクト属性を持つ。「プロトタイプ」「クラス」「拡張可フラグ」
・オブジェクトは大きく3種類あり、「ネイティブオブジェクト」「ホストオブジェクト」「ユーザ定義オブジェクト」
・プロパティは大きく2種類あり、「独自プロパティ」「継承プロパティ」

javascriptのオブジェクトの特徴

オブジェクト自身のプロパティの他、ほかのオブジェクトのプロパティも「継承」することができる。
継承の対象となるオブジェクトをプロトタイプと呼びます。

オブジェクトの詳細な挙動について

javascriptのプログラムをより深く正確にする為には、
オブジェクトについての理解を深める必要があります。

オブジェクトの詳細な挙動は、一つ一つこのブログで掘り下げて、できるだけサンプルプログラムを書いて、挙動を確かめながら記事化していきます。

文についてまとめ

概要

javascriptの文についてまとめます。

文はまず、式文と宣言文があり、式文は何らかの処理を書くことで作用します。
宣言文は変数や関数を宣言し定義するための文です。

また、実行順序を変更したり制御する為の制御文もあります。
制御文には以下のようなものがあります。
・条件文
if
swich
・ループ文
for
while
・ジャンプ文
break
return
throw

式文

もっとも簡単な文で、変数の代入や、インクリメント、デクリメント、オブジェクトのプロパティ削除のdeleteなども式文として使われます。

複合文

複合文とは文ブロック

{

}

で囲んで記述される文です。
文ブロックは末尾にセミコロンを記述しません。

宣言文

var
function
等の宣言する文が宣言文です。
変数の宣言と関数の宣言を行います。

var

変数を明示的に宣言する文です。

function

関数を明示的に宣言する文です。
javascriptの関数は、次のように宣言する方法

function test() {

}

や、次のように関数を変数に代入する式もあります。

var ftest = function test() {

}

//呼び出す時
ftest();

条件文

指定された式の値に応じて、他の文を実行したり、処理を分岐したりします。
条件文は「if文」や「if else」、「switch文」などあります。

if文

javascriptが条件を判定して、処理を分岐する文です。

if else文

if文と組み合わせて書きます。
これまで何度もサンプルコードを書いてきましたので、詳しい内容は割愛します。

switch文

ある一つの判定要素に対し、条件分岐が多数ある場合に用いる書き方です。
簡単なサンプルを書きます。

var test = 10;

swicth(test) {
	case 1:
		//処理A
		break;
	case 5:
		//処理B
		break;
	case 10:
		//処理C
		break;
	case 15:
		//処理D
		break;
	default:
		//処理E
		break;
}

上記の場合、変数testの値を判定し、処理Cが実行されます。
処理後のbreakを書かなければ、そのまま次の処理(上記の場合は処理D)が実行されます。

if else文のelseを連続的に書くよりは、この書き方のほうが処理のみとおしが立ちやすくなります。

ループ文 – while

とある条件が真の間、処理を繰り返すループです。

ループ文 – do while

とある条件が真の間、処理を繰り返すループです。
while文との違いは、最初の1回目は条件の真偽に関係なく、必ず実行されます。

ループ文 – for

カウンタ変数を持ち、そのカウンタの条件が真の間は処理を繰り返すループです。

ループ文 – for inf

オブジェクトに対して、オブジェクトのプロパティを巡回する時に書きます。

for (var test in testobject) {
	console.log(testobject[test]);
}

のように書きます。
オブジェクトについて深く理解する必要があるので、オブジェクトとプロパティについては、別途堀さげて書きます。

ジャンプ文

ループの処理中に、条件や状態により、処理をジャンプする場合に書きます。
breakやcontinueを使います。

ラベル文

先頭に識別子を書き、その後にコロンを書き、最後に文を記述します。

testlabel:while(1) {
	//ループ内の処理
	
	//ラベルを利用して、ループ処理を実行
	continue testlabel;
}

上記の例はラベル「testlabel」を宣言し、その後のwhileループを割り当てています。
ループ処理内のtestlabelの文が実行されると、ラベルしたwhileが実行されます。
上記のサンプルは極端な例ですが、ラベルの行でジャンプするイメージになります。

break文

ループ処理中に、処理をとめてループを抜ける時に書きます。

continue文

ループ処理中に、continue文より後の処理は実行せず、またループの最初から処理を開始する時に書きます。
一般的には条件文の後に、指定条件の場合にcontinue文を書くケースが多いです。

return文

関数内の処理が完了し、関数を抜けて結果を返す処理として書きます。

throw文

ある例外的な条件やエラーが発生したことを示すシグナルのことです。
次のようにループの処理中に、例外を強制発動することができます。

for (var i=0; i<10; i++) {
	//例外が発生した場合にスローする
	throw new Error("Err");
}

try catch finally文

java言語とよく似ていますが、プログラム中の処理を試み、
例外が発生した場合は、処理をcatchし、全ての処理後にfinallyを実行する文です。

with文

実用性は低いですが、オブジェクトが入れ子になっている場合や、
多数のオブジェクトのプロパティに何度もアクセスする場合に使います。

use strict文

ECMAScript5で導入された指示子です。
この指示子が書かれたプログラムは、厳密なコードであることが要求され、
プログラムもまた厳密に書きます。(とは言っても常に正確に記述することは基本です)

カンマ演算子

概要

一般的にプログラムを書いていると、よく使用するカンマですが、
javascriptではカンマも演算子と解釈するケースがあります。

セパレータとしてのカンマと、演算子としてのカンマがあります。

また、C言語やC++でもカンマ演算子はあります。
言語のできた順からするとCやC++のほうが先になります。
(C言語は1972年頃、C++は1983年頃、javascriptは1995年頃)

カンマ演算子の挙動

カンマ演算子は、左被演算子を評価して、その値を捨て、右被演算子を評価する動きになります。

演算子の実行例

実際にこの演算子について、コーディングして動作を確かめてみます。

//セパレータとしてのカンマ(変数の宣言なので、カンマ演算子では無い)
var x = 10, y = 20, z = 30;

//カンマ演算子としてのカンマ
var ret = (x, y, z);
console.log(ret); //出力結果「30」カンマ区切りの最後の文字列が代入される


//代入処理がされる場合
var ret = x, y, z;
console.log(ret); //出力結果「10」カンマ区切りの最初の文字列が代入される


//加算演算子を含む場合
var ret = (x += 2, y +=3, z += 4);
console.log(ret); //出力結果「34」


//加算演算子を含む場合(括弧をつけない場合)
var ret = x += 2, y +=3, z += 4;
//出力結果は「SyntaxError: unexpected token: '+='」
//javascriptでは、上記の書き方はエラーになる


//インクリメント
var x = 10, y = 20, z = 30;
x++, x++;
console.log(x); //出力結果「12」


//カンマ演算子を二項演算子の左辺に下記、値を代入する
(x, y) = 40;
//出力結果「ReferenceError: invalid assignment left-hand side」
//javascritpの言語仕様としてエラーになる

上記のような結果になります。
「左被演算子を評価して、その値を捨て、右被演算子を評価する」と一言で言っても、括弧の有無で挙動が変わる点に注意が必要です。

void演算子

概要

あまり見慣れないvoid演算子についてまとめます。

void演算子は単項演算子で、オペランド値を廃棄し未定義値を返す動作をします。

実際の使用場面

void演算子は特殊な利用用途以外では、あまりプログラムしません。

働きとして、戻り値がundefineにならなけれならない場合に使用することが多いです。

typeof演算子

概要

typeof演算子についてまとめます。

オペランドは1つのみで演算します。

オペランドの「データ型」(文字列)を返します。

typeof演算子で判定できる値について

typeof演算子は、以下の値を演算(判定)することができます。

undefined
null
true/false
数値
NaN
文字列
関数
ネイティブオブジェクト
ホストオブジェクト

条件演算子

概要

条件演算子は「?:」で記述する演算子です。
一般的にこれは三項演算子と呼ばれ、オペランドを3つ使い演算を行います。
先頭のオペランドの後に「?」を記述し、その後、2つ目のオペランドを記述し、その後「:」に続けて3つ目のオペランドを記述します。

書き方としては、次のようになります。

判定条件 ? 結果1 : 結果2

判定条件がtrueの場合は結果1のオペランドの値を評価し、falseの場合は結果2のオペランドの値を評価します。

三項演算子については、他の言語にもあり、開発時の環境やプログラマによっても意見が分かれるケースがあります。

eval()

概要

eval()について、詳しく調べてみます。

eval()は1つの引数を渡します。
引数として文字列を渡し、それ以外の場合はそのままの値を返します。

引数が解釈できない場合はシンタックスエラー例外になります。
解釈できた場合は、引数を評価します。

実行例1

eval()を呼び出すとき、呼び出したコードの環境変数が使われます。

実際にeval()で呼び出されたコードの変数が使われるかどうか、
例えば、次のようなコードを書いてみます。

var x = "123";

console.log(eval("x"));

出力結果は

123

になり、eval()で「x」という変数を呼び出したことになります。

実行例2

eval()の引数内で変数定義や関数定義も可能です。
また、定義した変数に値を代入することもできます。
例えば、次のようなコードを書いてみます。

var y = "abc";

console.log(eval("y='d'"));

出力結果は

d

という結果になります。
出力結果を確認する為に、console.logを行っていますが、便宜上書いているだけです。

実行例3

eval()での関数定義を試してみます。

eval("function abc() { return 'z'; }");

console.log(abc());

出力結果は

z

という結果になります。

評価式(eval)

概要

javascriptにはソースコード文字列を評価、実行する機能「eval」があります。
このevalはグローバル関数として定義されており、eval()という形で値を評価します。

evalは()内にはいる文字列を評価します。

eval(string)

stringには文字列が入りますが、文字列をそのまま評価するのではなく、動的に評価されます。

簡単に次の例を書いてみます。

console.log(eval("10 + 15"));

出力結果は「25」という結果になります。

では、次のような書き方をするとどうなるか試します。

var test = 30;

console.log(eval(test  + 15));

出力結果は「45」になります。
このように、引数として文字列を渡す場合でも、数値(の式)を渡す場合でも、評価時にjavascriptのコードとして実行される機能をもつことが特徴です。

算術演算と代入演算子

概要

javascriptは算術演算と代入演算子を組み合わせて使用することができます。
例えば二つのオペランドを「+=」という書き方を使って右辺のオペランドを左辺に代入する時、値を加算して代入することが可能です。

サンプル

次のような例を書いてみて動きを確かめます。

var test = 10;

test += 20;

console.log("test -> " + test);

非常に簡単な例ですが、出力結果は「30」と出力されます。
この時に気をつけなければならないのは、上記の例の場合は2つのオペランドが数値であることです。

では、この数値が文字列だった場合、どのような動きになるか試してみます。

var test = '10';

test += '20';

console.log("test -> " + test);

先ほどの例とほとんど同様のコードですが、出力結果は「1020」となります。
代入するタイミングの算術演算子(加算演算子)が、文字列同士を結合する働きをした為です。

代入演算子の種類

代入演算子には、上記の加算演算子の他に、次のような種類があります。

//加算代入演算子
+=

//減算代入演算子
-=

//乗算代入演算子
*=

//除算代入演算子
/=

//剰余代入演算子
%=

//左シフト代入演算子
<<=

//右シフト代入演算子(符号有り)
>>=

//0埋め右シフト代入演算子(符号無し)
>>>=

//ビットごとのAND代入演算子
&=

//ビットごとのOR代入演算子
|=

//ビットごとのXOR代入演算子
^=

それぞれの代入演算子に記載している「=」の前の演算子の性質を持ち、その演算結果を左にあるオペランドに代入する。という働きになります。

代入演算子

概要

javascriptの代入演算子についてです。
これは読んで字のごとく、変数に値を代入する為の演算子で、
これまでのサンプルで当たり前のように使ってきました。

動作について

代入演算子は「=」と記載し、左のオペランドに対し、右のオペランドを代入します。
左側のオペランドは変数、オブジェクトのプロパティです。
右側のオペランドは任意の値(型も任意)です。

代入時に複数の変数に代入することも可能です。

var test;
var test2;

test = test2 = 10;

上記のようにコードを書くと、変数testとtest2にそれぞれ10が入ります。

論理否定演算子

概要

論理否定演算子は

!

で表す単項演算子になります。
一つのオペランドに対して演算を行います。
論理否定演算子が返す値はtrueかfalseのどちらかになります。
通常はif文などの条件式について、条件を反転させた結果を判定する場合に使われます。

1 7