7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaScriptにおけるスコープとクロージャ

Posted at

グローバルスコープ

グローバルオブジェクト

グローバル変数やグローバルプロパティ、グローバルオブジェクトは、異なるスコープからでもアクセスすることができる。

JavaScriptにおいて、グローバルスコープで変数や関数を定義するとき、JavaScriptで元々定義されているwindowオブジェクトのプロパティやメソッドとして扱われる。

var myString = 'abc';
var myFunction = function(){};
console.log('myString' in window); //出力: true
console.log('myFunction' in window); //出力: true

グローバル関数

グローバル関数はどこからでも呼び出すことができる。

・decodeURI(encodeURI) - 引数として渡したURLエンコードされた文字列をデコードし、その文字列を返す。encodeURI()でエンコードされる文字のみがデコードされる。

・decodeURIComponent(encodeURI) - 引数として渡したURLエンコードされた文字列をデコードし、その文字列を返す。encodeURIComponent()でエンコードされる文字のみがデコードされる。

・encodeURI(uri) - 引数として渡した文字列にURLエンコードを行い、その文字列を返す。ただし、英数字といくつかの記号文字のエンコードは行わない。

・encodeURIComponent(uriComponent) - 引数として渡した文字列にURLエンコードを行い、その文字列を返す。ただし、英数字といくつかの記号文字のエンコードは行わない。

・eval(x) - 引数として渡した文字列をJavaScriptとして実行する。セキュリティホールとなりうるため、利用は推奨されない。

・isFinite(number) - 引数として渡した数値が実数かどうかを評価し、trueもしくはfalseを返す。引数には関数実行前にNumber()と同等の変換が行われる。

・isNaN(number) - 引数として渡した数値がNaNかどうか評価し、trueもしくはfalseを返す。引数には関数実行前にNumber()と同等の変換が行われる。そのため、実際には数ではないもの(true,null.'',' 'など)もfalseとして判定してしまう。

・parseFloat(string) - 引数として渡した文字列を数値に変換する。Number()とは文字列の変換方法が異なる。

・parseInt(string, radix) - strに渡した文字列を、radixに渡した底の値をもとに整数値に変換する。Number()とは文字列の変換方法が異なる。

グローバル変数とグローバルプロパティの違い

グローバル変数はvar演算子を使って定義されるもので、グローバルプロパティはvarを使わないで定義される。グローバルプロパティはdeleteで削除可能だがグローバル変数はdeleteで削除することができない。

確認用コード

var a = 10; //グローバル変数
b = 100; //グローバルプロパティ

delete a; //グローバル変数はdeleteで消去できない
delete b; //グローバルプロパティはここで削除される

console.log(a); //10
console.log(b); //bは削除されたのでエラーとなる

グローバル変数は関数外で定義するが、グローバルプロパティは、どのスコープからでも定義することができる。いいかえると、関数内でvarを使わずに定義したものはグローバルプロパティ扱いとなる。

var foo = function(){
    var bar1 = 'a'; //関数内スコープ
    bar2 = 'b'; //グローバルプロパティ
}();

try{
  console.log(bar1);
} catch(e) {
  console.log("Can't access bar1"); //関数内スコープの変数はアクセスできない
}

try{
  console.log(bar2);
} catch(e) {
  console.log("Can't access bar2"); //グローバルプロパティは異なるスコープでもアクセス可能
}

関数内で変数を定義するときは、必ずvarを使うべきである。

スコープチェーン(静的スコープ)とクロージャ

JavaScriptは変数を参照するときに、同じ関数内に目的の変数が定義されていないかを探す。見つからなかった場合、今度は親関数の中を探す。それでも見つからない場合はさらに親の関数内を探し、最終的にはグローバルスコープまで探しにいく。最後に見つからなかった場合は、undefinedを返す。
このように変数を参照することを、"スコープチェーンをたどる"という。スコープチェーンの検索は最初に発見した値を返す。したがって同じ名前をもった変数がスコープチェーンの上位にあっても、その変数が参照されることはない。(これをマスキングという)

クロージャとは「スコープチェーンに存在する変数への参照を保持している関数」である
クロージャの例を以下に示す

var countUpFromZero = function(){
  var count = 0;
  return function(){
    return ++count;
  };
}();

console.log(countUpFromZero());
console.log(countUpFromZero());
console.log(countUpFromZero());

クロージャは値ではなく参照を保持している。以下の例では、1 2 3と出力したいのだが失敗している。


var logElementNumber = function() {
  var funcArray = [];
  var i;
  for(i = 0; i < 3; i++){
    funcArray[i] = function(){ //クロージャ
      console.log(i);
    }
  }
  return funcArray;
}();

logElementNumber[0](); //出力 3 3回目のi++でiの値が3になる。クロージャのiは変わりゆくiを常に参照している。
logElementNumber[1](); //出力 3
logElementNumber[2](); //出力 3

クロージャを使わずに実装


var logElementNumber = function() {
  var funcArray = [];

  var func = function(n){
    return function(){
      console.log(n);
    };
  };

  for (i = 0; i < 3; i++) {
    funcArray[i] = func(i);
  }
  return funcArray;
}();

logElementNumber[0](); //出力 0 
logElementNumber[1](); //出力 1
logElementNumber[2](); //出力 2

以下の例では、nは値が代入されているのに対しiは参照を渡しているのを確認することができる


var logElementNumber = function(len){
  var funcArray = [];
  var i;
  var func = function(n) {
    return function(){
      console.log(n, i);//この場合は、nがこの関数のスコープでiが親関数のスコープ
    };
  };
  for(i = 0; i < len; i++){
    funcArray[i] = func(i);
  }
  return funcArray;
};//即時実行を行わない

var myArray1 = logElementNumber(3);
var myArray2 = logElementNumber(5);

myArray1[1]();//出力:1 3
myArray1[2]();//出力:2 3
myArray2[1]();//出力:1 5
myArray2[2]();//出力:2 5

ほかにjavascriptの仕様として重要なものがある。スコープチェーンは関数実行時ではなく関数定義時に決められるということだ。以下はグローバルスコープで宣言されているクロージャ関数だ。

var a = 1;
var func = function(){ console.log(a)};

(function(f){
   var a = 100;
   f();
})(func);

リファレンス

開眼!Javascript(第5章,第7章)

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?