JavaScriptの復習
しばらく生JSとまともに向き合っていなかったため、マスターズなるセミナに参加し、再勉強してきました。
その展開と備忘録です。
EcmaScript6の勉強にもなったので有益でした。
論理値について
true | false |
---|---|
0以外の値 | 0 |
1文字以上の文字列 | undefined |
オブジェクト({}も可) | null |
0がfalseになるので、要件次第では気をつけないとね。
オブジェクトの文法
var hoge = new Object();
or
var obj = {};
プロパティへのアクセスは
hoge.fuga
※ var hoge = {fuga: "fuga"};とした場合
or
hoge['fuga']
※ var hoge = {fuga: "fuga"};とした場合
typeof / instanceof
typeof
データの型を調べる
オブジェクトはほとんど「object」にサマられる。
instanceof
どのコンストラクタによって生成されたインスタンスなのかを判定する。
「なんの」というよりは、「このインスタンスだよね?」ってのをtrue/falseで返却する。
var ary = new Array(1,2,3);
console.log(ary instanceof Array);
→ trueが返る。
ラッパーオブジェクト
プリミティブ型を内包するオブジェクト
console.log("hoge".length);
上記はプリミティブであるにもかかわらず、オブジェクトのprototypeが保有するlengthAPIにアクセスが可能。
これは"hoge"文字列プリミティブを内包するStringオブジェクトとして変換されるため。
関数宣言
- 関数宣言
function hoge (str) {
alert(str);
}
hoge("hello");
上記の記載方法は、スクリプトの実行前に関数が評価される。
そのため、宣言の前で関数をコールしてもエラーとならず、下記のように正常にコールできる。
hoge("hello");
function hoge (str) {
alert(str);
}
- 関数式
var hoge = funciton(str) {
alert(str);
};
hoge("hello");
無名関数(匿名関数)をhoge変数に入れる。
このケースは宣言前にコールはできず、undefinedとなる。
- 即時関数
(function(str) {
alert(str);
})("hello");
無名関数を宣言して、その場で実行している。グローバルを汚さない手法。
EcmaScript6から追加される、関数周りの新仕様
EcmaScript6を試したい人はBABELを使うと良い。
EcmaScriptで記載したコードを5ベースにconvertしてくれる。
取り急ぎ、どのようにconvertされるか見たい人はTry it outで試してみると面白いと思う。High compliancyにチェックを入れてから試すこと。
- デフォルト引数
function hoge(i, y=10) {
console.log(i); // 5
console.log(y); // 10
}
hoge(5);
PHPにあるようなデフォルト引数と同じ仕様。指定がなければ初期値を仮引数に指定した値で設定する。
- 可変長引数
Javaにような可変長も導入された。
見たままです。
複数指定した引数がargsに配列として格納されます。
function hoge(...args) {
for (var hoge in args) {
// anything
}
}
hoge(1,2,3,4,5);
- アロー記法
var hoge = function(x) {
return x * 2;
};
上記は
var hoge = (x) => {return x * 2; };
に置き換えられるし、さらに引数が1つなら、()を省略して、処理がreturnのみなら
var hoge = x => x * 2;
と書ける!!
かちょえー。
スコープ
JSのスコープは「関数内かどうか」しかない!!
ただし、varを付け忘れたら、その変数はグローバルになってまう。
function hoge() {
a = 123;
}
alert(a); // 123
上記はstrictモードにすれば、チェックできる。
function hoge() {
'use strict'
a = 123; // エラー!!
}
あと、EcmaScript6からletが追加されたので、
関数内かどうかのみってのは実はそうでもなくなった。
例えば、以下は従来のvarを用いたグローバル変数の例
if (true) {
var hoge = "hogehoge";
}
alert(hoge); // hogehogeがアラートされる
これを
if (true) {
let hoge = "hogehoge";
}
alert(hoge); // undefined
とすることで、極所的に変数スコープを絞ることが可能となり、undefinedとなる。
変数の巻き上げ
=ホイスティング。
以下の記述は一見すると、グローバル変数のhogeが展開され、「グローバルと表示されそう」ですが、
var hoge = "グローバル";
function fuga() {
alert(hoge);
var hoge = "ローカルスコープ";
}
fuga();
下記に置き換えられるため、
var hoge = "グローバル";
function fuga() {
var hoge;
alert(hoge);
hoge = "ローカルスコープ";
}
fuga();
と内部で暗黙的に展開される(=巻き上げ)ため、「undefinedとなる(localスコープのhogeにアクセス。けど未定義。)」
なので、変数の宣言は、慣例としてスコープの先頭で行うようにすること。
prototype
prototypeとして扱いたい関数は、頭文字を大文字にするのが慣例
インスタンスはnewで生成される。
var Animal = function(){}; // 関数
var animal = new Animal(); // インスタンス
インスタンスはObject。prototypeオブジェクトは関数(Function)型
var Animal = function(){}; // prototypeオブジェクト
var animal = new Animal(); // インスタンス
alert(typeof Animal); // function
alert(typeof animal); // object
なぜ、インスタンスは「object型なのか」
var Animal = function(_name){
name: _name
};
はnewされる際に、内部で
var Animal = function(_name){
var this = {};
this.name = _name
return this;
};
としてる。つまり、オブジェクトを暗黙的にreturn しているため、object型となる。
prototypeメソッドによる共通化
アニマルオブジェクトをライオンさんとうさぎさんがインスタンス化します。
var Animal = function(_name) {
this.name = _name;
this.bark = function() {
alert(name + "!!");
};
}
var lion = new Animal("ライオン");
lion.bark(); // ライオン!!
var usagi = new Animal("うさぎ");
usagi.bark(); // うさぎ!!
barkメソッドはprototypeを利用して、以下のように共通化できます。
var Animal = function(_name) {
this.name = _name;
}
Animal.prototype.bark = function(){
alert(this.name + "!!");
};
var lion = new Animal("ライオン");
lion.bark(); // ライオン!!
var usagi = new Animal("うさぎ");
usagi.bark(); // うさぎ!!
lionとusagiは、インスタンスが暗黙的に内部で持つ「proto」プロパティを参照し、
prototypeプロパティを参照しにいく。そのため、prototypeのbarkメソッドをコールできる仕組みとなる。
継承
prototypeを使用して、以下のように継承を行う。
// 親
var Animal = function(_name) {
this.name = _name;
};
Animal.prototype.bark = function() {
alert(this.name + "!!");
};
// 子
var Lion = function(_hoge) {
this.hoge = _hoge;
};
// 継承
Lion.prototype = new Animal("アニマル");
var lion = new Lion("らいおん");
lion.bark(); // らいおん!!
となる。ライオンオブジェクトはbarkメソッドを所有していないが、Animalのprototpeを継承して、自分が所有しているかのように親のメソッドを継承して使用できる。
prototypeチェーン
以下の順番でprototypeを辿っていくので、prototypeチェーン
- 自身のインスタンス内に該当プロパティがあるか調べる
- prototypeオブジェクトのprototype内を調べる
- 親オブジェクトのprototype内を調べる
オーバライド
オーバーライドは先ほどの継承の例で、子供がprototype内のメソッドを再宣言するだけ。
// 親
var Animal = function(_name) {
this.name = _name;
};
Animal.prototype.bark = function() {
alert(this.name + "!!");
};
// 子
var Lion = function(_hoge) {
this.hoge = _hoge;
};
// 継承
Lion.prototype = new Animal("アニマル");
// オーバーライド
Lion.prototype.bark = function() {
alert(this.name + "??");
};
var lion = new Lion("らいおん");
lion.bark(); // らいおん??
EcmaScript6からのclass構文
可読性が上がった。従来の糖衣構文であるため、内部では従来のprototpe構文に置き換えられる。
BABELでみるとわかりやすい。
class Animal {
bark(name) {
alert(name);
}
};
class Lion extends Animal {
bark() {
super.bark("lion");
}
};
var lion = new Lion();
lion.bark(); // Lion
thisについて
<前提>
・オブジェクトの外のthisはWindowオブジェクト
※Windowは一番親のオブジェクト。
alert(this instanceof Window);
・thisを読み解く時は、どこに記載されているかよりも、誰が実行しているかを重点的に見る
メソッドと関数とthis
- メソッド
オブジェクトが内包するfunction
var hoge = {
disp: function() {
alert("hello");
}
};
hoge.disp();
- 関数
オブジェクト外のfunction
function disp() {
alert("hello");
}
disp();
- this
関数内のthisとメソッド内のthisでは、結果が異なる
▼メソッド
メソッド内のthisはそのオブジェクト自身になる。
var hoge = {
disp: function() {
alert(this);
}
};
hoge.disp();
上記のthisは「hogeオブジェクト」になる。
▼関数
関数内のthisはWindowとなる。
function disp() {
alert(this instaneof Window);
}
disp();
上記はtrueが表示される。
関数参照による関数呼び出し時のthis
var hoge = {
disp: function() {
alert(this instanceof Window);
}
};
var fuga = hoge.disp; // functionオブジェクトを渡す
fuga(); // こうすると関数呼び出しになる。
上記は、メソッドの中のthisに見える(「どこにthisがあるか」に着目すると、確かにメソッドの中)だが、functionオブジェクトを実行せず、参照をfuga変数に渡して、fugaとして関数が実行されるため、このケースのthisはWindowとなり、trueが表示される。
インスタンスとthis
newされたオブジェクトはインスタンスとなる。
インスタンス内のthisはインスタンス自身となる。
var Animal = function(_name) {
this.name = _name; // thisは生成されたインスタンス自身となる
};
var lion = new Animal("ライオン");
alert(lion.name);
そのため、上記のthisはnew Animal("ライオン");自身となり、「ライオン」が表示される。
prototypeメソッド内のthis
prototypeメソッド内のthisは、メソッドを実行したインスタンス自身
var Animal = function() {
this.name = "アニマル";
};
Animal.prototype.show = function() {
alert(this.name); // thisはlionとなる(showメソッドを実行したインスタンス自身)
};
var lion = new Animal();
lion.show();
コールバックとthis
コールバック内のthisはコールバック関数を実行するオブジェクトに依存する。
なので、setTimeoutで時間差実行した際のthisには注意が必要。
setTimeoutで実行するオブジェクトはWindowであるため、thisもWindowとなる。
var hoge = {
show: function() {
alert(this instanceof Window);
}
};
hoge.show(); // これだとfalse(= thisはhogeオブジェクト)
setTimeout(hoge.show, 1000); // これだと、thisはWindowなのでtrueになる
addEventListenerのthis
ボタン等のコンポーネントへのイベントリスナー内thisはコンポーネント自身となる。
function func() {
alert(this.toString);
}
var btn = document.getElementById("button").addEventListener("click", func);
上記のthisはidがbuttonのButtonコンポーネントをさす。
call/applyとthis
簡単に言うと、thisを任意のオブジェクトに置き換えられる術
var obj1 = {
name: "A",
showName: function() {
alert(this.name);
}
};
var obj2 = {
name: "B"
};
obj1.showName(); // thisはobj1になる
obj1.showName.call(obj2); // thisはobj2になる!!
【Aオブジェクト】.【Aオブジェクトのメソッド】.call【任意のオブジェクト】で、
Aオブジェクトのメソッド内のthisが、通常呼び出しだとAオブジェクトになるはずが、【任意のオブジェクト】に置き換わる。
※applyは引数が複数あるときに、配列になるだけしか違いがないので割愛。
🌟活用例
var obj = {
"0" : "aaa",
"1" : "bbb",
"2" : "ccc",
length: 3
};
alert(Array.prototype.join.call(obj, "/"));
joinメソッドはArrayオブジェクトの持ち物。
objはObjectオブジェクトなので、join("/")は使えないが、callメソッドでthisをobjに置き換えているため、joinメソッドをobjで使えるようにしている。
bindとthis
setTimeoutで実行するthisがWindowになるときに便利。
var myObj = {
message: "こんにちは",
show: functin() {
alert(this.message);
}
};
setTimeout(myObj.show, 1000); // thisはundefinedになる。Windowなので
// だけど〜
setTimeout(myObj,show.bind(myObj), 1000); // こうすると、thisはmyObjになるので、こんにちはが表示される
クロージャについて
変数のスコープ
JavaScriptのスコープはfunctionの中か、それ以外(グローバル)かしかない(ES6のletは除外)。
var a = 123; // グローバルスコープ
function hoge() {
var a = 1234; // ローカルスコープ
}
クロージャの役割
変数の状態を保持するための仕組み
var hoge = (function() {
var a = 0;
return function() {
alert(++a);
};
})();
hoge(); // 1
hoge(); // 2
hoge(); // 3
クロージャの作り方
- 関数を入れ子にする
- 内側の関数から、外側の関数のローカル変数を参照する
- 外側の関数が完了したのち、内側の関数がメモリ上に残っている状態にする
内側の関数が外側の変数を参照しつづけるため、GCが走らずにローカル変数の状態を保持できる仕組みとなっている。
よくあるクロージャ例
- イベントリスナーでの間違った使用例
var btuns = document.getElementByName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i],addEventListener("click", function() {
this.innerHTML = i + 1;
});
}
この結果は、最初にループ文がbtnsのlength分だけ回りきってしまい、
iは最終的に3になる。
そのあと、ユーザーがクリックして、イベントが発行されるが、その時点ではすでにiが3になっているため、4がボタンのvalueに設定されることになる。
- イベントリスナーでの正しい使用例
var btuns = document.getElementByName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i],addEventListener("click", (function(x) {
return function() {
this.innerHTML = x + 1;
};
})(i));
}
もしくは、
var btuns = document.getElementByName("btn");
for (var i = 0; i < btns.length; i++) {
btns[i],addEventListener("click", (function(x) {
return func(x);
)(i);
}
// クリックした際のイベント内容
function func (x) {
// anything
}