Home  >  Article  >  Web Front-end  >  Detailed explanation of JavaScript scopes and closures

Detailed explanation of JavaScript scopes and closures

小云云
小云云Original
2018-02-09 13:33:271262browse

Scope and closure are very important in JavaScript. But when I first learned JavaScript, it was difficult to understand. Let's start with scope. This article mainly introduces JavaScript scope and closure to you, hoping to help you better understand JavaScript scope and closure.

Scope

The scope of JavaScript limits which variables you can access. There are two types of scope: global scope and local scope.

Global scope

Variables defined outside all function declarations or braces are in the global scope.

However, this rule is only valid in JavaScript running in the browser. If you are in Node.js, variables in the global scope are different, but this article will not discuss Node.js.

`const globalVariable = 'some value'`

Once you declare a global variable, you can use it anywhere, including inside functions.

const hello = 'Hello CSS-Tricks Reader!'

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

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

Although you can define variables in the global scope, we do not recommend doing so. Because naming conflicts may occur, two or more variables use the same variable name. If you use const or let when defining a variable, you will receive an error if there is a naming conflict. This is not advisable.

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

If you use var when defining a variable, the second definition will overwrite the first definition. This also makes the code harder to debug and is undesirable.

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

So, you should try to use local variables instead of global variables

Local scope

Variables used in a specific scope of your code can be used locally defined within scope. This is a local variable.

There are two types of local scopes in JavaScript: function scope and block-level scope.

We start with function scope.

Function scope

When you define a variable in a function, it can be used anywhere within the function. Outside the function, you can't access it.

For example, in the following example, the hello variable in the sayHello function:

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

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

Block-level scope

When you use curly brackets, you declare a const or let variable, you can only use the variable inside curly braces.

In the following example, hello can only be used within curly brackets.

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

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

Block-level scope is a subset of function scope, because functions need to be defined with braces (unless you explicitly use return statements and arrow functions).

Function promotion and scope

When defined using a function, the function will be promoted to the top of the current scope. Therefore, the following code is equivalent:

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

When defined using a function expression, the function is not hoisted to the top of the variable scope.

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

Because there are two variables here, function promotion may cause confusion, so it will not take effect. So be sure to define the function before using it.

Function cannot access the scope of other functions

When different functions are defined separately, although a function can be called in one function, one function still cannot access the scope of other functions. internal.

In the following example, second cannot access the firstFunctionVariable variable.

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

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

Nested scope

If a function is defined inside a function, the inner function can access the variables of the outer function, but not vice versa. The effect is lexical scoping.

The outer function cannot access the variables of the inner function.

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
}

If you visualize the mechanism of scope, you can imagine a two-way mirror (single-sided see-through glass). You can see outside from inside, but people outside can't see you.

Function scope is like a two-way mirror. You can look out from the inside, but you can't be seen from the outside.

The nested scope is a similar mechanism, but it is equivalent to having more two-way mirrors.

Multi-layer functions mean multiple two-way mirrors.

Understand the previous part about scope, and you can understand what closure is.

Closure

When you create another function within a function, it is equivalent to creating a closure. Inner functions are closures. Usually, in order to make the internal variables of the external function accessible, this closure is generally returned.

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

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

 return innerFunction
}

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

Because the inner function returns a value, you can simplify the function declaration part:

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

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

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

Because closures can access the variables of the outer function, they usually have two uses:

  1. Reduce side effects

  2. Create private variables

Use closures to control side effects

When you perform something when a function returns a value, some side effects usually occur. Side effects can occur in many situations, such as Ajax calls, timeout processing, or even console.log output statements:

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

When you use closures to control side effects, what possibilities do you actually need to consider? Parts that will confuse the workflow of the code, such as Ajax or timeouts.

To make things clear, it is more convenient to look at examples:

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

The above is the detailed content of Detailed explanation of JavaScript scopes and closures. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn