javascriptのクラスごとのメンバーについて

Posted コメントするカテゴリー: javascript

概要

javascriptのクラスについて、クラスごとのメンバーを確認します。
javascriptでは関数を値として考えることができ、メソッドとフィールドに区別はないです。

プロパテの値が関数の場合は、メソッドとしての振る舞いになり、
関数ではない場合は、プロパティ(値)となります。

クラスに関係するオブジェクトは次の3種類になります。

コンストラクタオブジェクト
プロトタイプオブジェクト
インスタンスオブジェクト

各オブジェクトと実際のコードの書き方、動作の確認等、次回の投稿で掘り下げてみます。

コンストラクタ

Posted コメントするカテゴリー: javascript

概要

javascriptでクラスを定義する際にも、他の言語と同様にコンストラクタを定義する書き方があります。

コンストラクタはクラスをインスタンス化するタイミングで実行される(初期化のタイミングで実行される)関数と考えることができます。

コンストラクタはnewキーワードを使ってインスタンスを生成するタイミングで自動的に呼び出されます。

javascript特有の動きとしては、コンストラクタの呼び出し時にprototypeプロパティが新しいオブジェクトのプロトタイプとして使われます。

次にコンストラクタを使ったコードを書いてみます。

//クラスとなるメソッドを定義
function Testclass()
{
	//生成したオブジェクトに対して、プロパティを定義
	this.test1 = 2;
	this.test2 = 60;
}

//上記のクラスに新たにメソッドを定義(付与)する
Testclass.prototype = {
	
	//1つめのメソッドを追加
	addfunc1: function(a)
	{
		//引数aに元のクラスのプロパティtest1の値を加算して返す
		return a + this.test1;
	},
	
	//2つめのメソッドを追加
	addfunc2: function(b)
	{
		//引数aに元のクラスのプロパティtest2の値を加算して返す
		return b + this.test2;
	}
};

//Testclassメソッドをもとにインスタンス「ti」を生成する
let tic = new Testclass();

//インスタンス
console.log(tic.addfunc1(2));
console.log(tic.addfunc2(3));

上記の出力結果は

4
63

という値になります。

前回に投稿したブログ記事と構造は似ていますが、クラス「Testclass」の定義の方法と
呼び出し方(インスタンスの生成方法)が異なっています。

newキーワードを使ってTestclassを呼び出しているので、下記のように呼び出すタイミングで

let tic = new Testclass();

コンストラクタの実行としてfunction Testclass() 内の命令が実行され、this.test1 と this.test2 にそれぞれ値が入っているのことが把握できます。

また、クラス名(コンストラクタ)の先頭は大文字にしています。
小文字でも動作は同様になるのですが、一般的な慣習としてクラスの先頭は大文字にして書くと良いかと思います。

次に「Testclass.prototype」の定義の仕方ですが、これはコンストラクタ呼び出しの際に、オブジェクトのプロトタイプとして自動的に.prototypeが使われる為、このような書き方になります。

クラスとプロトタイプ

Posted コメントするカテゴリー: javascript

概要

javascriptのクラスはプロトタイプオブジェクトからプロパティを継承するオブジェクトと言えます。

プロトタイプオブジェクトはクラスの原型という位置づけで考えられ、
このプロトタイプオブジェクトを生成することはクラスを定義することと同じような意味になります。

一般的には新しいオブジェクトを生成する場合、インスタンスを初期化します。

次にシンプルなクラスを定義して、実際にコードを書いてみます。

//クラスとなるメソッドを定義
function testclass()
{
	//プロトタイプオブジェクト(addmethod)を継承するオブジェクトを生成する
	let tc = testclass.addmethod;
	
	//生成したオブジェクトに対して、プロパティを定義
	tc.test1 = 1;
	tc.test2 = 50;
	
	//オブジェクトを返す
	return tc;
}

//上記のメソッドに新たにメソッドを定義(付与)する
testclass.addmethod = {
	
	//1つめのメソッドを追加
	addfunc1: function(a)
	{
		//引数aに元のクラスのプロパティtest1の値を加算して返す
		return a + this.test1;
	},
	
	//2つめのメソッドを追加
	addfunc2: function(b)
	{
		//引数aに元のクラスのプロパティtest2の値を加算して返す
		return b + this.test2;
	}
};

//testclassメソッドをもとにインスタンス「ti」を生成する
let ti = testclass();

//インスタンス
console.log(ti.addfunc1(2));

console.log(ti.addfunc2(3));

上記の出力結果は

3
53

になります。

