AngularJSはよく利用しているのですが、JavaScriptについて体系的に勉強したことが無かったので、基本的な事を再確認し、備忘としてメモ書きしてまとめています。
※目次をクリックすると目次の下部にコンテンツが表示されます。
- 1.JavaScript言語の概要
- 2.JavaScriptの関数の基本
- 3.JavaScriptの変数と型の基本
- 4.JavaScriptのオブジェクトの基本
- 5.JavaScriptの等価演算子と型変換
- 6.JavaScriptの配列
- 7.JavaScriptのundefinedとnull
- 8.JavaScriptのプロトタイプ継承
- 9.JavaScriptのスコープに関する注意点
- 10.JavaScriptのスコープチェーンとクロージャ
- 11.JavaScriptの自動型変換
- 12.JavaScriptの即時関数(IIFE)
・読み込まれてインタプリタまたはエンジンによって実行。
コンパイル言語は、コンパイラによって読み込まれ実行ファイルに変換される。
・インタプリタがある環境であればどこでも実行できる。
●Cに似た構文
●オブジェクト指向言語
大半のオブジェクト指向言語と異なり、継承モデルがクラスベースではなく、プロトタイプベース。
●第一級関数(first-class function)を持つ
JavaScriptの関数はオブジェクトで、自身のプロパティ、メソッドを持ち、他の関数を引数として受け渡したり、変数に割り当てる事が出来る。
●動的プログラミング言語
変数の型とそのeval()メソッドなど。
●動的型付け(dynamic typing)で弱い型付け(weakly typed)
・JavaScriptの変数は、実行時に型の判定を行い、オペランドに異なる型が混在する場合は、JavaScript内のルールに基づいて決められる。
●標準の実装
ECMA-262
function myFunc(arg1, arg2) {
:
};
2)関数を実行
myFunc(“apple”, “orange”);
・JavaScriptは動的型付け言語で、引数の型を定義する必要がない。
・定義した引数の数と実行時に指定した引数の数は一致しなくてもよい。
指定しなかった引数は、”undefined”が代入される。
定義より多く指定した場合は、余分な引数は無視される。
・同じ名前の関数は定義できず、ポリモーフィズムは利用できない。
同じ名前の関数を定義した場合、後に定義した関数によって置き換えられてしまう。
3)結果をリターンする関数の例
function myFunc(name) {
return (“Hello ” + name + “.”);
};
リターンする結果の型を定義する必要はない。
4)関数式、無名関数
var myFunc = function() {
// function body
}
・関数を変数に代入。この変数は関数オブジェクトになる。
・関数名がない、無名関数を定義出来る。
5)AngularJSでオブジェクトが関数か確認する方法
angular.isFunction(object);
・JavaScriptは動的型付け言語なので、明示的に型を指定しない。
実際に変数に設定した値に基づいて自動的に型が決定される。後で型を変換することは出来る。
・varキーワードを使って定義。
・関数内に定義した変数はローカル変数で、関数内でのみ使用できる。
・script要素内で定義した変数はグローバル変数で、同じHTML文書内の他のscript要素内からもアクセスできる。
・基本的な型としてstring、number、booleanがある。
2)boolean
・treuとfalseのどちらかの値。
3)string
・シングルクォートまたはダブルクォートで囲んで定義する。
4)stringに関するAngularJSのメソッド
angular.isString(object)
引数が文字列かどうか確認
angular.lowercase(string)
引数を小文字に変換
angular.uppercase(string)
引数を大文字に変換
5)number
・整数、浮動小数点数
6)numberに関するAngularJSのメソッド
angular.isNumber(object)
引数が数値かどうか確認
var myData = new Object();
myData.name = “・・・”;
myData.address = “・・・”;
2)オブジェクトリテラル
オブジェクトとそのプロパティをオブジェクトリテラルの書式を使って定義出来る。下記のように波括弧で囲んで記述する。
var myData = { name: "・・・", address: "・・・" };
3)関数をメソッドとして使用
関数をオブジェクトに追加する事ができ、オブジェクト上で定義された関数をメソッドと呼ぶ。
var myData = { name: "Adam", address: "sunny", printMsg: function() { console.log("ようこそ!" + this.name + "さん。"); } };
・メソッドからオブジェクトで定義されたプロパティにアクセスする場合は、thisキーワードを使用しなければならない。
4)オブジェクトに値を設定
①方法1
myData.name = “・・・”;
②方法2
myData[“name”] = “青木”;
var propName = “name”;
myData[propName] = “青木”;
5)オプジェクト内のプロパティにループ処理でアクセス
①for
for (var pt in myData) { console.log("プロパティ名:" + pt + "プロパティ値: " + myData[pt]); }
②AngularのforEach
angular.forEach(myData, function (value, key) { console.log("プロパティ名:" + key + "プロパティ値:" + value); });
6)既存のオブジェクトにプロパティ、メソッドを追加、削除
①追加
var myData = { name: "・・・", address: "・・・" };
myData.age = “・・・”;
myData.printMsg = function() {
・・・
}:
②削除
var myData = { name: "Adam", address: "sunny", printMsg: function() { console.log("ようこそ!" + this.name + "さん。"); } }; delete myData.name; delete myData["address"]; delete myData.printMsg;
7)オブジェクトが指定したプロパティを持つかチェック
下記のように プロパティ名 in オブジェクト でオブジェクトが指定したプロパティを持つかチェックできます。結果は、true、falseでリターンされます。
var myData = { name: "・・・", address: "・・・" }; var chkName = "name" in myData; var chkAge = "age" in myData;
8)AngularJSでオブジェクト内のプロパティとメソッドを他のオブジェクトにコピー
○構文
angular.extend(dst, src);
・extendメソッドは、dstオブジェト内のプロパティとメソッドはそのまま維持する。保持せずにコピーしたい場合は、angular.copyメソッドを使用する。
9)AngularJSでオブジェクトかどうかチェック
angular.isObject(value);
(1)等価演算子 ==、===
1)==
・対象の二つの値が等しい場合はtrue。
・対象の二つのオペランドを同じ型に変換してから値を比較する。
例)
5 == “5” はtrue。
2)===
・二つの異なる型の場合はfalse。
例)
5 === “5” はfalse。
・オブジェクト以外の場合は値を型を含めた値を比較するがオブジェクトの場合は、参照を比較する。従って同じ属性値を持っていても異なるオブジェクトはfalse。
var person1 = { name: “佐藤”,age: 25};
var person2 = { name: “佐藤”,age: 25};
person1 === person2 はfalse。
(2)型変換
1)数値演算の+と文字列の結合の+
・数値と文字列に+演算子を適用した場合は、文字列の結合が優先。
“1” + 2 は3ではなく、12。2が文字列”2″に型変換されて結合される。
2)数値から文字列への型変換
・toString():10進数
・toString(2)
・toString(8)
・toString(16)
例)
var n = 17;
n.toString();→”17″
n.toString(2);→”10001″
“0” + n.toString(8);→”021″
“0x” + n.toString(16);→”0x11″
・toFixed(n)
・toExponetian(n)
・toPrecision(n)
例)
var n = 123456.789;
n.toFixed(0);→”123457″
n.toFixed(2);→”123456.79″
n.toExponential(3);→”1.235e+5″
n.toPrecision(4); // “1.235e+5”
n.toPrecision(7); // “123456.8”
3)文字列から数値
・Number(str)、parseInt(str)、parseFloat(str)
・宣言するときに、配列の要素数を指定する必要がない。JavaScriptがリサイズしてくれる。
・配列の要素の型を指定する必要がない。異なる型の要素が混在できる。
例1)
var demoAry = new Array();
demoAry[0] = 32;
demoAry[1] = “東京都新宿区”;
demoAry[2] = true;
例2)
var demoAry = [32, “東京都新宿区”, true];
2)オブジェクトが配列であるかチェック
angular.isArray(value)メソッドでチェックできる。
3)配列の要素にアクセス
①JavaScriptのforループ
for (var i = 0; i < demoAry.length; i++) { console.log("インデックス" + i + ": " + demoAry[i]); } ②AngularJSのforEachメソッド
angular.forEach(demoAry, function (value, key) {
console.log(key + “: ” + value);
});
4)配列に関するメソッド
①concat(otherArray) 配列をリターン
引数で指定した配列の中身を結合
②join(separator) 文字列をリターン
配列内のすべての要素を引数で指定した区切り文字を使って文字列に変換。
③pop() オブジェクトをリターン
スタックと同様の方法で配列の最後の要素から値を削除して、その値をリターン。
④push(item)
スタックと同様の方法で配列の最後に要素を追加。
⑤unshift(item)
配列の最初に要素を追加
⑥reverse()
配列の要素の並び順を反転する。
⑦slice(start,end) 配列をリターン
指定した範囲の配列要素をリターン。
⑧sort() 配列をリターン
配列内の要素をソート。
⑨splice(index, count) 配列をリターン
indexで指定した要素からcountで指定した個数の要素を削除
1)undefined
値が割り当てられていない変数を読もうとした時や存在しないオブジェクトの属性を読もうとした時にundefinedがリターンされる。
例)
var myData = { name: "佐藤", age: 25 }; console.log(myData.address);
2)null
・値がないということを明示的に定義したい場合にnullを割り当てる。
例)
var myData = { name: "佐藤", age: 25 address: null };
3)undefined、nullであるかどうかチェック
①if !オペレーターでチェック
undefined、nullであるかどうかチェック
var myData = { name: "佐藤", age: 25 address: null }; if (!myData.address) { →true if (!myData.gender) { →true
②angular.isDefined()メソッドでチェック
定義済みかどうかチェック。定義済みかどうかをチェックするのでnullであっても定義済みであればtrue。
angular.isDefined(myData.address) →true
angular.isDefined(myData.gender) →false
・JavaScriptはオブジェクト指向言語だが、他の多くの言語と異なり継承がクラスではなくプロトタイプを基に行われる。
・JavaScriptはクラスをサポートせず、オブジェクトのみ存在。
オブジェクトは他のオブジェクトから初期化する。
・JavaScriptの継承は、すべてのオブジェクトが持つ”prototype”という名前の属性を通して処理される。
“prototype”属性は、その親のオブジェクトから継承されたすべての属性、メソッド、そのprototype自身への参照を持つ。
・あるオブジェクトの属性またはメソッドにアクセスすると、JavaScriptはそのオブジェクトのローカルコピーから探し始め、存在しない場合はprototypeを探す。そのprototypeに存在しない場合は、さらにその上位のprototypeを探し、順にプロトタイプチェーンをたどっていく。
・プロトタイプの属性、メソッドを上書きする事が出来る。上書きするとそのポイントで探索が完了するのでそれより上位へのプロトタイプチェーンの探索が終了する。上書きしたポイントより下位のオブジェクトにはその上書きした内容が継承される。
・あるオブジェクトの属性、メソッドを変更すると、そのオブジェクトを継承した子オブジェクトには、そのオブジェクトが初期化された後の変更であっても変更が反映される。子オブジェクトのprototypeは親オブジェクトの属性とメソッドを参照しているため。
・どのグローバルオブジェクトのprototypeであっても変更する事が出来る。もちろん危険なので非推奨。
●プロトタイプ継承の例
var myParent = { a: 10, b: 50 } var myChild = Object.create(myParent); var myGrandChild = Object.create(myChild); alert(myGrandChild.a); // 10が表示 //親を変更すると子にも反映される。 myParent.a = 20; alert(myGrandChild.a); // 20が表示 alert(myChild.a); // 20が表示 //親に属性を追加すると子にも反映 myParent.c = "hello"; alert(myChild.c); // "hello"と表示 alert(myGrandChild.c); //"hello"と表示
1)関数レベルのスコープ
・グローバル変数はプログラム内のどこからでも参照できる変数のことで、ローカル変数は関数内で宣言された変数のことであり、宣言された関数の内部からしか参照できない。
・JavaScriptはC言語のようなブロックレベルのスコープではなく、関数レベルのスコープ。
関数内で定義された変数は、if文やforループなどのブロック内でも参照できる。
2)暗黙的なグローバル変数
ローカル変数を宣言する場合は、varを使って明示的に宣言する。varを使わずに宣言された変数は、グローバル変数として扱う。
3)変数の巻上げ(hoisting)
JavaScriptの変数は、関数内のいかなる場所で宣言されたとしても、その関数の先頭で宣言されたのと同じように動作する。
下記例にあるように関数の下の方で変数を宣言し値を割り当てたとしても、実際には関数の先頭で行われるので注意する。
(問題のある記述)
var mytest = "global"; //グローバル変数。どこからでも参照できる。 function testfunc() { alert(mytest); //"global"ではなく、"undefind"と表示 var mytest = "local"; alert(mytest); //"local"と表示 } testfunc();
(上記プログラムは下記プログラムと同等)
var mytest = "global"; function testfunc() { var mytest; //変数の巻上げで、関数の先頭で宣言される。 alert(mytest); //グローバルではなく、ローカルのmytestを参照。 mytest = "local"; alert(mytest); //"local"と表示 } testfunc();
・引数以外の変数を実行時の環境ではなく、自身が定義された環境(レキシカルスコープ、静的スコープ、構文スコープ)において解決する。
・JavaScriptの関数オブジェクトの内部では、関数のコードだけではなく、定義時の変数のレキシカルスコープを解決するのに必要なスコープチェインへの参照を持っている。
・この関数オブジェクトと変数、レキシカルスコープを解決するのに必要なスコープチェーンをクロージャと呼ぶ。
●スコープチェーンとは?
・JavaScriptにはグローバル変数と関数内で定義された変数であるローカル変数の2種類がある。
・JavaScriptの関数はオブジェクトで関数内にネストして関数を持つ事ができる。
・ネストされた関数の場合、ある変数がどのスコープの変数かを解決する際、子関数から親関数、グローバルへと順にその変数がどこで定義されたか確認していき、最初にマッチした段階で解決される。これがスコープチェーン。
●クロージャの例
function prntFnc(myName) { var prtString = "Hello " + myName; //ローカル変数 function chldFnc() { alert(prtString); } return chldFnc; } var prntVar = prntFnc("Tom"); // prntVarは関数。 prntVar(); //"Hello Tom"をアラート
①親関数を実行 var prntVar = prntFnc(“Tom”)
・親関数prntFncを実行し、子関数をchldFnc()を定義して変数prntVarに割当て。
・子関数chldFnc()では、変数prtStringを参照している。この変数のスコープは定義時のスコープチェーンで解決される。子関数では定義されていないので、スコープチェーンをたどると親関数で定義されていて、ここで解決される。
②子関数を実行 prntVar()
・親関数を実行してリターンされた子関数を実行。
・C言語などでは、関数が実行されてしまうとその関数で定義された変数はその時点でメモリから開放されて利用できなくなってしまう。
上記例では、親関数で定義されたprtStringは親関数実行後に開放されて利用できなくなってしまうので、子関数は期待通りに実行されないことになる。
しかしJavaScriptでは、定義時のスコープチェーンを通して変数を解決できるように実行済みの親関数の変数がそのまま維持されていて利用できる状態になっている。
そしてこのスコープチェーン解決するためにメモリ上に保持されているオブジェクトは、このクロージャが利用されなくなるまでメモリに維持される。
上記現象がメモリリークとなってしまい、古いブラウザでも大きく影響を受ける場合があるので注意する。
(1)基本的な型
・Boolean
trueまたはfalseの変数、式
・Number
整数、浮動小数点。64ビット
・String
・Object
属性とメソッドのコレクション。配列
(2)型の確認
typeofオペレータで確認できる。
(3)プリミティブとオブジェクト
非オブジェクトの単純な値。関数などプリミティブではないものはオブジェクト。
・Boolean
true、falseキーワード
・Null
nullキーワード
・Number
数値は数値プリミティブ。
・String
クォーティングされた文字は、文字列プリミティブ
・Undefined
varキーワードで作成後、値が見割当ての変数。
(4)プリミティブとオブジェクトの変換
・JavaScriptでは、プリミティブとオブジェクトの間で変換する事が出来る。
boolean、number、stringプリミティブはそれに対応するグローバルオブジェクトを持つ。
・下記の場合、stringプリミティブが自動でオブジェクトに変換される
var myString = “hello there” // プリミティブ
alert(myString.length); //Stringオブジェクトに自動で変換され、11と表示
(5)JavaScript内部での暗黙的変換
・JavaScript内部で、toPrimitive()、toNumber()、toBoolean()を使って変換される。(abstractでスクリプトからコールすることは出来ない)
1)toPrimitive()
・オブジェクトをプリミティブに変換。
・valueOf()がプリミティブをリターンしたらそれをリターン。valueOf()でプリミティブに変換出来なかった場合、toStringを使ってプリミティブに変換。それでも変換出来なかったらエラー。
2)toNumber()
入力の型 リターン Boolean 1、+0 Null +0 オブジェクト toNumber(toPrimitive(object)) 文字列 parseInt()と類似。数値、小数点、+-符号以外の場合はNaN Undefined NaN
3)toBoolean()
入力の型 リターン Null false 数値 -0、+0、NaNはfalse。それ以外はtrue オブジェクト true 文字列 emptyはfalse、それ以外はtrue Undefined false
(6)if (Expression)、==を使って比較する際の自動変換のルール
1)if (Expression)
toBoolean()でbooleanに変換して評価
2)==
x y 結果 Null Undefined true 数値 文字列 x == toNumber(y) Boolean Any toNumber(x) == y 文字列、数値 オブジェクト x == toPrimitive(y)
※NullとUndefined
NullとUndefinedの比較はtrueなので、定義済みかチェックする場合は、
if ((arg1 === undefined) || (arg1 === null)) {
ではなく、
if (arg1 == null) {
でよい。
Undefinedは変数なので、比較する際にスコープチェーンをたどる必要があり性能上の負荷が増大する。
JavaScriptでは、関数内に関数を作成し、関数をネストして定義できます。
例)
function hello1(name) { var msgString1 = "こんにちは!" + name + "さん"; function doAlert1() { alert(msgString1); } return doAlert1(); } hello1("佐藤");
●即時関数(Immediately-invoked function expression)
上記サンプルは、下記書式の即時関数を使って記述する事が出来ます。
(書式)
(function(){
/* code */
}());
(function(a, b){
/* code */
})(arg1, arg2);
例)
function hello2(name) { var msgString2 = "こんにちは!" + name + "さん"; (function() { alert(msgString2); }()) } hello2("佐藤");
●ループ内で使用した例
(即時関数を使用しない例)
for (var i = 0; i < 2; i++) { function hello3(name) { var msgString3 = "こんにちは!" + name + "さん"; function doAlert3() { alert(msgString3); } return doAlert3(); } hello3(i); } (即時関数を使用した例) var callbacks4 = []; for (var i = 10; i < 12; i++) { (function(name) { var msgString4 = "こんにちは!" + name + "さん"; var doAlert4 = function() { alert(msgString4); } callbacks4.push(doAlert4); }(i)) } callbacks4.forEach(function(c) { c(); });