node.js 入れるなら nodebrew が超簡単
node をバージョン別に使い分けたりしたかったので、nodebrew なるものを使うことにした(hokaccha++)。
最初は nvm 使ったんだけど、なんかうまくできなかった。
~/.bashrc に設定書いたら、ターミナル起動するたびに nvm use v0.8.19
とか出てくるのがイラっとしたのでやめた。
1. nodebrew のインストール
ターミナルに以下の 1 行コピペするだけ。超簡単。
curl https://raw.github.com/hokaccha/nodebrew/master/nodebrew | perl - setup
2. 環境設定ファイルにパスを通す
いまだに「パスを通す」っていう意味が分かってないけど、これをやらないと、ターミナルを再起動したときに、せっかく入れたツールが使えなくなるっていう認識でおります・・。
私は ~/.bashrc を使ってるので、以下の1行を追記した。
export PATH=$HOME/.nodebrew/current/bin:$PATH
~/.zshrc 使ってる人はそっちに同じもの書けばいいらしい。超簡単。
nodebrew がちゃんと入ってるか確認するために、ターミナルを再起動したら、以下の 1 行を実行してみる。
nodebrew -v
ちゃんと nodebrew 0.6.2
って表示されたのでできてるぽい。
追記
「PATHを通す」っていう意味は、教えてもらったページ読んだら分かった気がする!
- パス(PATH)の確認と設定方法は? - Pocketstudio.jp Linux Wiki
- .bash_profileとか.bash_loginとか.profileとか.bashrcとか.bash_logoutとか多すぎる - action power
OS にデフォルトで用意されていないコマンドを使うには、そのコマンドのプログラムがある場所を教えてあげないと OS は自力で探せないらしい。だから PATH を通してないと、「そんなコマンドないよ」って怒られるぽい。
@hokaccha @cipher さんありがとう〜(∩´∀`)∩
3. node.js をインストール
まず、nodebrew でインストールできる node.js のバージョンを確認するために、以下の 1 行を実行。
nodebrew ls-remote
バージョンの一覧が表示されるけど、この中からどれを入れようって迷う。nodejs.org のトップページで、「Current Version: v0.8.19」って書いてあったので、きっとこれが安定版の最新なんだろうと思い、以下の 1 行を実行してインストール。
nodebrew install-binary v0.8.19
インストールが一瞬で終わる。はやい。
ちなみに、nodebrew install v0.8.19
としてもインストールできるけど、コンパイルを自分でするからインストールにすごく時間がかかる。だから、バイナリからインストールするとよいです。最初、install
コマンドを使ったら、あまりにも時間がかかるし、意味不明なコードがターミナル上で延々と表示されて、壊れたんじゃないかと不安になった。
ちなみに、v0.8.6 以前のバージョンは、バイナリからインストールできないので、install-binary
じゃなくて install
コマンドでやるしかないらしい。
他に入れたいバージョンがあったら同じように、どんどんインストールしていきます。
インストールした node.js のバージョンの一覧は、以下のコマンドで確認できます。
nodebrew ls
node のバージョンを切り替えるのは、以下のコマンドで。
nodebrew use v0.8.19
これで、node.js がインストールできました。簡単すぎてすごい。
今回参考にしたサイト Thx♡
jQuery : キューの stop とfadeIn / fadeOut の謎
超大作は書くのが大変すぎて続かないので、どうでもいいエントリも織り交ぜつつ、ゆるく続けていくことにしました。
今回は、jQuery の勉強でツールチップ書いたときに、謎の現象で困ったのでメモ。
つくったもの
謎の現象その1 - チカチカする
アニメーションする速度より速く、複数のリスト上を通るようにマウスを高速に動かすと、ツールチップが点滅する・・。
.fadeIn() / .fadeOut()
を .show() / .hide()
にすれば大丈夫なんだけど・・ん・・?
「あっ!これ、zudo 本で出た問題だ!」
複数の命令を順番に処理するしくみを、キュー(待ち行列)と言います。
キューに貯まった吹き出しの表示・非表示アニメーションの処理は、マウスの操作に追いつきません。
その結果、マウスの動きを止めてからも順番に処理されることになります。
この問題を解消するために、stop メソッドを利用します。
これは現在進行中のアニメーションを即座に止めるメソッドです。
(Webデザイナーのための jQuery入門 より引用)
ということで、.fadeIn() / .fadeOut()
の前に .stop()
を入れてみることにしたのが次のコード。
謎の現象その2 - ツールチップの色が薄くなる
.stop()
を追加して、一見できたように思えたのですが、また複数リストの上を通るようにしてマウスを動かしてみると、だんだんとツールチップの色が薄くなっていく・・。
ナニコレ/(^o^)\
よく分からなかったので、zudo 本に書いてある通りに、.fadeIn() / .fadeOut()
を使わずに、.animate()
を使うことにした。
完成
やったーー!!でもなんでなのかは、よくわからん。まあいいか。
このあと、オプションでカスタマイズできるように機能追加してゆく予定。
追記
@5509 さんに、.stop()
の引数使えばうまくいくって教えてもらったので、やってみたらできた・・!ありがとうございます。
.fadeIn() / .fadeOut()
の前に .stop(true, true)
を指定しています。
引数の指定が必要な理由
以下のエントリがすごい分かりやすかったです。
.stopが単純にアニメーションを停止させるだけなので、.slideDownの途中で呼び出された場合、その時点のheightが次回以降のheightとして認識されることが原因です。
今回の場合は、opacity の値が次回の opacity として認識されてしまっていたようです。
引数の渡し方の種類
引数の渡し方の違いにより、どう変わるのかのサンプルが分かりやすかったです。
今回の場合は、.stop(true, false)
だとうまくいかなかったので、.stop(true, true)
にしたらうまくいきました。
参考にした本 Thx♡
jQuery プラグインの書き方メモ
勉強のために超簡単な jQuery プラグインを書いてみたら、凄腕 JSer の方々に色々とご指導ご鞭撻いただいたので、忘れないようにメモる。
オレオレ jQuery プラグインテンプレート
(function($) { $.fn.pluginName = function(options) { /** * Option */ options = $.extend({ opt1: 'val', opt2: 'val' }, options); /** * Core */ return this.each(function() { // Process }); /** * Method */ function _methodName() { }; }; }(jQuery)); /** * Execute */ $(function() { $(".foo").pluginName(); });
メソッドのまとまりを、コンストラクタの前と後ろのどっちに書くべきか、未だに迷い続けてる・・。関数リテラルだと前に書かないといけないし、function文なら前でも後ろでもいいぽいけど。
$.fn ってそもそも何なの・・
$.fn
は $.prototype
のエイリアスなのだそうです。
$.fn === $.prototype //=> true
jQuery オブジェクトをつくるときの $('.foo')
とかいうのも、実は jQuery が裏で new をしていて、セレクタを引数にとって jQuery クラスのインスタンスを返していたらしい。これによって jQuery オブジェクトは、jQuery クラスで用意されている便利なメソッドが使えるようになってたわけですね。
なので、$.fn.pluginName
というのは、jQuery クラスのプロトタイプに pluginName というメソッドを追加しているということになります。プラグインを読み込んで $(".foo").pluginName();
とか実行するっていうのも意味が分かった。なるほど。
(function($) {...}(jQuery)); で囲む
別の JavaScript のコードと変数名や関数名がぶつからないように、一番外を (function($) {...}(jQuery));
で囲むことにした。$.fn.pluginName = function(options){...};
の中ですべて完結するなら別に書かなくてもいいんだけど、もし何かを外に出したくなったときのために、あらかじめカプセル化することにしてみたけどしつこいかな・・?
return this; の位置
よくある jQuery プラグインだと、コードの最後に return this;
って書かれてる場合が多いと思うけど、これはコンストラクタの this.each(function() {...});
の前に return をつけて、return this.each(function() {...});
ってしてる。
この方法だと、プラグインの頭の方で this を返していることが分かるメリットがあるそうです。
関数はループの外に
関数が .each
の中にあると、ループ毎に関数が作られてしまうので、ループの外に置くようにします。
子要素から検索する
this を each で回して、その子要素から検索するという場合、書き方がいくつかあって、私はもともと $(".foo", this);
って書いていたのだけど、.find()
を使う方がおすすめらしい。this の書き忘れによる思わぬバグを防ぐというメリットがあるそうです。
.click よりも .on
まだこのへんちゃんと理解できてないけど .click(function(){...});
よりも .on('click', functiin() {...});
の方がいろいろ応用がきくそうです。.on
以外にも、.bind
、.delegate
とかあるけど、jQuery1.7 からは .on
が推奨らしいのでコレ使ってみました。
そのあたりの詳しいことは、下記のページでわかりやすくまとめてあったすごい。
jQuery 1.7の更新内容をまとめたよ。 | Ginpen.com
教えてくれてありがとう!!!
JavaScript の this を理解する
this はインスタンス自身を指す、ただそれだけの話でしょう?
そんなふうに考えていた時期が私にもありました。そう、JavaScript の this に出会うまでは・・。
用語について
私は Java 脳で書いてるので、言葉遣いが JavaScript と若干違う部分があると思います。 下記のようなイメージで言葉を使っています。
用語 | 意味 |
---|---|
クラス | インスタンスオブジェクトの元となる設計図 |
コンストラクタ | クラスのコード部分で、new したときにコンストラクタの内容で初期化する |
インスタンス | クラスを元に実体化したオブジェクト |
メンバ変数 | クラスやインスタンスに属するローカル変数 |
メソッド | クラスやインスタンスに属する命令(関数) |
オブジェクト指向とプロトタイプと this
JavaScript のオブジェクト指向開発では、元になるクラスを new することで、インスタンスを生成します。
同じクラスを元に作られたインスタンス間において、メンバ変数の値はインスタンスにごとに異なり、メソッドは共通で使える場合が多いです。そのため、基本的にはメンバ変数はコンストラクタ部分に、メソッドはプロトタイプ部分に記述します。 (詳しい内容は プロトタイプチェーンをもっと理解する を参照。)
function Bird(voice) { this.voice = voice; } Bird.prototype.sing = function() { console.log(this.voice); } var cock = new Bird("cocka-a-doodle-doo"); cock.sing(); // > "cocka-a-doodle-doo" (this が cock を指している) var duck = new Bird("quack"); duck.sing(); // > "quack" (this が duck を指している)
上記のコードでは、2 箇所に this を使っています。
- Bird クラスのコンストラクタ内
this.voice = voice;
- Bird クラスのプロトタイプ sing メソッド内
console.log(this.voice);
それぞれの this の使われ方について見ていきます。
メンバ変数に値を代入する this
Bird クラスをインスタンス化するときに引数で渡した値を、this を使ってインスタンス自身にセットすることにより、そのインスタンスのメンバ変数として値を代入できます。 このメンバ変数は、インスタンスごとに個別に作られ、それぞれ別々のメモリ領域に保管されます。
このとき this はインスタンス自身(cock, duck)を指しており、それぞれのインスタンスで voice 変数を持っています。
メソッドからメンバ変数を参照する this
クラスのプロトタイプで定義されたメソッドは、そのクラスを元にして作ったインスタンスから実行できます。しかし、実際にはメソッド本体はひとつしかなく、そのメソッドのあるアドレス(番地)を参照しているだけで、インスタンス化するときにメソッドが複製されているわけではありません。
sing メソッドが定義されているのは Bird クラスですが、「this はインスタンス自身を指す」ので、console.log(this.voice);
の this は Bird クラスではなくそれぞれのインスタンスを指しています。
関数内の this が指すもの
関数は独立したオブジェクトであり、関数を定義したオブジェクトに所属するものではありません。 これを念頭に置いておくと、関数内の this が何を指すのかがイメージしやすくなります。
関数を変数に代入して呼び出す場合
次のコードは、obj1 で定義した fn 関数を obj1 から呼び出しているので、this は obj1 を見ています。これは簡単ですね。
var obj1 = { str: "obj1", fn: function() { // obj1 で fn 関数を定義 console.log(this.str); // > "obj1" } } obj1.fn(); // obj1 から fn 関数を呼び出す
次のコードでは、obj2 の fn 変数に、obj1 で定義した fn 関数を代入後、obj2 から fn 関数を呼び出しています。 この場合、this は関数を定義した obj1 ではなく、呼び出した obj2 を見ています。
var obj1 = { str: "obj1", fn: function() { // obj1 で fn 関数を定義 console.log(this.str); // > "obj2" } } var obj2 = { str: "obj2", fn: obj1.fn // obj2.fn に obj1.fn の関数を代入 } obj2.fn(); // obj2 から obj1 で定義した fn 関数を呼び出す
function() { console.log(this.str); }
という関数オブジェクトは obj1 に所属しているわけではなく、独立したオブジェクトとして存在しています。
そもそも、「obj1 で fn 関数を定義」というのは、関数オブジェクトのアドレス情報を obj1.fn に代入しているだけです。 つまり、「obj2.fn に obj1.fn の関数を代入」というのも、obj1.fn に代入されている関数オブジェクトのアドレス情報を obj2.fn に代入しているだけのことです。 obj1.fn に関数が所属しているように見えるけれど、実際は obj1.fn も obj2.fn も、単に独立した関数オブジェクトへのアドレス情報を持っているだけで、どちらも同じことなのです。
こう考えると、関数内の this が、定義されたオブジェクトとは関係なしに、呼び出されたオブジェクトを指すというのも納得できます。
グローバルオブジェクトから呼び出す場合
グローバルスコープの範囲から呼び出された this は、グローバルオブジェクトを指します。
console.log の引数の str に this をつけない場合は、内側の関数の変数オブジェクトから順に名前解決をするため、ローカル変数 str を見にいきます。 (この挙動については、JavaScript のスコープチェーンとクロージャを理解する を参照。)
var str = "global"; function fn() { var str = "local"; console.log(str); // > "local" (自身の関数内のローカル変数を参照) } fn();
this をつけた場合は、this が呼び出し元であるグローバルオブジェクト(window)を指すので、グローバル変数の str を見にいきます。
this.str
が window.str
に置き換わるようなイメージです。
var str = "global"; function fn() { var str = "local"; console.log(this.str); // > "global" (this が window を指す) } fn(); // グローバルスコープの範囲から呼び出す
イベントハンドラで関数を呼び出す場合
ボタンをクリックしたら、メッセージを表示する showMsg 関数を実行するようにしてみます。
HTML は次のとおりです。
<input type="button" id="btn" name="btn" value="Click Me">
まずは、イベントなしの場合の挙動を見てみます。this はグローバルオブジェクトを指しているため、this.msg はグローバル変数の msg を参照します。
var msg = "Hello"; function showMsg() { alert(this.msg); // > "Hello" (this は グローバルオブジェクトを指す) } showMsg();
次に、input 要素のクリックイベントによって showMsg を呼び出してみます。すると this.msg が undefined を返すようになってしまいました。
var msg = "Hello"; function showMsg() { alert(this.msg); // > undefined } window.onload = function() { document.getElementById("btn").onclick = showMsg; }
this が何を指しているか確認するために、alert(this);
に変えてみたところ、object HTMLInputElement
と表示されました。イベントを元に関数を呼び出した場合、関数内の this はイベントの発生元の要素を指します。
var msg = "Hello"; function showMsg() { alert(this); // > [object HTMLInputElement] (this は input 要素を指す) } window.onload = function() { document.getElementById("btn").onclick = showMsg; }
call メソッドと apply メソッド
関数の呼び出し方によって this の指すオブジェクトがこれだけ変わると混乱してしまいますが、JavaScript のプロトタイプには、call と apply というメソッドが用意されています。 call と apply は、this の指すオブジェクトを自由にコントロールできます。
call も apply もほぼ同じ機能ですが、関数に渡す引数の指定方法が違います。 call は引数を個別に指定し、apply は配列でまとめて指定します。
call も apply の話だけで 1 エントリできてしまいそうなので、ひとまず参考サイトを貼っておきます。
考察
メソッド内の this は、メソッド呼び出し時にどのオブジェクトのプロパティとして呼び出されたかによって、this が何を指すのかが変わります。
なぜそんなややこしい仕様になっているのでしょうか。
Java の場合、メソッドはクラスであらかじめ定義しておき、それをインスタンス化することで初めて使えるようになります。インスタンス化した後に変更することはできません。しかし、JavaScript は柔軟な言語のため、あるオブジェクトで定義した関数を、他のオブジェクトの変数に代入して呼び出したりするなど、自由に再利用ができます。
もし this が「関数(メソッド)を定義したオブジェクトを指す」という仕様だとしたら、メソッドをインスタンス間で共有できても、メソッドを呼び出したインスタンスではなく、関数を定義したオブジェクトのメンバ変数を見に行ってしまいます。this が呼び出し元のオブジェクトによって指すものが変わるからこそ、同じメソッドを再利用できる仕組みになっているのだと思います。たぶん。
まとめ
- クラスをインスタンス化するときに引数で渡した値を、this を使ってインスタンス自身にセットすることにより、そのインスタンスのメンバ変数として値を代入できる。
- 関数内の this は、呼び出し時にどのオブジェクトのプロパティとして呼び出されたかによって、this が何を指すのかが変わる。
- イベントハンドラで関数を呼び出した場合の this は、イベントの発生源のオブジェクトを指す。
今回参考にしたサイト & 本 Thx♡
JavaScript のスコープチェーンとクロージャを理解する
前回で JavaScript のスコープの基本がわかったので、今回はスコープチェーンとクロージャを勉強してみました。 Call オブジェクトとクロージャの理解がかなり大変でした・・。
変数オブジェクト
JavaScript で変数の宣言と参照をするということは、変数オブジェクトを読み書きするということです。
- 変数オブジェクトというのは、key と value による変数管理専用のハッシュテーブルのこと
- key が変数名、value が値のセットになっているテーブルで、変数の数だけレコードができるイメージ
- 変数オブジェクトはプログラマが意識することのない、便宜的なオブジェクト
グローバルオブジェクト
JavaScript は、ブラウザが新しいページを読み込んだとき、内部的に新しいグローバルオブジェクトを生成して初期化します。 グローバルオブジェクトとは、グローバル変数やグローバル関数を管理するための、変数オブジェクトです。
つまり、グローバル変数を定義するということは、実際にはグローバルオブジェクトのプロパティを定義するということです。 ブラウザ上では window オブジェクトがグローバルオブジェクトなので、トップレベルで宣言した変数が window オブジェクトのプロパティとして扱えるようになるのも納得できます。
var n = 10; console.log(window.n); // > 10
Call オブジェクト
ローカル変数もグローバルオブジェクトと同じように、関数が実行されたタイミングで変数オブジェクトができます。 それを Call オブジェクトといいます。目に見えないオブジェクトで、プログラマが直接操作することはできません。
関数が実行されたタイミングで、var で宣言された変数と実行時に渡された引数などが、Call オブジェクトに格納されます。
Call オブジェクトは関数実行が終了すると消えます。ローカル変数の記憶領域が関数実行終了とともに破棄されるのは、これが関係しているのかもしれません。
Call オブジェクトに含まれる情報
- その関数内のローカル変数の値
- 関数に渡された引数名とその値
- 引数情報を管理するオブジェクト(arguments オブジェクト)
- this
- 親の Call オブジェクトの場所(アドレス情報)
スコープチェーン
変数 x がグローバル変数とローカル変数で名前がぶつかっている場合、値を調べる手順は次のとおりです(これを名前解決という)。
- チェーン先頭の Call オブジェクトから x というプロパティを探す。
- あればその値を返し、なければチェーンの次の Call オブジェクトを探す。
- あればその値を返し、なければチェーンの最後まで順に Call オブジェクトを探していく。
- グローバルオブジェクトにも見つからなければ、x はこのスコープ内に存在しないので、エラーになる。
チェーンの先頭は一番内側にある Call オブジェクトで、外側に行くにつれて優先度が下がります。この繋がりをスコープチェーンといい、Call オブジェクトに含まれる「親の Call オブジェクトの場所」の情報をたどっていくことによって、スコープチェーンが成り立ちます。
スコープチェーンを理解することによって、次のようなコードで名前解決がどのように行われるかをイメージできるようになりました。
var a = "Global"; var b = "Global"; function outer() { var a = "Outer"; function inner() { var c = "Inner"; console.log(c); // > "Inner" (自身の Call Obj で発見) console.log(b); // > "Global" (inner なし → outer なし → Global Obj で発見) console.log(a); // > "Outer" (inner なし → outer の Call Obj で発見) } inner(); // 呼び出し } outer(); // 呼び出し
クロージャ
クロージャとは、ローカル変数の状態を保持できる関数のことです。 通常ローカル変数は関数の呼び出しが終わると破棄されますが、クロージャはローカル変数を参照し続けられます。
クロージャのつくりかた
ローカル変数を参照している関数内の関数のことをクロージャといい、次のようにつくります。
- 関数の中にさらに関数を作る
- 外側の関数のスコープ内で変数を定義する
- 関数の中で入れ子になった関数を戻り値として返す
- 内側の関数から、外側の変数を参照する
クロージャでカウンタをつくる
function fn(n) { var cnt = n; return function() { // この無名関数がクロージャ return ++cnt; // スコープチェーンにより、外側のローカル変数 cnt を参照 } } var f = fn(0); // fn 関数の実行 console.log(f()); // > 1 console.log(f()); // > 2 console.log(f()); // > 3
上記のコードでは、次のようなことが内部で行われています。
var f = fn(0);
で fn 関数を実行- fn 関数の戻り値は内側の無名関数なので、f には無名関数のアドレスが代入される
- このとき、無名関数が内部的に参照しているローカル変数 cnt も一緒に引っ張ってくる
f();
とは、無名関数を実行すること(ここではreturn ++cnt;
)なので、cnt に渡した 0 がインクリメントされて 1 として返ってくる- 次に
f();
を実行したときも、匿名関数が cnt を参照し続けているため、先ほどの 1 をさらにインクリメントした 2 が返ってくる
内側の関数は、スコープチェーンによって、外側の関数の Call オブジェクトを参照し続けることができます。
独立したスコープチェーン
先ほどのカウンタで、fn 関数の実行を複数行った場合の挙動は次のとおりです。
function fn(n) { var cnt = n; return function() { return ++cnt; } } var f1 = fn(0); // fn 関数の実行 1 var f2 = fn(10); // fn 関数の実行 2 console.log(f1()); // > 1 console.log(f2()); // > 11 console.log(f1()); // > 2 console.log(f2()); // > 12
Call オブジェクトは関数の実行のタイミングで生成されます。よって、f1 と f2 で fn 関数を実行(インスタンス化)することで、別々の Call オブジェクトが生成され、スコープチェーンも別物になります。それぞれのローカル変数 cnt も別々の記憶領域として確保され、独立しています。
fn 関数は、インスタンス化するときに、一度だけ実行されるコンストラクタの役割をしています。 クロージャ自身はメソッド、クロージャから参照されるローカル変数 cnt はプロパティと考えられます。
クロージャによって、データの記憶領域をもったメソッドを作ることができます。
クロージャの使いどころ
クロージャでできることはなんとなく分かったけれど、実際にはどう使えばいいのかと考えていたところ、猿でもわかるクロージャ超入門 6 クロージャの応用例「注文ボタン」 のページで、なるほどという使い方が紹介されていたので、引用させていただきます。
ショッピングカートの「注文する」ボタンで、2重クリックすると2回決済されてしまうというサイトをたまに見かけます。 jQuery + クロージャを使うことで、これを防止してみましょう。
<form name="frm" id="frm"> <input type="submit" value="注文する" /> </form>
$(function(){ var isClicked = false; $('#frm').submit(function(){ if (isClicked) { alert('すでにクリック済みです。'); return false; } isClicked = true; }); });
たしかにクロージャを使って、isClicked ローカル変数の状態を保存しておけば、2 重注文を防げますね。
まとめ
- JavaScript で変数の宣言と参照をするということは、変数オブジェクトを読み書きするということ
- 変数の名前解決は、スコープチェーンによって内側のローカル変数から順に探していく
- スコープチェーンは、生成したインスタンスごとで別物になる
- クロージャによって、ローカル変数の状態を保持できるメソッドをつくれる
参考サイト & 本 Thx♡
JavaScript のスコープを理解する
スコープとは、変数の有効範囲のことで、プログラムのどの場所から参照できるかを決める概念です。
スコープの種類
JavaScript のスコープには、グローバル変数とローカル変数の 2 種類あります。
グローバル変数 | ローカル変数 |
---|---|
関数の外(トップレベル)で宣言した変数 | 関数の中で宣言した変数, 関数の仮引数 |
プログラム全体から参照できる | その関数の中でのみ参照できる |
ブロックスコープは存在しない
Java などの言語では、if や for などの {} で囲まれたブロックごとにもブロックスコープがありますが、JavaScript には存在しません。 JavaScript でどうしてもブロックスコープを使いたい場合は、with 命令を使う方法や、無名関数を定義と同時に呼び出すなどの方法で、擬似的にブロックスコープを作ることは可能です。
補足 : let を使うとブロックスコープがつくれると教えてもらったのですが、ちゃんと調べてないので、調べたらここに追記します!
スコープの役割
- 同じ名前の変数が、意図せず競合することを避ける。
スコープが違う場合、同じ名前の変数であっても別物として扱われる。 - ローカル変数の記憶領域は、関数の実行が終わり次第、破棄される。
グローバル変数はプログラムが終了するまで記憶領域を確保するため、関数内でしか必要ない変数までグローバル変数にした場合、無駄にメモリを消費することになる。
宣言時の var の有無
var を省略して変数宣言をした場合、関数内での宣言であっても、その変数はグローバル変数になってしまいます。 混乱を避けるため、変数宣言の var は必ずつけること。
ローカル変数の有効範囲
ローカル変数は「関数全体で有効」なので、同じ関数内であれば、変数宣言より前のコードからアクセスできます。 これをホイスティング(巻き上げ)と呼びます。
var scope = "Global"; function getValue() { console.log(scope); // > undefined var scope = "Local"; return scope; } console.log(getValue()); // > "Local" (ローカル変数を参照) console.log(scope); // > "Global" (グローバル変数を参照)
getValue 関数の console.log(scope);
は、変数宣言 var scope = "Local";
より前にありますが、ホイスティングによってグローバル変数の scope ではなく、ローカル変数の scope を参照しています。
ただし、変数宣言はホイスティングされますが、代入はされないために undefined が返っています。
ホイスティングとは、JavaScript の内部で、上記コードの getValue 関数を、下記のように書き換えているようなイメージです。
function getValue() { var scope; // ホイスティングで関数宣言だけを先頭に移動 console.log(scope); // > undefined scope = "Local"; // 代入は元の位置にそのまま残る return scope; }
Java などのブロックスコープがある言語では、できるだけスコープを小さくするために、必要になった場所で変数宣言をしますが、JavaScript にはブロックスコープがないので、関数内の先頭に変数宣言を持っていった方が、直感的なスコープと実際のスコープが一緒になるので分かりやすいです。
関数の入れ子
関数を入れ子にして定義すると、それぞれの関数ごとに独自のローカルスコープを持ちます。 内側と外側のローカル変数が同じ名前の場合は、内側のローカル変数が優先されます。
仮引数のスコープ
仮引数もローカル変数です。
var n = 0; function incrementValue(n) { n++; return n; } console.log(incrementValue(n)); // > 1 (ローカル変数を参照) console.log(n); // > 0 (グローバル変数を参照)
incrementValue 関数の仮引数は、グローバル変数と同じ名前ですが、incrementValue 関数内のみで有効で、グローバル変数に影響は与えません。 また、incrementValue 関数の外から変数 n を参照した場合は、グローバル変数を参照します。 スコープの外から、スコープの中の変数を参照することはできません。
同じ名前でも別の変数として扱われるというのはつまり、仮引数の名前を変えた下記のコードと同じことです。
var n = 0; function incrementValue(m) { // n を m にするのと同じ意味 m++; return m; } console.log(incrementValue(n)); // > 1 (ローカル変数を参照) console.log(n); // > 0 (グローバル変数を参照)
引数で値を渡すということは、渡した先の関数のローカル変数に、渡した値をコピーしているようなイメージです。 よって、コピー先(ローカル変数)をいくら変更しても、コピー元(グローバル変数)に影響は与えません。
仮引数で参照型を渡したときのスコープ
参照型とは、値そのものではなく、その値があるメモリ上の番地(アドレス)が格納される変数のことです。 関数 function、配列 array などは参照型なので、これらの変数には実際のオブジェクトが格納されているわけではなく、別の場所にあるオブジェクトのアドレス情報が格納されているだけです。
引数で参照型を渡すということは、オブジェクトそのものをコピーしているわけではなく、アドレス情報をコピーしているだけなので、元々のオブジェクトはひとつのままです。
var ary = [1, 2]; function addValue(ary) { ary.push(3); return ary; } console.log(addValue(ary)); // > [1, 2, 3] (ローカル変数を参照) console.log(ary); // > [1, 2, 3] (グローバル変数を参照)
仮引数で渡されたアドレスにあるオブジェクトの値を関数内で変更すると、グローバル変数からも同じオブジェクトを参照するので、どちらも値が変わっています。仮引数はローカル変数ですが、参照渡しの場合はスコープの話からは外れます。
まとめ
- グローバル変数はプログラム全体から参照でき、ローカル変数はその関数の中でのみ参照できる。
- ローカル変数は var の有無でスコープが変わるので、変数宣言の var は必ずつける。
- ホイスティングによる混乱を避けるため、関数内の先頭に変数宣言をまとめて書く。
- 仮引数もローカル変数のスコープだが、参照渡しの場合は関数外から参照するオブジェクトにも影響を与えるので気をつける。
長くなったので、スコープチェーンとクロージャについては分割して書きます。たぶん今日中に。
→ この話の続き : JavaScript のスコープチェーンとクロージャを理解する
参考にした本 Thx♡
プロトタイプチェーンをもっと理解する
Markdown が使いたくて、はてなダイアリーからはてなブログに引っ越してきました。ちょっと書いては放置してるブログの残骸があちこちに散らかっております。
前回プロトタイプについて勉強してみてふんわり分かった気になったけど、その中で 1 箇所よく分からなかったところがありました。
前回のおさらい
Javaと比較しつつ、JavaScriptのプロトタイプについて調べてみる - tacamy memo の「分からなかったところ」を参照。
前回分からなかったところ
プロトタイプには次のような特徴がありました。
- プロトタイプの中身は、そのオブジェクトを基に生成されたインスタンスから参照される (暗黙の参照)
- 同じオブジェクトを基に複数のインスタンスを生成した場合、どのインスタンスからも同じプロトタイプの中身 (プロパティ) を参照できる
- 複数のインスタンスからひとつのプロトタイプを参照しているだけなので、元のコンストラクタのプロトタイプを変更すると、インスタンスにも反映される
- インスタンスからプロトタイプのプロパティの値を変更すると、別のメモリ領域にそのインスタンス自身のプロパティとして新たに定義されるので、元のコンストラクタのプロトタイプには影響を与えない
javascript のオブジェクト指向とかプロトタイプとか - (゚∀゚)o彡 sasata299's blog の「プロトタイプの再定義とオーバーライド」の項で、7 行目でコンストラクタのプロトタイプのプロパティ値を変更しているのに、それを参照している redBox.color が blue にならないのが不思議だったのです。でも、その点については、次のように理解して解決しました。
- 7 行目は、プロパティの値を変更したのではなく、オブジェクトを再定義し直したのではないか
- 再定義することで、名前は同じ Box.prototype.color だけど、それ以前とは別のオブジェクトとなってしまったのではないか
- その根拠は、13 行目では、プロパティの値を yellow に変更しているだけなので、この場合はインスタンスである blueBox.color も yellow に変わっているから
新たな疑問
ここで、また新たな疑問が発生しました。13 行目でコンストラクタのプロトタイプのプロパティ値を変更しているのに、同じコンストラクタから生成した blueBox.color は連動して変わるのに、redBox.color は値が変わらないのはなぜだろう、と。
今回勉強したこと
変数には、基本型と参照型がある
数値や真偽値などは基本型で、オブジェクトは参照型で、それぞれ次のような違いがあります。
項目 | 基本型 | 参照型 |
---|---|---|
型の分類 | number, string, boolean, undefined, null, |
object, |
変数に格納されるもの | 値そのものを直接格納 | オブジェクト本体が保存されているメモリ上のアドレスを格納 |
変数のコピー | 値そのものがコピーされ、別々の要素ができる | アドレスがコピーがされ、オブジェクトそのものはひとつのまま |
ここで大事なのは、参照型のオブジェクトをコピーしたとき、オブジェクト自身が複製されるのではなく、オブジェクトへのアドレスがコピーされるので、実際のオブジェクトはひとつのまま増えないということです。
コンピュータのメモリ上で、それぞれ値がどのように保存されているかのイメージを図にしてみました。
JavaScript のオブジェクトのコピーは参照のコピー
オブジェクトのコピーは参照(アドレス)のコピーということは、どれだけオブジェクトをコピーしても、オブジェクトの実体はひとつだけということです。
ということは、コピーしたオブジェクトの片方の値を変更すると、もう片方も連動して変わります。
参照しているオブジェクトの実体は同じものなので、当然の結果です。
次のコードを使って、具体的に理解を深めます。
// sample1 var foo = { color: 'red' }; // foo オブジェクトを生成 var bar = foo; // foo の実体への参照を bar にコピー bar.color = 'blue'; // foo オブジェクトの color プロパティの値を変更 console.log(foo.color); // blue console.log(bar.color); // blue // sample2 var foo = { color: 'red' }; // foo オブジェクトを生成 var bar = foo; // foo の実体への参照を bar にコピー bar = { color: 'blue' }; // 新しい別のオブジェクトを定義 console.log(foo.color); // red console.log(bar.color); // blue
sample1 と sample2 のどちらも、var bar = foo;
の時点では、foo と bar どちらも同じ foo オブジェクトへの参照が格納されています。
sample1 の bar.color = 'blue';
は、参照先の foo オブジェクトが持っている color プロパティの値を変更しました。
foo と bar はどちらも同じ foo オブジェクトへの参照なので、そのオブジェクト実体のプロパティの値が変更されれば、連動して値が変わります。
これは、今までの流れに沿って考えると、違和感なく受け入れられます。
次に、sample2 の bar = { color: 'blue' };
についてです。
bar に新しく別のオブジェクトを定義し直しているので、bar に格納されていた foo オブジェクト実体への参照は、別のオブジェクトへの参照に置き換わってしまいました。
この時点で foo と bar は同じオブジェクトの参照ではなく、別々のオブジェクトの参照となってしまったため、bar の変更が foo と連動しなくなりました。
よって、foo.color と bar.color の結果も連動しません。
prototype と __proto__ の違い
プロトタイプチェーンについてもう一度おさらいします。
function Super() {} Super.prototype.a = 10; var Sub = function() {} Sub.prototype = new Super(); var instance = new Sub(); console.log(instance.a); // 10
上記のコードでは、次のことをしています。
- スーパークラス Super のプロトタイプに a プロパティを定義
- サブクラス Sub のプロトタイプで Super のインスタンスを格納
- 変数 instance に Sub のインスタンスを作成して格納
instance から暗黙の参照によって、Super のプロトタイプにある a プロパティの値を表示する流れは次のとおりです。
- instance 自身に a プロパティはあるか探す
- 見つからないので、Sub の prototype を探す
- 見つからないので、Super の prototype を探す
- 見つかったのでここで Super.prototype.a の値を返す
ところでこの暗黙の参照、どうやって実現しているかよく考えてみると疑問です。自分の継承元の prototype をどうやって探してるのかなと。
ここで __proto__ プロパティの出番です。__proto__ プロパティには、次のような特徴があります。
- __proto__ はすべてのオブジェクトが持つプロパティ
- オブジェクトに対象のプロパティが見つからなければ、__proto__ の指すオブジェクトを探す
- __proto__ の値が null になったらそこで探索終了
- Object.prototype (すべてのオブジェクトの元) の __proto__ には null が入っている
- new したときに、コンストラクタの prototype への参照(アドレス)を格納する
親の prototype がある場所(アドレス)を、自動でそれぞれのオブジェクトの __proto__ に代入してくれているイメージです。次のコードで __proto__ へのアドレスの代入のイメージを書いてみましたが、実際にこれを自分で書く必要はありません。
function Super() {} Super.prototype.a = 10; Super.prototype.__proto__ = Object.prototype; // Object.prototype のアドレス情報 var Sub = function() {} Sub.prototype = new Super(); Sub.prototype.__proto__ = Super.prototype; // Super.prototype のアドレス情報 var instance = new Sub(); instance.__proto__ = Sub.prototype; // Sub.prototype のアドレス情報
この __proto__ が持っているアドレス情報をどんどん遡っていくことによって、プロトタイプチェーンが実現できます。
「新たな疑問」を振り返る
ここまで勉強したので、javascript のオブジェクト指向とかプロトタイプとか - (゚∀゚)o彡 sasata299's blog の「プロトタイプの再定義とオーバーライド」の項をもう一度見てみます。
13 行目でコンストラクタのプロトタイプのプロパティ値を変更しているのに、同じコンストラクタから生成した blueBox.color は連動して変わるのに、redBox.color は値が変わらない理由を考えてみます。
- 4 行目で
var redBox = new Box();
をした時点では、redBox.__proto__ には、Box.prototype のアドレス情報が入る(仮に1000番地とする) - 7 行目の
Box.prototype = { color: "blue" };
でオブジェクトが再定義されたため、Box.prototype の値(参照)が、別のオブジェクトへの参照に変わってしまった(仮に2000番地とする) - この時点で、redBox.__proto__ は、Box.prototype の番地を指していないことになる(1000番地のまま)
- 9 行目の
var blueBox = new Box();
によって、blueBox.__proto__ に Box.prototype のアドレス情報が入る(2000番地)
この時点でそれぞれ、次の番地となっています。
- Box.prototype => 2000番地
- blueBox.__proto__ => 2000番地
- redBox.__proto__ => 1000番地
redBox.__proto__ は Box.prototype のプロトタイプチェーンから外れてしまいました。 そのため、redBox から Box のプロトタイプを見に行くことができないため、コンストラクタのプロトタイプの変更が反映されなくなってしまったのです。
やっと意味が分かった!!!長かった・・。
プロトタイプチェーンでの継承の意義
プロトタイプチェーンによって、スーパークラス(親)からサブクラス(子)へ、プロパティやメソッドを継承できます。
こういう例を出すと実務とかけ離れるので、あんまりよくないかもですが、
動物(親) > 犬(親 / 子) > チワワ(子) > ゆに(インスタンス)
みたいなイメージでしょうか。
継承には、次のようなメリットがあります。
- コードの再利用
- 新しいオブジェクトの定義にかかる手間を削減できる
- 継承しなければ、すべてのプロパティを一から定義し直さなければならない
- 継承関係によって複数のオブジェクトをグループ化できる
- プロパティの追加やメソッドのオーバーライドで機能を拡張
- メモリの節約
- 共通部分はひとつのプロパティを見に行くので、メモリ使用量を節約できる
大規模開発でこそ威力を発揮してくれそうですし、設計力が問われそうですね・・(ヽ'ω`)
まとめ
プロトタイプチェーンを使うと、JavaScript でも、Java のクラスベースのオブジェクト指向的なことができるっぽい。 でも Java とは考え方も書き方も違うところがいっぱいあるから気をつけましょう。