上記のコードの動きについてですが、まずクラス「testclass」を定義し、その定義したクラスに対してプロトタイプオブジェクトを継承し、
継承したプロトタイプオブジェクト内のメソッドaddfunc1やaddfunc2を呼び出しています。

このように書いて検証してみることで、プロトタイプオブジェクトの動きが見てわかるようになります。

クラス

Posted コメントするカテゴリー: javascript

概要

javascriptのクラスについて、特徴を簡単にまとめてみます。

クラスの考え方として一般的にプロパティとメソッドを持ちます。
プロパティはクラス内で保持する変数(のようなもの)の概念があり、メソッドはクラスの振る舞いを定義する関数のようなものです。

javascriptでクラスを定義した場合、上記のプロパティとメソッドを用い、処理を行うことができます。

プロトタイプ

javascriptはプロトタイプという概念があるので、クラスを定義する際にもその考え方が適用されます。
2つのオブジェクトが同じプロトタイプからプロパティを継承している場合、この2つのオブジェクトは同じクラスのインスタンスとして振舞います。

コンストラクタ

オブジェクト指向の言語で必ず出てくるキーワードとしてコンストラクタがあります。
コンストラクタはクラスを基にオブジェクトが生成されたタイミングで実行されるメソッドと考えられます。

次回からの投稿でクラスを詳しく試して把握していこうと思います。

メモ化

Posted コメントするカテゴリー: javascript

概要

javascriptの関数の書き方としてメモ化(Memoization)というものがあります。
これはjavascriptのみならず、他の言語でも同じ概念で書くことができます。

ざっと調べると「関数のサブルーチンの呼び出し結果を後で再利用する為に保持し、そのサブルーチンの呼び出し毎の再計算を防ぐ手法」ということが言えます。

この保持する部分を「オブジェクトに記憶」や「キャッシュしておく」という言い方に変えればイメージがつきやすくなるかもしれません。

サンプル

簡単な例を書いてみました。

// 関数定義
let testfunc = function(n) {

	let memo;

	// キャッシュのキーに値があるかどうか
	if (testfunc.cache[n]) {
		// キャッシュ内にある場合は、既にメモ化された結果を返す
		return testfunc.cache[n];
	} else {
		// 引数を基にキャッシュから読み込み
		memo = {};

		// 仮の演算を行う(時間がかかる処理にする)
		for (i = 0; i < 100000; i++) {
			memo[i] = i * n * 2;
		}

		// 演算結果をメモ化したキャッシュに格納
		testfunc.cache[n] = memo;
		return testfunc.cache[n];
	}
};

// メモ化した関数のキャッシュ領域を用意
testfunc.cache = {};

上記の関数定義と、その後キャッシュ領域の用意(初期化)を行うことにより、
初回の関数呼び出しのみ、時間のかかる処理が呼び出され、その後の呼び出し時には、一度演算したことがある引数についてはキャッシュから値を読み込んで値を返す動作になります。

高階関数

Posted コメントするカテゴリー: javascript

概要

高階関数についてまとめます。
高階関数を簡単に言うと、「引数で関数を取り扱う処理方法」ということができます。

関数を引数にとり、関数内で引数の関数に対して処理を行い、演算結果を返す関数です。
引数に1つ以上の関数を受け取り、新たな関数を返します。

簡単なサンプル

文章で書いても理解しにくいので、簡単なサンプルを書いて実行してみます。

//高階関数の定義
function test(fs, data) {
    fs(data + 1);
}

//ユーザ定義の関数
function test2(val_a) {
    console.log(val_a);
}

//テスト用の変数に数値を定義
let t_val = 10;

//高階関数を呼び出す
//このとき、引数の1つめはユーザ定義関数、2つめはテスト用の変数
test(test2, t_val);

実行結果は「11」になります。

上記の例では単純な関数を使っていますが、書き方次第では、
ユーザ定義関数をより複雑な処理にすることもできます。

関数型プログラミングについて

Posted コメントするカテゴリー: javascript

概要

javascriptは関数をオブジェクトのように扱えるので、関数型プログラミングの書き方ができます。

サンプル(reduce)

関数型プログラムの例として、配列をreduce()メソッドを用いて処理してみます。

まず、reduce()のサンプル

let arr = [1, 2, 3, 5];

let calc_arr = arr.reduce(function (x, y) {
	return x + y;
});

console.log(calc_arr);

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

ものすごい単純な例ですが、reduceメソッドは関数型プログラミングの動作をしていると言えます。

reduceは、元となる配列arrの1つめの要素を引数と渡して処理した後で、演算後にreturnされた値を、2つめ以降の関数の引数として用いることができます。(配列の要素が無くなるまで演算されます)

