浅谈JavaScript作用域和闭包

作用域和闭包在JavaScript里非常重要。但是在我最初学习JavaScript的时候,却很难理解。这篇文章会用一些例子帮你理解它们。

我们先从作用域开始。

作用域

JavaScript的作用域限定了你可以访问哪些变量。有两种作用域:全局作用域,局部作用域。

全局作用域

在所有函数声明或者大括号之外定义的变量,都在全局作用域里。

不过这个规则只在浏览器中运行的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!'

尽管你可以在全局作用域定义变量,但我们并不推荐这样做。因为可能会引起命名冲突,两个或更多的变量使用相同的变量名。如果你在定义变量时使用了const或者let,那么在命名有冲突时,你就会收到错误提示。这是不可取的。

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

如果你定义变量时使用的是var,那第二次定义会覆盖第一次定义。这也会让代码更难调试,也是不可取的。

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

所以,你应该尽量使用局部变量,而不是全局变量

局部作用域

在你代码某一个具体范围内使用的变量都可以在局部作用域内定义。这就是局部变量。

JavaScript里有两种局部作用域:函数作用域和块级作用域。

我们从函数作用域开始。

函数作用域

当你在函数里定义一个变量时,它在函数内任何地方都可以使用。在函数之外,你就无法访问它了。

比如下面这个例子,在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语句和箭头函数)。

函数提升和作用域

当使用function定义时,这个函数都会被提升到当前作用域的顶部。因此,下面的代码是等效的:

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

因为这里有两个变量,函数提升可能会导致混乱,因此就不会生效。所以一定要在使用函数之前定义函数。

函数不能访问其他函数的作用域

在分别定义的不同的函数时,虽然可以在一个函数里调用一个函数,但一个函数依然不能访问其他函数的作用域内部。

下面这例,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 }

如果把作用域的机制可视化,你可以想象有一个双向镜(单面透视玻璃)。你能从里面看到外面,但是外面的人不能看到你。

浅谈JavaScript作用域和闭包

函数作用域就像是双向镜一样。你可以从里面向外看,但是外面看不到你。

嵌套的作用域也是相似的机制,只是相当于有更多的双向镜。

浅谈JavaScript作用域和闭包

多层函数就意味着多个双向镜。

理解前面关于作用域的部分,你就能理解闭包是什么了。

闭包

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wyzxjw.html