Maison  >  Article  >  interface Web  >  Explication détaillée des portées et des fermetures JavaScript

Explication détaillée des portées et des fermetures JavaScript

小云云
小云云original
2018-02-09 13:33:271260parcourir

La portée et la fermeture sont très importantes en JavaScript. Mais quand j’ai appris JavaScript pour la première fois, c’était difficile à comprendre. Commençons par la portée. Cet article vous présente principalement la portée et la fermeture de JavaScript, dans l'espoir de vous aider à mieux comprendre la portée et la fermeture de JavaScript.

Portée

La portée en JavaScript limite les variables auxquelles vous pouvez accéder. Il existe deux types de portée : la portée globale et la portée locale.

Portée globale

Les variables définies en dehors de toutes les déclarations de fonction ou accolades sont dans la portée globale.

Cependant, cette règle n'est valable que dans JavaScript exécuté dans le navigateur. Si vous êtes dans Node.js, les variables dans la portée globale sont différentes, mais cet article ne discutera pas de Node.js.

`const globalVariable = 'some value'`

Une fois que vous avez déclaré une variable globale, vous pouvez l'utiliser n'importe où, y compris dans des fonctions.

const hello = 'Hello CSS-Tricks Reader!'

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

console.log(hello) // 'Hello CSS-Tricks Reader!'
sayHello() // 'Hello CSS-Tricks Reader!'

Bien que vous puissiez définir des variables dans la portée globale, nous vous déconseillons de le faire. Des conflits de noms pouvant survenir, deux variables ou plus utilisent le même nom de variable. Si vous utilisez const ou let lors de la définition d'une variable, vous recevrez une erreur en cas de conflit de nom. Ce n'est pas conseillé.

// Don't do this!
let thing = 'something'
let thing = 'something else' // Error, thing has already been declared

Si vous utilisez var lors de la définition d'une variable, la deuxième définition écrasera la première définition. Cela rend également le code plus difficile à déboguer et n’est pas souhaitable.

// Don't do this!
var thing = 'something'
var thing = 'something else' // perhaps somewhere totally different in your code
console.log(thing) // 'something else'

Donc, vous devriez essayer d'utiliser des variables locales au lieu de variables globales

Portée locale

Les variables utilisées dans une portée spécifique de votre code peuvent être utilisées définies dans portée locale. Il s'agit d'une variable locale.

Il existe deux types de portées locales en JavaScript : la portée de la fonction et la portée au niveau du bloc.

Nous commençons par la portée de la fonction.

Portée de la fonction

Lorsque vous définissez une variable dans une fonction, elle peut être utilisée n'importe où dans la fonction. En dehors de la fonction, vous ne pouvez pas y accéder.

Par exemple, dans l'exemple suivant, la variable hello dans la fonction sayHello :

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

sayHello() // 'Hello CSS-Tricks Reader!'
console.log(hello) // Error, hello is not defined

Portée au niveau du bloc

Lorsque vous utilisez des accolades, vous déclarez un const ou let variable, vous ne pouvez utiliser cette variable qu'entre accolades.

Dans l'exemple suivant, hello ne peut être utilisé qu'entre accolades.

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

console.log(hello) // Error, hello is not defined

La portée au niveau du bloc est un sous-ensemble de la portée des fonctions, car les fonctions doivent être définies avec des accolades (sauf si vous utilisez explicitement des instructions de retour et des fonctions fléchées).

Promotion et portée de la fonction

Lorsqu'elle est définie à l'aide d'une fonction, la fonction sera promue en haut de la portée actuelle. Par conséquent, le code suivant est équivalent :

// 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()

Lorsqu'elle est définie à l'aide d'une expression de fonction, la fonction n'est pas hissée en haut de la portée de la variable.

sayHello() // Error, sayHello is not defined
const sayHello = function () {
 console.log(aFunction)
}

Comme il y a deux variables ici, le levage de fonction peut prêter à confusion et ne prendra donc pas effet. Assurez-vous donc de définir la fonction avant de l'utiliser.

La fonction ne peut pas accéder à la portée des autres fonctions

Lorsque différentes fonctions sont définies séparément, bien qu'une fonction puisse être appelée dans une fonction, une fonction ne peut toujours pas accéder à la portée des autres fonctions internes.

Dans l'exemple suivant, second ne peut pas accéder à la variable firstFunctionVariable.

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

function second () {
 first()
 console.log(firstFunctionVariable) // Error, firstFunctionVariable is not defined
}

Portée imbriquée

Si une fonction est définie à l'intérieur d'une fonction, la fonction interne peut accéder aux variables de la fonction externe, mais pas l'inverse. L’effet est une portée lexicale.

La fonction externe ne peut pas accéder aux variables de la fonction interne.

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
}

Si vous visualisez le mécanisme de la lunette, vous pouvez imaginer un miroir sans tain (verre transparent d'un seul côté). Vous pouvez voir de l’intérieur, mais les personnes extérieures ne peuvent pas vous voir.

La portée des fonctions est comme un miroir sans tain. Vous pouvez regarder de l’intérieur, mais vous ne pouvez pas être vu de l’extérieur.

Les lunettes emboîtées ont un mécanisme similaire, mais elles sont équivalentes à davantage de miroirs sans tain.

Plusieurs couches de fonctions signifient plusieurs miroirs bidirectionnels.

Comprenez la partie précédente sur la portée et vous comprendrez ce qu'est une fermeture.

Fermeture

Lorsque vous créez une autre fonction au sein d'une fonction, cela équivaut à créer une fermeture. Les fonctions internes sont des fermetures. Normalement, afin de rendre accessibles les variables internes de la fonction externe, cette fermeture est généralement renvoyée.

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

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

 return innerFunction
}

outerFunction()() // I see the outer variable!

Parce que la fonction interne renvoie une valeur, vous pouvez simplifier la partie déclaration de la fonction :

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

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

outerFunction()() // I see the outer variable!

Parce que les fermetures peuvent accéder aux variables de la fonction externe, elles ont généralement deux utilisations :

  1. Réduire les effets secondaires

  2. Créer des variables privées

Utiliser des fermetures pour contrôler les effets secondaires

Lorsque vous faites quelque chose lorsqu'une fonction renvoie une valeur, certains effets secondaires se produisent généralement. Des effets secondaires peuvent survenir dans de nombreuses situations, telles que les appels Ajax, les délais d'attente ou même les instructions de sortie console.log :

function (x) {
 console.log('A console.log is a side effect!')
}

Lorsque vous utilisez des fermetures pour contrôler les effets secondaires, vous devez en fait prendre en compte les parties qui pourraient prêter à confusion. le flux de travail de votre code, comme Ajax ou les délais d'attente.

Pour clarifier les choses, il est plus pratique de regarder des exemples :

比如说你要给为你朋友庆生,做一个蛋糕。做这个蛋糕可能花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作用域和闭包_基础知识

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn