サーチ…
備考
スコープは、変数が存在し、同じスコープ内の他のコードからアクセスできるコンテキストです。 JavaScriptは主に関数型プログラミング言語として使用できるため、実行時のバグや予期しない動作を防ぐのに役立ちますので、変数と関数の範囲を知ることは重要です。
varとletの違い
(注: let
を使用するすべての例はconst
でも有効です)
var
はJavaScriptのすべてのバージョンで利用可能ですが、 let
とconst
はECMAScript 6の一部であり、一部の新しいブラウザでのみ利用可能です 。
var
は、それが宣言されたときに応じて、包含する関数またはグローバル空間にスコープされます。
var x = 4; // global scope
function DoThings() {
var x = 7; // function scope
console.log(x);
}
console.log(x); // >> 4
DoThings(); // >> 7
console.log(x); // >> 4
つまりif
文とそれに類するすべてのブロック構造を "エスケープ"します:
var x = 4;
if (true) {
var x = 7;
}
console.log(x); // >> 7
for (var i = 0; i < 4; i++) {
var j = 10;
}
console.log(i); // >> 4
console.log(j); // >> 10
比較すると、 let
ブロックがスコープされています。
let x = 4;
if (true) {
let x = 7;
console.log(x); // >> 7
}
console.log(x); // >> 4
for (let i = 0; i < 4; i++) {
let j = 10;
}
console.log(i); // >> "ReferenceError: i is not defined"
console.log(j); // >> "ReferenceError: j is not defined"
i
とj
はfor
ループでのみ宣言されているため、その外側で宣言されていないことに注意してください。
他にもいくつかの重要な違いがあります。
グローバル変数宣言
一番上のスコープ(関数やブロックの外側)では、 var
宣言は要素をグローバルオブジェクトに置きます。 let
いません。
var x = 4;
let y = 7;
console.log(this.x); // >> 4
console.log(this.y); // >> undefined
再宣言
var
を使って変数を2回宣言しても、エラーは発生しません(宣言するのと同じですが):
var x = 4;
var x = 7;
let
を使用すると、エラーが発生します。
let x = 4;
let x = 7;
TypeError:識別子
x
はすでに宣言されています
y
がvar
で宣言されている場合も同様です。
var y = 4;
let y = 7;
TypeError:識別子
y
は既に宣言されています
しかし、letで宣言された変数は、ネストされたブロックで再利用することができます(再宣言されません)。
let i = 5;
{
let i = 6;
console.log(i); // >> 6
}
console.log(i); // >> 5
ブロック内では、外部i
にアクセスできますが、内部ブロックにi
let
宣言がある場合は、外部i
にアクセスすることはできず、2番目が宣言される前にReferenceError
がスローされます。
let i = 5;
{
i = 6; // outer i is unavailable within the Temporal Dead Zone
let i;
}
ReferenceError:iが定義されていません
ホイスト
var
とlet
両方で宣言された変数は持ち上げられます。差がで宣言された変数のことであるvar
、それが自動的に(と割り当てられますから、独自の割り当ての前に参照することができundefined
値として)が、 let
、それがない、具体的に起動される前に宣言される変数を必要とすることができます:
console.log(x); // >> undefined
console.log(y); // >> "ReferenceError: `y` is not defined"
//OR >> "ReferenceError: can't access lexical declaration `y` before initialization"
var x = 4;
let y = 7;
ブロックの開始とlet
またはconst
宣言の間の領域はTemporal Dead Zoneと呼ばれ、この領域の変数への参照はReferenceError
を引き起こします。これは、 宣言される前に変数が割り当てられていても発生します 。
y=7; // >> "ReferenceError: `y` is not defined"
let y;
非strictモードでは、 宣言なしで変数に値を代入すると、自動的に変数がグローバルスコープに宣言されます 。この場合、グローバルスコープ内でy
が自動的に宣言される代わりに、 let
は変数の名前( y
)を予約し、宣言/初期化された行の前に変数へのアクセスや代入を許可しません。
閉鎖
関数が宣言されると、その宣言の文脈における変数は、その範囲内に捕捉されます。たとえば、以下のコードでは、変数x
は外側のスコープ内の値にバインドされ、 x
の参照はbar
のコンテキストで取得されbar
。
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
bar(); // prints 4 to console
サンプル出力:
4
スコープをキャプチャするこのコンセプトは、外部スコープが終了した後でも外部スコープから変数を使用して変更できるので興味深いです。たとえば、次の点を考慮してください。
function foo() {
var x = 4; // declaration in outer scope
function bar() {
console.log(x); // outer scope is captured on declaration
}
return bar;
// x goes out of scope after foo returns
}
var barWithX = foo();
barWithX(); // we can still access x
サンプル出力:
4
上記の例では、 foo
が呼び出されると、そのコンテキストが関数bar
取り込まれbar
。だから、返した後も、 bar
まだ変数にアクセスして変更することができx
。コンテキストが別の関数で捕捉されている関数foo
はクロージャと呼ばれます。
私的なデータ
これにより、特定の関数または関数セットにしか見えない "private"変数を定義するなど、興味深いことができます。考案された(しかし普及した)例:
function makeCounter() {
var counter = 0;
return {
value: function () {
return counter;
},
increment: function () {
counter++;
}
};
}
var a = makeCounter();
var b = makeCounter();
a.increment();
console.log(a.value());
console.log(b.value());
サンプル出力:
1 0
makeCounter()
が呼び出されると、その関数のコンテキストのスナップショットが保存されます。 makeCounter()
内のすべてのコードは、実行時にそのスナップショットを使用します。 makeCounter()
を2回呼び出すと、2つの異なるスナップショットが作成され、独自のcounter
のコピーが作成されます。
すぐに呼び出される関数式(IIFE)
クロージャは、多くの場合、すぐに呼び出される関数式を使用して、グローバル名前空間汚染を防ぐためにも使用されます。
直ちに呼び出される関数式 (またはより直感的には、 自己実行型の無名関数 )は、本質的に宣言の直後に呼び出されるクロージャです。 IIFEの一般的なアイデアは、IIFE内のコードにしかアクセスできない別のコンテキストを作成するという副作用を呼び出すことです。
$
jQuery
を参照できるようにしたいとします。 IIFEを使用せずに単純な方法を考えてみましょう。
var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery
次の例では、IIFEを使用して、 $
がクロージャによって作成されたコンテキストでのみjQuery
に確実にバインドされるようにしています。
(function ($) {
// $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution
クロージャの詳細については、Stackoverflowの標準的な答えを参照してください。
ホイスト
ホイストとは何ですか?
ホイストは、すべての変数宣言および関数宣言をスコープの先頭に移動するメカニズムです。しかし、変数の割り当ては元々あった場所でも起こります。
たとえば、次のコードを考えてみましょう。
console.log(foo); // → undefined
var foo = 42;
console.log(foo); // → 42
上記のコードは次のコードと同じです:
var foo; // → Hoisted variable declaration
console.log(foo); // → undefined
foo = 42; // → variable assignment remains in the same place
console.log(foo); // → 42
ホイストのために、上記のundefined
は、実行not defined
結果としてnot defined
れてnot defined
と同じではないことに注意してください。
console.log(foo); // → foo is not defined
同様の原則が機能に適用されます。関数が変数( 関数式 )に代入されると、代入が同じ場所にある間に変数宣言が呼び出されます。次の2つのコードスニペットは同等です。
console.log(foo(2, 3)); // → foo is not a function
var foo = function(a, b) {
return a * b;
}
var foo;
console.log(foo(2, 3)); // → foo is not a function
foo = function(a, b) {
return a * b;
}
関数ステートメントを宣言すると、別のシナリオが発生します。関数宣言とは異なり、関数宣言はスコープの先頭に持ち込まれます。次のコードを考えてみましょう:
console.log(foo(2, 3)); // → 6
function foo(a, b) {
return a * b;
}
上記のコードは、巻き上げによる次のコードスニペットと同じです。
function foo(a, b) {
return a * b;
}
console.log(foo(2, 3)); // → 6
吊り上げるものと持たないもののいくつかの例を以下に示します。
// Valid code:
foo();
function foo() {}
// Invalid code:
bar(); // → TypeError: bar is not a function
var bar = function () {};
// Valid code:
foo();
function foo() {
bar();
}
function bar() {}
// Invalid code:
foo();
function foo() {
bar(); // → TypeError: bar is not a function
}
var bar = function () {};
// (E) valid:
function foo() {
bar();
}
var bar = function(){};
foo();
ホイストの限界
変数を初期化することはできません。単純なJavaScript Hoist宣言では、初期化ではありません。
例:以下のスクリプトは異なる出力を与えます。
var x = 2;
var y = 4;
alert(x + y);
これはあなたに6の出力を与えます。しかし、これは...
var x = 2;
alert(x + y);
var y = 4;
これにより、NaNの出力が得られます。 yの値を初期化しているので、JavaScript Hoistingは起こっていないので、yの値は未定義です。 JavaScriptは、yがまだ宣言されていないとみなします。
したがって、2番目の例は以下と同じです。
var x = 2;
var y;
alert(x + y);
y = 4;
これにより、NaNの出力が得られます。
varの代わりにループを使用する(クリックハンドラの例)
loadedData
れた各loadedData
配列のボタンを追加する必要があるとしましょう(たとえば、各ボタンはデータを表示するスライダである必要がありますが、簡単にするためにメッセージを通知するだけです)。 1つはこのような何かを試みるかもしれません:
for(var i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
しかし、警告する代わりに、各ボタンが
TypeError:loadedData [i]は定義されていません
エラー。これは、 i
のスコープがグローバルスコープ(または関数スコープ)で、ループの後にi == 3
です。私たちが必要とするのは、「 i
の状態を覚えている」ことではありません。これはlet
を使って行うことができます:
for(let i = 0; i < loadedData.length; i++)
jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>")
.children().last() // now let's attach a handler to the button which is a child
.on("click",function() { alert(loadedData[i].content); });
このコードでテストされるloadedData
の例:
var loadedData = [
{ label:"apple", content:"green and round" },
{ label:"blackberry", content:"small black or blue" },
{ label:"pineapple", content:"weird stuff.. difficult to explain the shape" }
];
メソッド呼び出し
オブジェクトのメソッドとしての価値関数を呼び出しthis
、その対象となります。
var obj = {
name: "Foo",
print: function () {
console.log(this.name)
}
}
objのメソッドとしてprintを呼び出すことができるようになりました。 this
はobjになります
obj.print();
こうしてログに記録されます:
フー
匿名呼び出し
関数を無名関数として呼び出すと、 this
はグローバルオブジェクトになります(ブラウザではself
)。
function func() {
return this;
}
func() === window; // true
ECMAScript 5のstrictモードでは、関数が匿名で呼び出された場合、 this
はundefined
です。
(function () {
"use strict";
func();
}())
これは出力されます
undefined
コンストラクタの呼び出し
new
キーワードを持つコンストラクタとして関数が呼び出されると、 this
は構築されているオブジェクトの値をとります
function Obj(name) {
this.name = name;
}
var obj = new Obj("Foo");
console.log(obj);
これによりログに記録されます
{name: "Foo"}
矢印関数の呼び出し
矢印の関数を使用する場合this
、囲んでいる実行コンテキストのからの値を取りthis
(つまり、 this
矢印関数で字句スコープではなく、通常の動的範囲を有しています)。グローバルコード(関数に属さないコード)では、グローバルオブジェクトになります。ここで説明した他のメソッドのどれかから矢印表記で宣言された関数を呼び出すとしても、それはそのままです。
var globalThis = this; //"window" in a browser, or "global" in Node.js
var foo = (() => this);
console.log(foo() === globalThis); //true
var obj = { name: "Foo" };
console.log(foo.call(obj) === globalThis); //true
メソッドが呼び出されたオブジェクトを参照するのではなく、コンテキストがコンテキストをどのthis
継承するかを確認しthis
ください。
var globalThis = this;
var obj = {
withoutArrow: function() {
return this;
},
withArrow: () => this
};
console.log(obj.withoutArrow() === obj); //true
console.log(obj.withArrow() === globalThis); //true
var fn = obj.withoutArrow; //no longer calling withoutArrow as a method
var fn2 = obj.withArrow;
console.log(fn() === globalThis); //true
console.log(fn2() === globalThis); //true
適用と呼び出し構文と呼び出し。
apply
とcall
すべての関数のメソッドは、それがためにカスタム値を提供することができthis
。
function print() {
console.log(this.toPrint);
}
print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"
上で使用された両方の呼び出しの構文が同じであることがわかります。つまり、署名は似ています。
しかし、関数を扱い、スコープを変更しているので、関数に渡される元の引数を維持する必要があるため、使用法には少しの違いがあります。 apply
とcall
両方のサポートは、次のようにターゲット関数に引数を渡します。
function speak() {
var sentences = Array.prototype.slice.call(arguments);
console.log(this.name+": "+sentences);
}
var person = { name: "Sunny" };
speak.apply(person, ["I", "Code", "Startups"]); // >> "Sunny: I Code Startups"
speak.call(person, "I", "<3", "Javascript"); // >> "Sunny: I <3 Javascript"
apply
では、 Array
またはarguments
オブジェクト(配列のような)を引数のリストとして渡すことができますが、 call
では各引数を個別に渡す必要があることに注意してください。
これらの2つのメソッドは、ECMAScriptのネイティブbind
の貧弱なバージョンを実装して、元の関数からオブジェクトのメソッドとして常に呼び出される関数を作成するなど、自由に自由にできるようにします。
function bind (func, obj) {
return function () {
return func.apply(obj, Array.prototype.slice.call(arguments, 1));
}
}
var obj = { name: "Foo" };
function print() {
console.log(this.name);
}
printObj = bind(print, obj);
printObj();
これによりログに記録されます
"Foo"
bind
関数には多くのことがあります
-
obj
はthis
値として使用されます - 引数を関数に転送する
- 値を返します
バインドされた呼び出し
すべての関数のbind
メソッドでは、特定のオブジェクトに厳密にバインドされたコンテキストを使用して、その関数の新しいバージョンを作成することができます。関数をオブジェクトのメソッドとして強制的に呼び出すことは、特に有用です。
var obj = { foo: 'bar' };
function foo() {
return this.foo;
}
fooObj = foo.bind(obj);
fooObj();
これはログに記録されます:
バー