资讯专栏INFORMATION COLUMN

Jeffrrey / 1525人阅读

大白话解释作用域和闭包是个啥 作用域的分类

常见的变量作用域就是 静态作用域(词法作用域)动态作用域 。词法作用域注重的是 write-time ,即 编程时的上下文 ,而 动态作用域 则注重的是 run-time ,即 运行时上下文 。词法作用域中我们需要知道一个函数 在什么地方被定义 ,而动态作用域中我们需要关心的是函数 在什么地方被调用

而 javascript 使用的则是词法作用域

let value = 1

function foo() {
    console.log(value)
}

function bar() {
    let value = 2
    foo()
}

bar() // 1

在 javascript 解析模式中,当 foo 被调用的时候:

检查 foo 函数内是否存在 value

存在则使用这个 value

不存在则根据书写代码的位置查找上一层代码(这里的 window),找到 value 为 1

在动态作用域的解析模式中,当 foo 被调用的时候:

检查 foo 函数内是否存在 value

存在则使用这个 value

不存在则根据调用该函数的作用域中去寻找也就是这里的 bar 函数,找到 value 为 2

在从内层到外层的变量搜索过程中,当前作用域到外层作用域再到更外层作用域直到最外层的全局作用域,整个搜寻轨迹就是 作用域链

变量的两种查找类型

一种是 rhs 一种是 lhs

假设有这么一段代码:

console.log(a) // 输出 undefined
console.log(a2) // 报错 a2 is not defined
var a = 1

上述代码实际上在变量提升的作用下应该是下面这个顺序:

var a
console.log(a) // 输出 undefined
console.log(a2) // 报错 a2 is not defined
a = 1

第一个 console 输出 undefined 因为还未执行赋值操作,查询过程是 rhs 也就是 right-hand-side

第二个 console 报错,是因为 rhs 查询 a2 变量不存在因此报错

a = 1 则是赋值操作,也就是 lhs,英文 left-hand-side

闭包

闭包是啥?闭包就是从函数外部访问函数内部的变量,函数内部的变量可以持续存在的一种实现。

在了解了词法作用域和变量的查询方式之后,我们看看一个简单的闭包的实现逻辑:

function f() {
    let num = 1 // 里面的变量
    function add() {
        num += 1
    }
    function log() {
        console.log(num)
    }
    return { add, log } // 我要到外面去了
}

const { add, log } = f()

log() // 1 我从里面来,我在外面被调用,还是可以获得里面的变量
add()
log() // 2

首先定义一个 f 函数,函数内部维护一个变量 num,然后定义两个函数 add 和 log

add 函数每次调用会增加 num 的值

log 函数每次调用会打印 num 的值

然后我们将两个函数通过 return 方法返回

紧接着先调用外部的 log 方法打印 f 方法维护的 num,此时为 1

然后调用外部的 add 方法增加 num 的值

最后再次调用 log 方法打印 num,此时则为 2

为什么外部定义的 add 函数可以访问 f 函数内部的变量呢。正常情况下外部作用域不可访问内部作用域的变量,但我们将内部访问其内部变量的方法“导出”出去,以至于可以从外部直接调用函数内部的方法,这样我们就可以从函数的外部访问函数内部的变量了。

经典的 for 循环问题
arr = []
for (var i = 0; i < 10; i ++) {
    arr[i] = function() {
        console.log(i)
    }
}
arr[2]() // 10

首先我们知道 for 循环体内的 i 实际上会被定义在全局作用域中

每次循环我们都将 function 推送到一个 arr 中,for 循环执行完毕后,arr 中张这样:

随后我们执行代码 arr[2]() 此时 arr[2] 对应的函数 function(){ console.log(i) } 会被触发

函数尝试搜索函数局部作用域中的 i 变量,搜索不到则会继续向外层搜索,i 被定义到了外层,因此会直接采用外层的 i,就是这里的全局作用域中的 i,等到这个时候调用这个函数,i 早已变成 10 了

那么有什么方法能够避免出现这种情况吗?

ES6 之前的解决方案:

了解了闭包我们就知道了闭包内的变量可以持续存在,所以修改代码将 arr 中的每一项改为指向一个闭包:

