ホームページ ウェブフロントエンド jsチュートリアル JavaScriptのスコープとクロージャの詳細な説明

JavaScriptのスコープとクロージャの詳細な説明

Feb 09, 2018 pm 01:33 PM
javascript js 範囲

スコープとクロージャはJavaScriptにおいて非常に重要です。しかし、初めて JavaScript を学んだときは、理解するのが難しかったです。まずはスコープから始めましょう。この記事では主に JavaScript のスコープとクロージャについて紹介し、JavaScript のスコープとクロージャをより深く理解できるようにしたいと考えています。

スコープ

JavaScript スコープは、アクセスできる変数を制限します。スコープには、グローバル スコープとローカル スコープの 2 種類があります。

グローバル スコープ

すべての関数宣言または中括弧の外側で定義された変数は、グローバル スコープ内にあります。

ただし、このルールはブラウザーで実行される JavaScript でのみ有効です。 Node.js を使用している場合、グローバル スコープの変数は異なりますが、この記事では Node.js については説明しません。

`const globalVariable = 'some value'`
ログイン後にコピー

グローバル変数を宣言すると、関数内を含むどこでもそれを使用できます。

const hello = 'Hello CSS-Tricks Reader!'

function sayHello () {
 console.log(hello)
}

console.log(hello) // 'Hello CSS-Tricks Reader!'
sayHello() // 'Hello CSS-Tricks Reader!'
ログイン後にコピー

グローバル スコープで変数を定義できますが、それはお勧めしません。名前の競合が発生する可能性があるため、2 つ以上の変数が同じ変数名を使用します。変数を定義するときに const または let を使用すると、名前の競合があるとエラーが発生します。これはお勧めできません。

// Don't do this!
let thing = 'something'
let thing = 'something else' // Error, thing has already been declared
ログイン後にコピー

変数を定義するときに var を使用すると、2 番目の定義が最初の定義を上書きします。これにより、コードのデバッグが困難になるため、望ましくありません。

// Don't do this!
var thing = 'something'
var thing = 'something else' // perhaps somewhere totally different in your code
console.log(thing) // 'something else'
ログイン後にコピー

そのため、グローバル変数の代わりにローカル変数を使用するようにしてください

ローカルスコープ

コードの特定のスコープで使用される変数は、ローカルスコープで定義できます。これはローカル変数です。

JavaScript には、関数スコープとブロックレベル スコープの 2 種類のローカル スコープがあります。

関数スコープから始めます。

関数スコープ

関数内で変数を定義すると、その変数は関数内のどこでも使用できます。関数の外からはアクセスできません。

たとえば、次の例では、sayHello 関数の hello 変数:

function sayHello () {
 const hello = 'Hello CSS-Tricks Reader!'
 console.log(hello)
}

sayHello() // 'Hello CSS-Tricks Reader!'
console.log(hello) // Error, hello is not defined
ログイン後にコピー

ブロックレベルのスコープ

中括弧を使用して const 変数または let 変数を宣言する場合、この変数は中括弧内でのみ使用できます。

次の例では、hello は中括弧内でのみ使用できます。

{
 const hello = 'Hello CSS-Tricks Reader!'
 console.log(hello) // 'Hello CSS-Tricks Reader!'
}

console.log(hello) // Error, hello is not defined
ログイン後にコピー

関数は中括弧で定義する必要があるため (明示的に return ステートメントとアロー関数を使用しない限り)、ブロックレベルのスコープは関数スコープのサブセットです。

関数の昇格とスコープ

関数を使用して定義すると、その関数は現在のスコープの先頭に昇格されます。したがって、次のコードは同等です:

// This is the same as the one below
sayHello()
function sayHello () {
 console.log('Hello CSS-Tricks Reader!')
}

// This is the same as the code above
function sayHello () {
 console.log('Hello CSS-Tricks Reader!')
}
sayHello()
ログイン後にコピー

関数式を使用して定義された場合、関数は変数スコープの先頭にホイストされません。

sayHello() // Error, sayHello is not defined
const sayHello = function () {
 console.log(aFunction)
}
ログイン後にコピー

ここには 2 つの変数があるため、関数のホイスティングは混乱を引き起こす可能性があるため、機能しません。したがって、関数を使用する前に必ず定義してください。

関数は他の関数のスコープにアクセスできません

異なる関数が個別に定義されている場合、1 つの関数内で関数を呼び出すことはできますが、ある関数は他の関数のスコープにアクセスすることはできません。

次の例では、2 番目は firstFunctionVariable 変数にアクセスできません。

function first () {
 const firstFunctionVariable = `I'm part of first`
}

function second () {
 first()
 console.log(firstFunctionVariable) // Error, firstFunctionVariable is not defined
}
ログイン後にコピー

ネストされたスコープ

関数が関数内で定義されている場合、内側の関数は外側の関数の変数にアクセスできますが、その逆はできません。その効果は字句スコープです。

