本記事を投稿しとうとしたきっかけ
JavaScriptの勉強をしていて、prototype
でのオブジェクト指向について勉強をやっていた時
this
の挙動がよくわからなくなったため、色々調べました。
その際に調べたthis
について、本記事で色々まとめてみたいと思います。
何か不足している情報などあれば、コメントください。。。
prototypeオブジェクト
よくJavaScriptで、機能ごとにオブジェクトにまとめたいなどあると思います。
その際に必ず、prototype
や、ES6だとclass
に出会うことができると思います。
var UserInfo = function(userName, age, sex) {
this.userName = userName;
this.age = age;
this.sex = sex;
}
UserInfo.prototype = {
toString() {
console.log('名称:' + this.userName);
console.log('年齢:' + this.age);
console.log('性別:' + this.sex);
}
}
var userInfo = new UserInfo('太郎', 20, '男');
userInfo.toString();
// -> 名称:太郎
// -> 年齢:20
// -> 性別:男
「名称」「年齢」「性別」の3つのメンバ変数を持ち、prototype
には、ユーザ情報について表示するtoString
メソッドのあるユーザ情報オブジェクトです。
メンバ変数に注目すると、this
が出現します。
そのthis
で定義された変数は、prototype
で定義したメソッド内(上記ソースだとtoString
メソッド)などで用いることができます。
しかし、以下のソースを実行してみると、予想とは違う挙動になります。
var UserInfo = function(userName, age, sex) {
this.userName = userName;
this.age = age;
this.sex = sex;
}
UserInfo.prototype = {
toString: function() {
console.log('名称:' + this.userName);
console.log('年齢:' + this.age);
console.log('性別:' + this.sex);
},
// 追記
set: (userName, age, sex) => {
this.userName = userName;
this.age = age;
this.sex = sex;
}
}
var userInfo = new UserInfo('太郎', 20, '男');
// 追記
userInfo.set('花子', 30, '女');
userInfo.toString();
// -> 名称:太郎
// -> 年齢:20
// -> 性別:男
あれ?変わらない?
先ほどの追記したソースをみてみると、set
メソッドはアロー関数(Arrow_functions)で記述されています。
勉強していく上で、いちいちfunction
って書くのがめんどくさくなり、アロー関数をたくさん書くようにしたら、上記のようなソースでハマりました。。。
よくわからないまま直そうとした際、とりあえず、アロー関数を普通のfunction
にしてみようと思い、戻してみました。
すると表示結果は自分の思っていたような正常動作になりました。
// ...省略
set: function(userName, age, sex) {
this.userName = userName;
this.age = age;
this.sex = sex;
}
}
var userInfo = new UserInfo('太郎', 20, '男');
userInfo.set('花子', 30, '女');
userInfo.toString();
// -> 名称:花子
// -> 年齢:30
// -> 性別:女
いやー、そんなに長くハマらずに済みました〜。
うん?つまり、アロー関数が悪い?
アロー関数とthis
の相性が悪いのか?っと考え、色々調べてみました。
MDNさんのアロー関数の説明によると
2 つの理由から、アロー関数が導入されました。1 つ目の理由は関数を短く書きたいということで、2 つ目の理由は this を束縛したくない、ということです。
あぁなるほど〜束縛したくないからね〜。
裏側の実装とか、細かいことなどが理解していないと、上記の説明では理解に苦しみます。。。
さらに色々調べてみました。戦いですね。
するといい記事が発見できました。
@mejileben, @takkyunさん、ありがとうございます!
とってもわかりやすかったです!
そもそもthis
というのは
簡潔にいうと、this
というのは
-
this
はfunction
を呼んだ時の.
の前についているオブジェクトを指している
※@takkyunさんの記事引用
らしい。
.
の前に何もなければ、グローバルオブジェクトをthis
は参照するらしい。
例として、以下のソースを見ていただきたい。
var userName = '花子';
var age = 30;
var sex = '女';
var toString = function() {
console.log('名称:' + this.userName);
console.log('年齢:' + this.age);
console.log('性別:' + this.sex);
}
var UserInfo = {
userName: '太郎',
age: 20,
sex: '男',
toString: toString
}
// toStringの . の前はUserInfoです。
// よって、thisはUserInfoを参照するわけです。
UserInfo.toString();
// -> 名称:太郎
// -> 年齢:20
// -> 性別:男
// toStringメソッドをいきなり呼ぶと
// . の前には何もないため、thisはグローバルオブジェクトを参照するわけです。
toString();
// -> 名称:花子
// -> 年齢:30
// -> 性別:女
グローバルオブジェクトについては、知っている前提で話を進めます。
もし、わからない方がいれば
を参照してください。
以下のソースは、グローバル領域とグローバルオブジェクトについてのソースとなります。
// グローバル領域でthisを参照すると、グローバルオブジェクト(Window Object)が出力されます。
console.log(this);
// -> Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
// グローバル領域で変数を定義。
// すると、以下のargはグローバルオブジェクトのメンバ変数として追加される。
var arg = "argument0";
// 以下は全て同じものを参照している。
console.log(arg); // -> argument0
console.log(this.arg); // -> argument0
console.log(window.arg); // -> argument0
なるほど、だんだんわかってきましたぞ。
あ。アロー関数は?
そもそもアロー関数を使用したら、うまくいかなかったというのに、少しthis
の話が長くなってしまいました。。。
それでは、アロー関数でのthis
の挙動はどうなるのでしょうか?
こちらも簡潔にいうと
-
アロー関数式で宣言された関数は、宣言された時点で、
this
を確定(=束縛)させてしまうのです。
※@mejilebenさんの記事引用
こちらも実際にソースをみてみましょう。
var arg = "argument0";
// アロー関数でメソッドを定義
// 定義した時点でthisの参照先は束縛されます。
var func = () => {
console.log(this.arg);
}
// 上記で宣言したメソッドをメンバ変数として追加。
var obj = {
arg: "argument1",
func: func
}
// こちらは、先ほどと変わらず、グローバル領域の変数を参照します。
func(); // -> argument0
// アロー関数で定義した時点でthisの参照先が決定するため
// objを参照せず、その外側の領域(グローバル領域)を参照します。
obj.func(); // -> argument0
いやー、結構わかってきたぞー
this
での挙動についてある程度理解しました。
そこで、自分なりに色々意地悪なソースを考えてみたりして。。。
皆さんも、以下のソースで実際にどのような出力結果となるか考えてみてください!
arg = "arg1";
function consoleLog() {
console.log(this.arg);
}
var obj1 = {
arg: "argument2",
func: consoleLog
}
var obj2 = {
arg: "argument3",
func: () => {
console.log(this.arg);
}
}
var obj3 = {
arg: "argument4",
func: function() {
var arg = "argument5";
var subFunc = () => {
console.log(this.arg);
}
subFunc();
}
}
var obj4 = {
arg: "argument6",
func: function() {
var arg = "argument7";
var subFunc = function() {
console.log(this.arg);
}
subFunc();
}
}
consoleLog(); // -> ???
obj1.func(); // -> ???
obj2.func(); // -> ???
obj3.func(); // -> ???
obj4.func(); // -> ???
ちょっと応用がきいているのではないかと思いますが、考えてみる価値はありそうです!
答えは、Chromeなどの開発者ツールのコンソールに上記ソースをはっつけていただいて、実行すれば答えがわかります!
個人的には、obj4
のfunc
実行時の出力結果が難しいのではないかと思っています。
ただ、this
の最初の説明にあった箇所を思い出せば解けると思います。
どんなにスコープがネストしても、いきなり .
の前にオブジェクトがないfunctionを呼び出したりすると。。。
まとめ
ここまで、説明が足りない箇所もあったかと思われますが、ざっとまとめてみました。
JavaScriptでのthis
は少し複雑だなと感じました。
あとは、何も考えずにアロー関数をバンバン用いるのは危険であることもわかりました。(気をつけます)
this
の操作に慣れると、ソースも安全に組むことができますし、正しいthis
の使い方でコーディングできると思われます。
また、this
を正しく理解して用いると、コード量も減るのではないか?とも感じました。
もっとthis
について、上記以外の複雑なとこがあれば教えてください!
ここまで読んでくれた方、ありがとうございました🙇♂️