サーチ…


備考

スコープは、変数が存在し、同じスコープ内の他のコードからアクセスできるコンテキストです。 JavaScriptは主に関数型プログラミング言語として使用できるため、実行時のバグや予期しない動作を防ぐのに役立ちますので、変数と関数の範囲を知ることは重要です。

varとletの違い

(注: letを使用するすべての例はconstでも有効です)

varはJavaScriptのすべてのバージョンで利用可能ですが、 letconstは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"

ijforループでのみ宣言されているため、その外側で宣言されていないことに注意してください。

他にもいくつかの重要な違いがあります。

グローバル変数宣言

一番上のスコープ(関数やブロックの外側)では、 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はすでに宣言されています

yvarで宣言されている場合も同様です。

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が定義されていません

ホイスト

varlet両方で宣言された変数は持ち上げられます。差がで宣言された変数のことである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
5

ECMAScript 5のstrictモードでは、関数が匿名で呼び出された場合、 thisundefinedです。

(function () {
    "use strict";
    func();
}())

これは出力されます

undefined

コンストラクタの呼び出し

newキーワードを持つコンストラクタとして関数が呼び出されると、 thisは構築されているオブジェクトの値をとります

function Obj(name) {
    this.name = name;
}

var obj = new Obj("Foo");

console.log(obj);

これによりログに記録されます

{name: "Foo"}

矢印関数の呼び出し

6

矢印の関数を使用する場合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

適用と呼び出し構文と呼び出し。

applycallすべての関数のメソッドは、それがためにカスタム値を提供することができthis

function print() {
    console.log(this.toPrint);
}

print.apply({ toPrint: "Foo" }); // >> "Foo"
print.call({ toPrint: "Foo" }); // >> "Foo"

上で使用された両方の呼び出しの構文が同じであることがわかります。つまり、署名は似ています。

しかし、関数を扱い、スコープを変更しているので、関数に渡される元の引数を維持する必要があるため、使用法には少しの違いがあります。 applycall両方のサポートは、次のようにターゲット関数に引数を渡します。

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関数には多くのことがあります

  1. objthis値として使用されます
  2. 引数を関数に転送する
  3. 値を返します

バインドされた呼び出し

すべての関数のbindメソッドでは、特定のオブジェクトに厳密にバインドされたコンテキストを使用して、その関数の新しいバージョンを作成することができます。関数をオブジェクトのメソッドとして強制的に呼び出すことは、特に有用です。

var obj = { foo: 'bar' };

function foo() {
    return this.foo;
}

fooObj = foo.bind(obj);

fooObj();

これはログに記録されます:

バー



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow