JavaScriptの言語仕様を勉強していくことにした。
いい技術書に巡り合ったこともあり、それなりに理解できるようにはなったが、まだまだ身についてはいない。
あくまでも、技術書の説明を読めば理解できる、というレベルに過ぎない。
これでは実際のコーディングに役立てることは出来ないし、開発中に詰まる度に、調べ直さなきゃいけない。
「読めば分かる」と「理解している」は、かなり距離がある。この距離を埋めていく。
ES5に準拠した内容を学んでいく。
本当はES2015(ES6)を学んだほうがいいのかもしれないが、ES6を体系的にまとめた入門書はまだ見当たらない。
それに、ES2015についての様々な情報は、ES5の内容を理解していることを前提にしているものが多い。
基礎を疎かにしないためにも、背伸びせずES5から学ぶことにした。
そのほうが、スムーズにES2015に移行でき、結果的に早いと思う。歯抜けのまま、理解が曖昧なまま進んでいくのは、よくない。
今回は、this
の基礎について。
2016.9.1追記
this
についてはこちらも参照。
ES2015の関数(アロー関数、this、残余引数など)
this
this
は、その関数を呼び出しているオブジェクトを示す。
グローバルスコープで呼び出した場合は、グローバルオブジェクトを示す。
function sayNameForAll(){ console.log(this.name); }; var person1 = { name: 'Tom', sayName: sayNameForAll, }; var person2 = { name: 'John', sayName: sayNameForAll, }; var name = 'Hoge'; person1.sayName(); // Tom person2.sayName(); // John sayNameForAll(); // ブラウザではHoge, Node.jsだとundefined
sayNameForAll()
の中で、this
を使っている。
この関数をperson1
のメソッドとして呼び出した場合、this
はperson1
を指す。
同様に、person2
のメソッドとして呼び出した場合は、this
はperson2
を指す。
そのため、それぞれのオブジェクトのname
が表示されることになる。
また、値がHoge
であるグローバル変数name
を宣言している。
この場合、sayNameForAll()
をグローバルスコープで呼び出した際、「グローバルオブジェクトのname
プロパティ」であるHoge
が表示される。
しかし、これはブラウザの場合のみ。Node.jsだとundefined
となってしまった。
そこで、グローバルオブジェクトそのものを表示してみると、以下のような結果になった。
console.log(this); // ブラウザではWindowオブジェクト, Node.jsだと空のオブジェクト function myFunc(){ console.log(this); }; myFunc(); // ブラウザではWindowオブジェクト, Node.jsだと謎のオブジェクト
ブラウザにおける挙動は予想通りだが、Node.jsの挙動はよく分からない。いずれ別の機会に調べる。
call()
JavaScriptの関数はオブジェクトであるため、関数自身がメソッドを持っている。
その中には、this
を操作できるメソッドもいくつかある。
これらは関数のみが持つメソッドなので、関数以外のオブジェクトが実行しようとすると当然エラーになる。
まずはcall()
。
第一引数に指定したオブジェクトを、this
として、関数を実行する。第二引数は、そのまま関数の引数となる。
function sayNameForAll(label){ console.log(label + 'の名前は、' + this.name); }; var person1 = { name: 'Tom' }; var person2 = { name: 'John' }; var name = 'Hoge'; sayNameForAll.call(this, 'global'); // globalの名前は、Hoge (Node.jsの場合はHogeではなくundefinedになってしまう) sayNameForAll.call(person1, 'person1'); // person1の名前は、Tom sayNameForAll.call(person2, 'person2'); // person2の名前は、John sayNameForAll.call(name, 'name'); // nameの名前は、undefined sayNameForAll.call('文字列', 'name'); // nameの名前は、undefined // undefinedの名前は、Hoge ブラウザの場合 // undefinedの名前は、undefined Node.jsの場合 sayNameForAll.call();
引数を指定しなかった場合、グローバルオブジェクトがthis
になるようだ。
その際のブラウザとNode.jsの挙動の差は、先程と同じ。
また、第一引数にプリミティブ型を与えても、エラーにはならない。
上記の例だと文字列が格納されているname
や、文字列そのものを与えているが、エラーにはならず動作する。恐らく、ラッパーオブジェクトが生成されるのだろう。
だが当然、name
プロパティは持っていないので、undefined
となる。
call()
を使えば、関数をそれぞれのオブジェクトにメソッドとして登録する必要がない。
必要なときに、call()
を使って呼び出せばいいからだ。
apply()
apply()
は、call()
とまったく同じ。
違いは、第二引数が配列になり、この配列に必要な引数を全て格納するということだけ。
以下のコードは、call()
のときと全く同じ結果になる。
function sayNameForAll(label){ console.log(label + 'の名前は、' + this.name); }; var person1 = { name: 'Tom' }; var person2 = { name: 'John' }; var name = 'Hoge'; sayNameForAll.apply(this, ['global']); // globalの名前は、Hoge (Node.jsの場合はHogeではなくundefinedになってしまう) sayNameForAll.apply(person1, ['person1']); // person1の名前は、Tom sayNameForAll.apply(person2, ['person2']); // person2の名前は、John sayNameForAll.apply(name, ['name']); // nameの名前は、undefined sayNameForAll.apply('文字列', ['name']); // nameの名前は、undefined // undefinedの名前は、Hoge ブラウザの場合 // undefinedの名前は、undefined Node.jsの場合 sayNameForAll.apply();
bind()
ES5で追加された機能。
bind()
は、this
の値を最初に固定して関数を作る。
第一引数に指定したものが、this
となる。
この方法で作られた関数は、今後どのような文脈で呼ばれても、this
の値は最初に設定したもののまま、変わることがない。
function sayNameForAll(label){ console.log(label + 'の名前は、' + this.name); }; var japanese = { name: 'Ichiro' }; var american = { name: 'Bob' }; var sayNameForJ = sayNameForAll.bind(japanese); // sayNameForJにおいては、thisはjapaneseを指すようになる sayNameForJ('日本人'); // 日本人の名前は、Ichiro var sayNameForA = sayNameForAll.bind(american); // sayNameForAにおいては、thisはamericanを指すようになる sayNameForA('アメリカ人'); // アメリカ人の名前は、Bob american.sayName = sayNameForJ; // americanのメソッドになっても、sayNameForJのthisはjapaneseのまま american.sayName('アメリカ人'); // アメリカ人の名前は、Ichiro
また、第二引数以降の引数は、作成される関数の引数として固定される。
新しく作成した関数を使用する際に与えられた引数は、arguments
の末尾に追加されていく。
function sayNameForAll(label){ console.log(label + 'の名前は、' + this.name); console.log(arguments); }; var japanese = { name: 'Ichiro' }; var american = { name: 'Bob' }; var sayNameForJ = sayNameForAll.bind(japanese, '日本人'); sayNameForJ(); // 日本人の名前は、Ichiro // ["日本人"] var sayNameForA = sayNameForAll.bind(american, 'アメリカ人', '中国人'); sayNameForA('ロシア人'); // アメリカ人の名前は、Bob // ["アメリカ人", "中国人", "ロシア人"]