プログラマとして、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 変数を宣言する場合、宣言できるのは 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 つの関数内で関数を呼び出すことはできますが、ある関数は他の関数のスコープにアクセスできません。
次の例では、 Second は変数 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 つの用途があります:
副作用の削減
プライベート変数を作成する
クロージャを使用して副作用を制御する
関数が値を返すときに何かを実行すると、通常、いくつかの副作用が発生します。副作用は、Ajax 呼び出し、タイムアウト、さらには console.log 出力ステートメントなど、さまざまな状況で発生する可能性があります。
当你使用闭包来控制副作用时,你实际上是需要考虑哪些可能会混淆代码工作流程的部分,比如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 这一变量。因此,它也被成为特权函数。
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のスコープとクロージャの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。