ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScriptのスコープとクロージャの詳細な説明

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

小云云
小云云オリジナル
2018-02-09 13:33:271274ブラウズ

スコープとクロージャは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 までご連絡ください。