外部関数は内部関数の変数にアクセスできません。

function outerFunction () {
 const outer = `I'm the outer function!`

 function innerFunction() {
  const inner = `I'm the inner function!`
  console.log(outer) // I'm the outer function!
 }

 console.log(inner) // Error, inner is not defined
}
ログイン後にコピー

スコープの仕組みをイメージすると、ツーウェイミラー(片面シースルーガラス)をイメージできます。中からは外が見えますが、外の人からは見えません。

関数スコープは二面鏡のようなものです。中からは外が見えますが、外からは見えません。

ネストされたスコープも同様のメカニズムですが、より多くの双方向ミラーを持つことと同等です。

多層機能とは、複数の双方向ミラーを意味します。

スコープに関する前の部分を理解すると、クロージャが何であるかを理解できます。

クロージャー

関数内に別の関数を作成する場合、それはクロージャーを作成することと同じです。内部関数はクロージャです。通常、外部関数の内部変数にアクセスできるようにするために、このクロージャが返されるのが一般的です。

function outerFunction () {
 const outer = `I see the outer variable!`

 function innerFunction() {
  console.log(outer)
 }

 return innerFunction
}

outerFunction()() // I see the outer variable!
ログイン後にコピー

内部関数は値を返すため、関数宣言部分を簡素化できます:

function outerFunction () {
 const outer = `I see the outer variable!`

 return function innerFunction() {
  console.log(outer)
 }
}

outerFunction()() // I see the outer variable!
ログイン後にコピー

クロージャは外部関数の変数にアクセスできるため、通常、次の 2 つの用途があります:

  1. 副作用を減らす

  2. プライベート変数を作成する

クロージャを使用して副作用を制御する

関数が値を返すときに何かを実行すると、通常、何らかの副作用が発生します。副作用は、Ajax 呼び出し、タイムアウト、さらには console.log 出力ステートメントなど、さまざまな状況で発生する可能性があります:

function (x) {
 console.log('A console.log is a side effect!')
}
ログイン後にコピー

クロージャーを使用して副作用を制御する場合、コードの動作を混乱させる可能性があるものを実際に考慮する必要があります。 Ajax やタイムアウトなどのプロセス。

物事を明確に説明するには、例を見るとより便利です:

比如说你要给为你朋友庆生,做一个蛋糕。做这个蛋糕可能花1秒钟的时间,所以你写了一个函数记录在一秒钟以后,记录做完蛋糕这件事。

为了让代码简短易读,我使用了ES6的箭头函数:

function makeCake() {
 setTimeout(_ => console.log(`Made a cake`, 1000)
 )
}
ログイン後にコピー

如你所见,做蛋糕带来了一个副作用:一次延时。

更进一步,比如说你想让你的朋友能选择蛋糕的口味。那么你就给做蛋糕makeCake这个函数加了一个参数。

function makeCake(flavor) {
 setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
}
ログイン後にコピー

因此当你调用这个函数时,一秒后这个新口味的蛋糕就做好了。

makeCake('banana')
// Made a banana cake!
ログイン後にコピー

但这里的问题是,你并不想立刻知道蛋糕的味道。你只需要知道时间到了,蛋糕做好了就行。

要解决这个问题,你可以写一个prepareCake的功能,保存蛋糕的口味。然后,在返回在内部调用prepareCake的闭包makeCake。

从这里开始,你就可以在你需要的时调用,蛋糕也会在一秒后立刻做好。

function prepareCake (flavor) {
 return function () {
  setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
 }
}

const makeCakeLater = prepareCake('banana')

// And later in your code...
makeCakeLater()
// Made a banana cake!
ログイン後にコピー

这就是使用闭包减少副作用:你可以创建一个任你驱使的内层闭包。

私有变量和闭包

前面已经说过,函数内的变量,在函数外部是不能访问的既然不能访问,那么它们就可以称作私有变量。

然而,有时候你确实是需要访问私有变量的。这时候就需要闭包的帮助了。

function secret (secretCode) {
 return {
  saySecretCode () {
   console.log(secretCode)
  }
 }
}

const theSecret = secret('CSS Tricks is amazing')
theSecret.saySecretCode()
// 'CSS Tricks is amazing'
ログイン後にコピー

这个例子里的saySecretCode函数,就在原函数外暴露了secretCode这一变量。因此,它也被成为特权函数。

使用DevTools调试

Chrome和Firefox的开发者工具都使我们能很方便的调试在当前作用域内可以访问的各种变量一般有两种方法。

第一种方法是在代码里使用debugger关键词。这能让浏览器里运行的JavaScript的暂停,以便调试。

下面是prepareCake的例子:

function prepareCake (flavor) {
 // Adding debugger
 debugger
 return function () {
  setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
 }
}

const makeCakeLater = prepareCake('banana')
ログイン後にコピー

打开Chrome的开发者工具,定位到Source页下(或者是Firefox的Debugger页),你就能看到可以访问的变量了。