こうすることで、上記の例では配列の各要素が順に加算された演算結果「11」となります。

サンプル(map)

次に関数型プログラムの例として、配列をmap()メソッドを用いて処理してみます。

上記のreduce()のサンプルを少し変えて、次のように書いてみました。


//mapサンプル
let arr2 = [1, 2, 3, 5];

let calc_arr2 = arr.map(function (x) {
	return x + 10;
});

console.log(calc_arr2);

出力結果は

Array(4) [ 11, 12, 13, 15 ]

という結果になります。

map()は、配列の各要素に対し、map()内部で宣言された無名関数の演算結果を、関数の戻り先の配列に対して、各要素の値を演算した結果を返して生成しています。
ただ、map()引数の指定の仕方によっては、元の配列要素の値を変更することが可能です。

それぞれの違い

map()は、reduce()と違い、要素数が複数になります。

map()は配列の全要素に対して演算を行い、演算結果も配列の要素数が生成されることに対して、
reduce()は配列の各要素に対して、演算した結果をさらに無名関数内部で演算をするため、処理結果が単一となります。

for等のループ文を使うより、シンプルな書き方ができるので、実用面で有効な書き方と言えそうです。

クロージャ(その2)

Posted コメントするカテゴリー: javascript

クロージャ(その2)

クロージャについて、別なサンプルを書いて動作させてみます。

前回のクロージャは、関数内にローカル変数を宣言し、そのローカル変数をクロージャを動作させるタイミングでアクセスしていました。
次に、ローカル変数を宣言せず、関数の引数として指定するサンプルを動かしてみます。

function test3(n)
{
	return {
		//ゲッター
		get f1() {
			return n++;
		},
		//セッター
		set f1(m) {
			n = m;
		}
	};
}

//関数test3を変数へ代入し、オブジェクトを生成する
var obj3 = test3(50); //このタイミングでゲッターメソッドを実行する

console.log(obj3.f1);
console.log(obj3.f1);
console.log(obj3.f1);

obj3.f1 = 100; //セッターメソッドを実行する

console.log(obj3.f1);
console.log(obj3.f1);
console.log(obj3.f1);

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

50
51
52
100
101
102

上記のように、関数内にローカル変数を持たない場合でも、
引数を渡してその値をコントロール(セッター&ゲッター)することにより、
内部の値を保持するような形で処理することができています。

クロージャ

Posted コメントするカテゴリー: javascript

概要

他の言語同様、javascriptにもクロージャの概念があります。

コードを書きながら理解してみようと思います。

基本的な考え方

クロージャを理解するには、関数とスコープについて整理が必要です。
まず、関数が定義された時、変数の定義がどこでされているかに注意します。

通常、関数を定義すると、スコープチェーンを持ちます。

関数の中に(入れ子の)関数がある状態にある場合、定義された(入れ子の)関数から、親側の関数内で定義された変数を参照できる。という考え方があります。

文章にするとわかりにくいので、まず通常の入れ子の関数を書いてみます。

let test_val = "Global_A";

function test1()
{
	let test_val = "Local_A";
	
	function test2() {
		return test_val;
	}
	
	return test2();
}
console.log(test1()); //出力:Local_A

「console.log(test1());」の行で出力された文字列は「Local_A」になります。

次に、ネストされた関数オブジェクトを返す関数にしてみます。

let test_val = "Global_A";

function test1()
{
	let test_val = "Local_A";
	
	function test2() {
		return test_val;
	}
	
	return test2; //このタイミングで関数オブジェクトを返す
}
console.log(test1()()); //出力:Loacl_A

一番外側で「test1()()」という形で(親となる)関数内にあるネストされた関数をオブジェクトを使って呼び出しています。
最初の例と合わせて、どちらも「Local_A」が出力されます。

簡単なクロージャの書き方(例)

ここで、非常に簡単なクロージャの例を書いて動かしてみます。

var test1 = (function() {
	var counter = 0;
	
	return function() {
		return counter++;
	};
}());

console.log(test1());
console.log(test1());
console.log(test1());
console.log(test1());

上記のコードを動作させると、出力結果は

0
1
2
3

となります。

test1()を呼び出す度に、関数内部で宣言されているローカル変数counterがカウントアップされた結果が返ってきます。
test1に返ってくる値は全体が括弧()で囲まれているので、内部の無名関数の戻り値が代入されます。

上記の例では、無名関数内に一つの入れ子型の関数がある形になりますが、
無名関数が2つある場合は次のような書き方ができます。

