一文彻底搞懂作用域

简介: 一文彻底搞懂作用域

网络异常,图片无法展示
|


「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战


什么是作用域


几乎所有编程语言最基本的功能之一,就是能够储存变量当中的值,并且能在之后对这个值进行访问或修改。事实上,正是这种储存和访问变量的值的能力将状态带给了程序。


若没有了状态这个概念,程序虽然也能够执行一些简单的任务,但它会受到高度限制,做不到非常有趣。


这些问题说明需要一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量。这套规则被称为作用域。


也就是说作用域负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并有一套规则,确定当前执行的代码对这些标识符是否有访问权限。


词法作用域


词法作用域简单来说就是定义在词法阶段的作用域。


那什么是词法阶段呢?


它是代码编译的一个步骤,在这个阶段,编译器会把字符串分解成一个个词法单元。


词法单元就是对于编程语言来说有意义的代码块。例如:


var name = "snail"
复制代码


就会被分解为 varnamesnail


接下来进入语法分析阶段,这些词法单元会被转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。


最后是代码生成阶段,AST 会被转换为可执行代码。


所以词法作用域是由写代码时将变量和块作用域写在哪里来决定的。


函数作用域


属于这个函数的全部变量都可以在整个函数的范围内使用及复用,如果有嵌套的子函数,也可以在子函数中使用。这样属于函数的变量存储及访问的规则就是函数作用域。


我们把每一个函数的作用域想象成一个气泡,则内部的子函数的作用域对应的气泡就是当前气泡内的一个小气泡,同时当前函数作用域的气泡也被包含在一个更大的气泡内。


块级作用域


块级作用域,顾名思义就是属于一个代码块的作用域,但是很遗憾,ES6 之前,JavaScript 中其实并没有块级作用域。


console.log('1',i) // undefined
for(var i = 0;i<3;i++){}
console.log('2',i) // 3
复制代码


这里我们在 for 循环中定义变量 i,其实是只想在 for 循环内部的上下文中使用 i,但是在 JavaScript 中,var 声明的变量此时会被绑定到外部作用域中,也就是常说的变量提升,有时候,这会给我们带来一些麻烦。


例如循环陷阱:


var result = [];
for(var i = 0;i<10;i++){
result[i] = function(){
  console.log(i)
}
}
result[0](); // 10
result[1](); // 10
复制代码


例如变量覆盖:


var name = 'snail'
if(true){
  var name = 'running snail'
}
console.log(name) // running snail
复制代码


ES6 之前,变量作用域的基本单元就是函数作用域,所以,当我们需要创建一个块作用域,最普遍的方法除了普通的函数声明之外,就是立即调用函数表达式(IIFE)。例如:


var name = 'snail'
;(function(){
  var name = 'running snail'
  console.log(name) // running snail
})()
console.log(name)   // snail
复制代码


基于这样的一个问题,ES6 中引入了 letconst 声明变量的方式,通过 letconst 声明的变量只在当前代码块内有效。


所以它们实际上为 JavaScript 新增了块级作用域。


console.log('1',i) // ReferenceError: i is not defined
for(let i = 0;i<3;i++){}
复制代码


动态作用域


实际上 JavaScript 并不具有动态作用域,它只有词法作用域。原因如下:


词法作用域和动态作用域的区别是:


词法作用域是在写代码或者说变量定义时确定的;


动态作用域是在运行时确定的;


词法作用域关注函数在何处声明,而动态作用域关注函数在何处调用。


又因为 JavaScript 是需要编译再执行的,所以它属于词法作用域,而非动态作用域。


但是 this 机制很像动态作用域。因为 this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。


作用域链


当一个代码块或者函数嵌套在另一个代码块或者函数中时,就发生了作用域的嵌套。因此,当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。


我们把这之中的每一个作用域看作一环,作用域之间层层嵌套就形成了一个链,这就是作用域链。


好了,以上就是本文总结的作用域相关知识,你学废了吗? <( ̄︶ ̄)>


如有任何问题或建议,欢迎留言讨论!👏🏻👏🏻👏🏻

相关文章
全局变量初始化顺序探究
全局变量初始化顺序探究
|
6月前
|
JavaScript 前端开发
经典面试题【作用域、闭包、变量提升】,带你深入理解掌握!
经典面试题【作用域、闭包、变量提升】,带你深入理解掌握!
|
7月前
|
JavaScript 前端开发 Java
什么是作用域,它的作用是什么?
什么是作用域,它的作用是什么?
197 1
|
7月前
|
存储 自然语言处理 前端开发
【面试题】三道面试题让你掌握JavaScript中的执行上下文与作用域以及闭包
【面试题】三道面试题让你掌握JavaScript中的执行上下文与作用域以及闭包
|
7月前
|
存储 前端开发 JavaScript
【面试题】作用域面试题
【面试题】作用域面试题
|
7月前
|
存储 缓存 自然语言处理
【面试题】深入理解闭包的形成过程及应用!
【面试题】深入理解闭包的形成过程及应用!
|
存储 算法 安全
【重学C/C++系列(三)】:这一次彻底搞懂指针和引用
相信学过C++都知道指针以及引用,C++中使用指针是为了兼容C语言,而使用引用是为了更加贯彻面向对象编程思想,今天小余就来和大家聊聊关于C++中指针以及引用。
【重学C/C++系列(三)】:这一次彻底搞懂指针和引用
|
Web App开发 JavaScript 前端开发
应该如何理解闭包
应该如何理解闭包
应该如何理解闭包
|
存储 Java
【Java编程进阶】常量变量及作用域
Java 变量的定义在程序运行期间,此时可能产生一些临时数据,应用程序会将这些数据保存在一些内存单元中,每个内存单元都用一个标识符来标识。这些内存单元被称为`变量`,定义的标识符就是`变量名`,内存单元中存储的数据就是变量的`值`。
141 0
【Java编程进阶】常量变量及作用域
|
编译器 C语言 开发者
作用域和链接属性|学习笔记
当变量被定义在程序的不同位置时,它的作用范围是不一样的,这个作用范围就是我们所说的作用域。
121 0
作用域和链接属性|学习笔记