使用debugger调试prepareCake的作用域。

你也可以把debugger关键词放在闭包内部。注意对比变量的作用域:

function prepareCake (flavor) {
 return function () {
  // Adding debugger
  debugger
  setTimeout(_ => console.log(`Made a ${flavor} cake!`, 1000))
 }
}

const makeCakeLater = prepareCake('banana')
ログイン後にコピー

调试闭包内部作用域

第二种方式是直接在代码相应位置加断点,点击对应的行数就可以了。

通过断点调试作用域

总结一下

闭包和作用域并不是那么难懂。一旦你使用双向镜的思维去理解,它们就非常简单了。

当你在函数里声明一个变量时,你只能在函数内访问。这些变量的作用域就被限制在函数里了。

如果你在一个函数内又定义了内部函数,那么这个内部函数就被称作闭包。它仍可以访问外部函数的作用域。

相关推荐:

详解JavaScript作用域和闭包

javascript 词法作用域和闭包分析说明_javascript技巧

深入理解javascript作用域和闭包_基础知识

以上がJavaScriptのスコープとクロージャの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

C言語でのtypedef構造体の使い方 C言語でのtypedef構造体の使い方 May 09, 2024 am 10:15 AM

typedef struct は、構造体の使用を簡素化するために構造体型のエイリアスを作成するために C 言語で使用されます。構造体の別名を指定することで、新しいデータ型を既存の構造体に別名付けします。利点としては、可読性の向上、コードの再利用、型チェックなどが挙げられます。注: エイリアスを使用する前に構造体を定義する必要があります。エイリアスはプログラム内で一意であり、宣言されているスコープ内でのみ有効である必要があります。

Javaで期待される変数を解決する方法 Javaで期待される変数を解決する方法 May 07, 2024 am 02:48 AM

Java における変数の期待値の例外は、変数の初期化、null 値の使用、およびローカル変数のスコープの認識によって解決できます。

JSのクロージャーの長所と短所 JSのクロージャーの長所と短所 May 10, 2024 am 04:39 AM

JavaScript クロージャーの利点には、変数スコープの維持、モジュール化コードの有効化、遅延実行、およびイベント処理が含まれますが、欠点としては、メモリ リーク、複雑さの増加、パフォーマンスのオーバーヘッド、およびスコープ チェーンの影響が挙げられます。

C++ で include は何を意味しますか C++ で include は何を意味しますか May 09, 2024 am 01:45 AM

C++ の #include プリプロセッサ ディレクティブは、外部ソース ファイルの内容を現在のソース ファイルに挿入し、その内容を現在のソース ファイル内の対応する場所にコピーします。主に、コード内で必要な宣言を含むヘッダー ファイルをインクルードするために使用されます。たとえば、標準入出力関数を組み込むための #include <iostream> などです。

C++ スマート ポインター: ライフサイクルの包括的な分析 C++ スマート ポインター: ライフサイクルの包括的な分析 May 09, 2024 am 11:06 AM

C++ スマート ポインターのライフ サイクル: 作成: スマート ポインターは、メモリが割り当てられるときに作成されます。所有権の譲渡: 移動操作を通じて所有権を譲渡します。リリース: スマート ポインターがスコープ外に出るか、明示的に解放されると、メモリが解放されます。オブジェクトの破壊: ポイントされたオブジェクトが破壊されると、スマート ポインターは無効なポインターになります。

C++ での関数の定義と呼び出しはネストできますか? C++ での関数の定義と呼び出しはネストできますか? May 06, 2024 pm 06:36 PM

できる。 C++ では、ネストされた関数の定義と呼び出しが可能です。外部関数は組み込み関数を定義でき、内部関数はスコープ内で直接呼び出すことができます。ネストされた関数により、カプセル化、再利用性、スコープ制御が強化されます。ただし、内部関数は外部関数のローカル変数に直接アクセスすることはできず、戻り値の型は外部関数の宣言と一致している必要があります。内部関数は自己再帰的ではありません。

Vueのletとvarの違い Vueのletとvarの違い May 08, 2024 pm 04:21 PM

Vue では、let と var の間で変数を宣言するときのスコープに違いがあります。 スコープ: var にはグローバル スコープがあり、let にはブロック レベルのスコープがあります。ブロックレベルのスコープ: var はブロックレベルのスコープを作成しません。let はブロックレベルのスコープを作成します。再宣言: var は同じスコープ内の変数の再宣言を許可しますが、let は許可しません。

js の this が指す状況がいくつかあります。 js の this が指す状況がいくつかあります。 May 06, 2024 pm 02:03 PM

JavaScript では、this のポインティング タイプには、1. グローバル オブジェクト、2. 関数呼び出し、4. イベント ハンドラー、5. アロー関数 (this の外側の継承) が含まれます。さらに、bind()、call()、および apply() メソッドを使用して、これが何を指すかを明示的に設定できます。

See all articles