function test2()
{
	var n = 1;
	
	return {
		doubleup: function() {
			n = n * 2;
			return n;
		},
		reset: function() {
			n = 0;
		}
	};
}

//関数test2を変数へ代入し、オブジェクトを2つ生成する
var cnt1 = test2();
var cnt2 = test2();

//出力確認(1回目)
console.log("----");

console.log(cnt1.doubleup());
console.log(cnt1.doubleup());
console.log(cnt1.doubleup());
console.log(cnt1.doubleup());

console.log(cnt2.doubleup());
console.log(cnt2.doubleup());
console.log(cnt2.doubleup());
console.log(cnt2.doubleup());

console.log("----");

//cnt1の内部カウンタをリセットする
cnt1.reset();

//出力確認(2回目)
console.log(cnt1.doubleup());
console.log(cnt1.doubleup());
console.log(cnt1.doubleup());
console.log(cnt1.doubleup());

console.log(cnt2.doubleup());
console.log(cnt2.doubleup());
console.log(cnt2.doubleup());
console.log(cnt2.doubleup());

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

----
2
4
8
16
2
4
8
16
----
0
0
0
0
32
34
128
256

最初の8行では、生成したオブジェクト「cnt1」と「cnt2」に対して、カウントアップ(2ずつ加算)する処理がされて出力されます。
その後、cnt1に対してreset()メソッドを呼んでいるので、
後半の8行では、cnt1についてはまた最初の値からカウントアップされています。

生成したオブジェクトが別な場合は、内部の変数が同じでも、共に影響しない形でアクセスできていることがわかります。

クロージャのシンプルな例として、上記のような関数内への変数の取り扱い方があるので、基本として抑えておくのが良いかと思います。

クロージャにはまだ別な書き方があるので、次回のブログとして取り上げようと思います。

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

Posted コメントするカテゴリー: javascript

概要

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

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

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

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

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

関数プロパティ

Posted コメントするカテゴリー: javascript

概要

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」については、関数プロパティに定義された値が呼び出されているので、単純に文字列が連結された結果が出力されています。

グローバル変数との違い

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

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

Posted コメントするカテゴリー: javascript

概要

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の関数の引数の型の扱いについて

Posted コメントするカテゴリー: javascript

概要

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

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

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

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

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

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

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

test1("abc");

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

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

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

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

Posted コメントするカテゴリー: javascript

概要

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

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

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

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

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

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として出力されます。

可変長の引数リスト

Posted コメントするカテゴリー: javascript

概要

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

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

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プロパティは匿名関数にて、自分自身を再帰的に呼び出せます。

引数と仮引数

Posted コメントするカテゴリー: javascript

概要

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

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として解釈されます。

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

Posted コメントするカテゴリー: javascript

概要

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

関数の入れ子について

Posted コメントするカテゴリー: javascript

概要

関数の入れ子について、まとめてみます。
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の中からアクセスされてインクリメントされている為です。

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

関数の定義について

Posted コメントするカテゴリー: javascript

関数の定義

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が返されます。

関数

Posted コメントするカテゴリー: javascript

概要

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

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

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

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

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

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

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

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

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

Posted コメントするカテゴリー: 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」と出力されます。

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

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

Posted コメントするカテゴリー: javascript

概要

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

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

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

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

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

ES2015(ES6)の配列メソッド

Posted コメントするカテゴリー: javascript

概要

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

forEach

forEachは前のブログ投稿にも出てきました。
配列test1に対して、全ての要素を加算し、最終的な合計値を出力しています。
次のようなコードを書きます。

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

出力結果

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

indexOfの例

const testArray = [1, 2, 3, 4, 5];
const elementToCheck = 3;

if (testArray.indexOf(elementToCheck) !== -1) {
    console.log('配列内に存在する');
} else {
    console.log('配列内に存在しない');
}

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

Posted コメントするカテゴリー: 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の多次元配列

Posted コメントするカテゴリー: javascript

概要

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

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

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

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

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

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

//要素の全てを回して処理
for (let 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次元以上の配列も応用次第で活用することができます。

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

Posted コメントするカテゴリー: javascript

概要

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

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の関数型プログラムに発展することができ、それはまた別な投稿で取り上げたいと思います。

要素の追加と削除

Posted コメントするカテゴリー: 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プロパティについて

Posted コメントするカテゴリー: javascript

概要

配列の長さについて試してみます。
配列の長さは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から埋まっていない配列

Posted コメントするカテゴリー: javascript

概要

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

特徴

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

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

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

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

Posted コメントするカテゴリー: javascript

概要

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」が配列に格納されている動きになります。