第1回 JavaScriptの基礎を見直す:聞いたら一生の宝,プログラミングの基礎の基礎 |gihyo.jp … 技術評論社 という記事を読みました。
jsってthisのトラップ多いよねーと思いながら読んでいたのですが気になる所があったので検証してみました。
thisの使い分けができていますか?
JavaScriptのthisは呼び出し元によって意味が変わります。
1. グローバルオブジェクトとしてのthis
2. コンストラクタ内のthis
3. メソッドに所属しているthis
4. apply,callから変えられるthis
といった内容の箇所にサンプルコードが載っていて
やりたいこと
下のコードでresizeTouphone.ratio.wを正常に出力したい
var resizeToiphone = { wWidth : document.documentElement.clientWidth, wHeight : document.documentElement.clientHeight, iWidth : 320, iHeight : 568, ratio : { w : this.iWidth/this.wWidth, h : this.iHeight/this.wHeight } }
現在のコードでresizeToiphone.ratio.wを出力するとundefinedになってしまいます。
とあります。
記事内にも書いてあるようにthis
はWindow
なので、this.iWidth
は undefined
かと思いますが、undefined / undefined
はNaN
になったような気がします。
もしかして、この計算ってブラウザで異なるのでしょうか???
※以下Chromeで確認しています。
resizeToiphone.ratio.wはundefinedなのか?
var resizeToiphone = { wWidth : document.documentElement.clientWidth, wHeight : document.documentElement.clientHeight, iWidth : 320, iHeight : 568, ratio : { self: this, // thisが何かちぇっく。 iWidth: this.iWidth, w : this.iWidth/this.wWidth, h : this.iHeight/this.wHeight } } console.log(resizeToiphone.ratio); // -> Object {self: Window, iWidth: undefined, w: NaN, h: NaN}
resizeToiphone.ratio
オブジェクト内のthis
はWindow
、this.iWidth
はundefined
で合ってました。
そして予想通りundefined / undefined
がNaN
なので、resizeToiphone.ratio.w
とresizeToiphone.ratio.h
はNaN
になっているようです。
undefinedの計算とかをしらべてみる
undefined + undefined; // NaN undefined - undefined; // NaN undefined * undefined; // NaN undefined / undefined; // NaN // 数値にしてみる Number(undefined); // NaN parseInt(undefined,10); // NaN undefined - 0; // NaN +undefined; // NaN // 文字列にしてみる String(undefined); // "undefined" undefined.toString(); // TypeError: Cannot read property 'toString' of undefined undefined + ''; // "undefined" // Bool値にしてみる Boolean(undefined); // false !!undefined; // false
ことごとくNaN
です!
さて、元の記事にもどります。
まぁさっきのコードは上手くいかないよってことで下記のような解決策のコードが載っています。
var resizeToiphone = { wWidth : document.documentElement.clientWidth, wHeight : document.documentElement.clientHeight, iWidth : 320, iHeight : 568, ratio : function () { return { w : this.iWidth/this.wWidth, h : this.iHeight/this.wHeight }; } };
他にもコンストラクタ内のthisとして扱う方法もありますが, シンプルに行うのは上記でしょう。
resizeTouphone.ratio.wを正常に出力したいという事でしたので、これ解決になってないような気がします。。。
まずresizeToiphone.ratio
がfunction
ままだと思います。
なので、このままresizeToiphone.ratio.w
とした場合functionオブジェクトのwプロパティが返されるような気がします。Function.w
と同じ?(すみませんここハッキリ理解してるわけじゃないので明言できないのですが...) なので結果はundefined
ではないでしょうか?
resizeToiphone.ratio.w
ではなくresizeToiphone.ratio().w
とすれば、function内のthisはresizeToiphone
になるので値が取得できそうな気がします。取得する方法を書き忘れてたのかな?
試してみましょう。
修正されたコードのresizeToiphone.ratio.wで値は取れるのか?
var resizeToiphone = { wWidth : document.documentElement.clientWidth, wHeight : document.documentElement.clientHeight, iWidth : 320, iHeight : 568, ratio : function () { return { self: this, // 関数内のthisが何になってるかしらべる。 w : this.iWidth/this.wWidth, h : this.iHeight/this.wHeight }; } }; // resizeToiphone.ratio.w console.log(resizeToiphone.ratio); // function() {... typeof(resizeToiphone.ratio); // "function" console.log(resizeToiphone.ratio.w); // undefined console.log(resizeToiphone.ratio.self); // undefined // Function も見てみる console.log(Function); // function Function() { [ native code ] } console.log(Function.w); // undefined // resizeToiphone.ratio().w console.log(resizeToiphone.ratio().w); // 0.xxxx 欲しい値! console.log(resizeToiphone.ratio().self); // -> Object {wWidth: XXX, wHeight: XXX, iWidth: 320, iHeight: 568, ratio: function}
やはり、resizeToiphone.ratio.w
はundefined
でした。
resizeToiphone.ratio().w
と関数を実行して返されるオブジェクトの値を見れば欲しい値が取れています。特にresizeToiphone.ratio()
を実行する度にwWidth
とwHeight
は再計算される訳じゃないのでresizeToiphone.ratio().w
とresizeToiphone.ratio().h
で取れる値はこの変数が実行された時の値のまま一定です。
レスポンシブなサイトで使うとかなら関数のままにしておいて都度再計算させるのが良い様に思いますが、最初に計算しておいて常に固定の値が欲しいのであれば、値を取りに行く度に関数が実行されるので少しアレな気もします。
即時関数にすればいい?
即時関数になってない。みたいなコメントも見たのですが、即時関数にした所で即時関数内のthis
はWindow
のままなのでダメな気がします。
私気になります!
はい。調べます。
var resizeToiphone = { wWidth : document.documentElement.clientWidth, wHeight : document.documentElement.clientHeight, iWidth : 320, iHeight : 568, // ↓ 即時関数にしてみるよ。 ratio : (function () { return { self: this, // 即時関数内のthisが何かしらべる w : this.iWidth/this.wWidth, h : this.iHeight/this.wHeight }; })() }; console.log(resizeToiphone.ratio.w); // NaN console.log(resizeToiphone.ratio); // -> Object {self: Window, w: NaN, h: NaN}
はい。やはり即時関数にしてもthis
はWindow
なのでダメでした。
あ。
即時関数の書き方によってはratio
の値が変わってしまうことがあるので注意が必要です。
過去の記事ですが参考までに
僕ならどうするか考えてみた。
case1
resizeToiphone.ratio.w
は初回に計算しちゃって常に固定値を返せばOKresizeToiphone.ratio.w
とresizeToiphone.ratio.h
だけ取れればOKresizeToiphone.wWidth
などは外から取れなくてOK
var resizeToiphone = (function(d) { var wWidth= d.documentElement.clientWidth, wHeight= d.documentElement.clientHeight, iWidth= 320, iHeight= 568; return { ratio: { w: iWidth/wWidth, h: iHeight/wHeight } }; })(document); console.log(resizeToiphone.ratio); // -> Object {w: 0.XXXXX, h: 0.XXXXXXXX}
case2
- あくまでthisを使いたい
resizeToiphone.wWidth
なども外から取れるようにしたい
var ResizeToiphone = function() { this.wWidth = document.documentElement.clientWidth; this.wHeight = document.documentElement.clientHeight; this.iWidth = 320; this.iHeight = 568; this.ratio = { w: this.iWidth / this.wWidth, h: this.iHeight / this.wHeight }; return this; } var resizeToiphone = new ResizeToiphone(); console.log(resizeToiphone); // -> ResizeToiphone {wWidth: XXX, wHeight: XXX, iWidth: 320, iHeight: 568, ratio: Object} console.log(resizeToiphone.ratio); // -> Object {w: 0.XXXXX, h: 0.XXXXXXXX}
case3
- CLASSみたいに使いまわさないので、
ResizeToiphone
って変数をglobalに定義したくない
よろしい。ならば無記名関数を使ってやる!
var resizeToiphone = (new function(d) { this.wWidth = d.documentElement.clientWidth; this.wHeight = d.documentElement.clientHeight; this.iWidth = 320; this.iHeight = 568; this.ratio = { w: this.iWidth / this.wWidth, h: this.iHeight / this.wHeight }; return this; }(document)); console.log(resizeToiphone); // -> Object {wWidth: XXX, wHeight: XXX, iWidth: 320, iHeight: 568, ratio: Object} console.log(resizeToiphone.ratio); // -> Object {w: 0.XXXXX, h: 0.XXXXXXXX}
キモい!!
少し別件ですが即時関数でnew function()
とする時は即時関数の閉じ方を気をつけないと変数を渡せないというか、エラーになるっぽいです!少しハマりましたw
// 変数を渡せない。というかエラーになるパティーン var a = (new function(w, d) { console.log(w, d); })(window, document); // -> TypeError: object is not a function // 変数を渡せるパティーン var b = (new function(w, d) { console.log(w, d); // Window, #document }(window, document));
即時関数でnew function()
するのは気をつけないとダメなケースがあるので、即時関数の中でnewして返すのが見通しが良さそうな気がします。
var resizeToiphone = (function(d) { var ResizeToiphone = function() { this.wWidth = d.documentElement.clientWidth; this.wHeight = d.documentElement.clientHeight; this.iWidth = 320; this.iHeight = 568; this.ratio = { w: this.iWidth / this.wWidth, h: this.iHeight / this.wHeight }; return this; }; return new ResizeToiphone(); })(document); console.log(resizeToiphone); // -> ResizeToiphone {wWidth: XXX, wHeight: XXX, iWidth: 320, iHeight: 568, ratio: Object} console.log(resizeToiphone.ratio); // -> Object {w: 0.XXXXX, h: 0.XXXXXXXX} /* ↓ var ResizeToiphoneの定義もなくしちゃう */ var resizeToiphone = (function(d) { return new function() { this.wWidth = d.documentElement.clientWidth; this.wHeight = d.documentElement.clientHeight; this.iWidth = 320; this.iHeight = 568; this.ratio = { w: this.iWidth / this.wWidth, h: this.iHeight / this.wHeight }; return this; }; })(document); console.log(resizeToiphone); // -> Object {wWidth: XXX, wHeight: XXX, iWidth: 320, iHeight: 568, ratio: Object} console.log(resizeToiphone.ratio); // -> Object {w: 0.XXXXX, h: 0.XXXXXXXX}
はい。私気になります!で色々と調べてみる事ができて楽しかったです。
やはりjavascriptのthisはトラップが多いので、ぼくはオブジェクトや関数内で使う時は最初にvar self = this;
としてしまって最初のthis
を変数にしておくことが多いです。変数にしておけば関数内に関数を作って実行しする際に元のthis
を使いたい時とかself
という変数になっているので解りやすいし間違いも少なくなると思っています。
CoffeeScriptとか使えばいいじゃん!って?
まぁそうなのですが、元のjavascriptでどうなっているか知っていた方がCoffeeScript使った時にも間違いは少ないと思うの、なのです!
[参考]

JavaScript: The Good Parts ―「良いパーツ」によるベストプラクティス
- 作者: Douglas Crockford,水野貴明
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/12/22
- メディア: 大型本
- 購入: 94人 クリック: 1,643回
- この商品を含むブログ (187件) を見る

1/700 特シリーズ SPOTNo.42 日本海軍軽巡洋艦 北上 DX
- 出版社/メーカー: フジミ模型
- 発売日: 2015/01/24
- メディア: おもちゃ&ホビー
- この商品を含むブログ (1件) を見る