JavaScript作用域和闭包详解

作用域和闭包在javascript里非常重要。但是在我最初学习javascript的时候,却很难理解。我们先从作用域开始。本文主要和大家介绍了javascript作用域和闭包,希望能帮助大家更好的理解javascript作用域和闭包。

作用域

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

全局作用域

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

立即学习“Java免费学习笔记(深入)”;

不过这个规则只在浏览器中运行的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 codeconsole.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 belowsayHello()function sayHello () { console.log('Hello CSS-Tricks Reader!')}// This is the same as the code abovefunction sayHello () { console.log('Hello CSS-Tricks Reader!')}sayHello()

登录后复制

使用函数表达式定义时,函数就不会被提升到变量作用域的顶部。

sayHello() // Error, sayHello is not definedconst 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作用域和闭包详解

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

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

闭包

你在一个函数内新建另一个函数时,就相当于创建了一个闭包。内层函数就是闭包。通常情况下,为了能够使得外部函数的内部变量可以访问,一般都会返回这个闭包。

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!

登录后复制

因为闭包可以访问外层函数的变量,因此他们通常有两种用途:

减少副作用

创建私有变量

使用闭包控制副作用

当你在函数返回值时执行某些操作时,通常会发生一些副作用。副作用在很多情况下都会发生,比如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页),你就能看到可以访问的变量了。

JavaScript作用域和闭包详解

使用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作用域和闭包

以上就是JavaScript作用域和闭包详解的详细内容,更多请关注【创想鸟】其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至253000106@qq.com举报,一经查实,本站将立刻删除。

发布者:PHP中文网,转转请注明出处:https://www.chuangxiangniao.com/p/2786691.html

(0)
上一篇 2025年3月8日 17:52:30
下一篇 2025年3月8日 17:52:41

AD推荐 黄金广告位招租... 更多推荐

相关推荐

  • 使JavaScript进行断舍离的函数分享

    本文主要和大家介绍了用函数式编程对javascript进行断舍离,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望能帮助到大家。 关于DHTML DHTML是Dynamic HTML的简称,就是动态的html…

    编程技术 2025年3月8日
    200
  • JavaScript中的RegExp对象解析

    正则表达式(regular expression)是一种表达文本模式(即字符串结构)的方法,有点像字符串的模板,常常用作按照“给定模式”匹配文本的工具。比如,正则表达式给出一个 email 地址的模式,然后用它来确定一个字符串是否为 ema…

    编程技术 2025年3月8日
    200
  • JavaScript数组进化与性能分析

    正式开始前需要声明,本文并不是要讲解 javascript 数组基础知识,也不会涉及语法和使用案例。本文讲得更多的是内存、优化、语法差异、性能、近来的演进。本文主要和大家介绍javascript 数组的进化与性能分析,本文讲得更多的是内存、…

    2025年3月8日
    200
  • JavaScript实现HTML5游戏断线自动重连

    断线重连的需求一断线重连原理二游戏内自动重连不刷新三刷新游戏自动重连重连数据locationreplace重置url重连四实际项目中处理重连机制最后的总结断线重连的需求,尤其是手机上,会因为网络的不稳定或者其他原因,导致用户的socket链…

    编程技术 2025年3月8日
    200
  • JavaScript实现斑马线表格示例分享

    虽然现在有很多框架可以轻松的实现斑马线效果,而且兼容性也很不错,比如bootstrap,但是不可否认的是使用javascript实现的是兼容性最强的(浏览器不支持或禁止javascript脚本除外),所以今天使用原生js实现了一个斑马线效果…

    编程技术 2025年3月8日
    200
  • js点击收缩或张开的悬浮窗实例分享

    本文主要和大家介绍了js实现可以点击收缩或张开的悬浮窗效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能帮助到大家。 说明:点击”+“按钮,悬浮窗收缩/展开 思路 1、在html中定义一个p块,定一个id;一个按钮,点击时用。 …

    编程技术 2025年3月8日
    200
  • SVG和Vanilla JS框架创建一个“星形变心形”代码分享

    本文我们主要和大家分享用svg和vanilla js框架创建一个“星形变心形”的动画效果代码,希望能帮助到大家。 思路 它们都是由五个三次贝塞尔曲线构成。下边的互动演示展示了每条曲线以及这些曲线相连接的点。点击任意曲线或连接点可以看到两个图…

    编程技术 2025年3月8日
    200
  • 改变JavaScript对象的rest和spread属性方法

    在JavaScript中合并多个对象是一个很常见的事情。但在JavaScript中,到目前为止并没有一种很方便的语法来进行合并。本文主要和大家分享三个点如何改变JavaScript对象的rest和spread属性。 在ES5中,通过使用Lo…

    2025年3月8日 编程技术
    200
  • Vue.js响应式原理详解

    本人是Java背景,许多年前刚接触JavaScript时有点怪怪的,因为它没有 getters 和 setters。随着时间的推移,我开始喜欢上这个缺失的特性,因为相比Java大量的 getter 和 setter,它让代码更简洁。例如,我…

    2025年3月8日
    200
  • javascript中的隐式调用详解

    所谓的隐式调用简单来说就是自动调用一些方法,而这些方法像钩子一样可以在外部修改,从而改变既定行为。 下面我会列举一些最近看到的隐式调用,例子都是点到即止,欢迎补充 数据类型转换 toSting 和 valueOf var obj = { a…

    编程技术 2025年3月8日
    200

发表回复

登录后才能评论