arr = []
for (var i = 0; i < 10; i ++) {
    arr[i] = (function() { // 这是一个闭包
        var temp = i // 闭包内部维护一个变量,这个变量可以持续存在
        return function() {
            console.log(temp)
        }
    })()
}

这样程序就能按照我们的想法运行了

ES6 之后的解决方案:

ES6 之后我们就有了块级作用域因此代码可以改为这样:

arr = []
for (let i = 0; i < 10; i ++) { // 使用 let
    arr[i] = function() {
        console.log(i)
    }
}

在使用 let 之后,我们每次定义 i 都是通过 let i 的方法定义的,这个时候 i 不再是被定义到全局作用域中了,而是被绑定在了 for 循环的块级作用域中

因为是块级作用域所以对应 i 的 arr 每一项都变成了一个闭包,arr 每一项都在不同的块级作用域中因此不会相互影响

参考:

https://github.com/mqyqingfen...

https://www.datchley.name/bas...

https://segmentfault.com/a/11...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102467.html

相关文章

  • vue中如何实现的自定义按钮

    摘要:在实际开发项目中,有时我们会用到自定义按钮因为一个项目中,众多的页面,为了统一风格,我们会重复用到很多相同或相似的按钮,这时候,自定义按钮组件就派上了大用场,我们把定义好的按钮组件导出,在全局引用,就可以在其他组件随意使用啦,这样可以大幅度 在实际开发项目中,有时我们会用到自定义按钮;因为一个项目中,众多的页面,为了统一风格,我们会重复用到很多相同或相似的按钮,这时候,自定义按钮组件就...

    biaoxiaoduan 评论0 收藏0
  • JavaScript代码整洁之道

    摘要:代码整洁之道整洁的代码不仅仅是让人看起来舒服,更重要的是遵循一些规范能够让你的代码更容易维护,同时降低几率。另外这不是强制的代码规范,就像原文中说的,。里式替换原则父类和子类应该可以被交换使用而不会出错。注释好的代码是自解释的。 JavaScript代码整洁之道 整洁的代码不仅仅是让人看起来舒服,更重要的是遵循一些规范能够让你的代码更容易维护,同时降低bug几率。 原文clean-c...

    liaorio 评论0 收藏0
  • 前端经典面试题总结

    摘要:接着我之前写的一篇有关前端面试题的总结,分享几道比较经典的题目第一题考点作用域,运算符栗子都会进行运算,但是最后之后输出最后一个也就是那么其实就是而且是个匿名函数,也就是属于,就输出第二和第三个都是类似的,而且作用域是都是输出最后一个其实就 接着我之前写的一篇有关前端面试题的总结,分享几道比较经典的题目: 第一题: showImg(https://segmentfault.com/im...

    BlackMass 评论0 收藏0
  • 私有云那家好-六大私有云厂商详细对比!

    对比内容UCloudStackZStackVMwareQingCloud腾讯TStack华为云Stack优势总结•基于公有云自主可控•公有云架构私有化部署•轻量化/轻运维/易用性好•政府行业可复制案例轻量化 IaaS 虚拟化平台•轻量化、产品成熟度高•业内好评度高•功能丰富、交付部署快•中小企业案例多全套虚拟产品及云平台产品•完整生态链、技术成熟•比较全面且健全的渠道•产品成熟度被市场认可,市场占...

    ernest.wang 评论0 收藏0
  • cross-env使用记录

    摘要:能跨平台地设置及使用环境变量让这一切变得简单,不同平台使用唯一指令,无需担心跨平台问题安装方式改写使用了环境变量的常见如在脚本多是里这么配置运行,这样便设置成功,无需担心跨平台问题关于跨平台兼容,有几点注意 cross-env能跨平台地设置及使用环境变量, cross-env让这一切变得简单,不同平台使用唯一指令,无需担心跨平台问题 1、npm安装方式 npm i --save-de...

    Michael_Ding 评论0 收藏0
  • webpack打包插件

    摘要:引入的模块引入的使用将打包打包的拆分将一部分抽离出来物理地址拼接优化打包速度压缩代码,这里使用的是,同样在的里面添加 const path = require(path); //引入node的path模块const webpack = require(webpack); //引入的webpack,使用lodashconst HtmlWebpackPlugin = require(ht...

    ChanceWong 评论0 收藏0

发表评论

0条评论

Jeffrrey

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<