一文彻底搞懂作用域

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

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


「这是我参与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 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。


作用域链


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


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


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


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

相关文章
|
1月前
|
JavaScript 前端开发 Java
什么是作用域,它的作用是什么?
什么是作用域,它的作用是什么?
11 1
|
4月前
|
存储 自然语言处理 前端开发
【面试题】三道面试题让你掌握JavaScript中的执行上下文与作用域以及闭包
【面试题】三道面试题让你掌握JavaScript中的执行上下文与作用域以及闭包
|
4月前
|
存储 前端开发 JavaScript
【面试题】作用域面试题
【面试题】作用域面试题
|
4月前
|
存储 缓存 自然语言处理
【面试题】深入理解闭包的形成过程及应用!
【面试题】深入理解闭包的形成过程及应用!
|
5月前
|
JavaScript 前端开发
JavaScript开发基础问题:解释一下变量提升。
JavaScript开发基础问题:解释一下变量提升。
42 5
|
11月前
|
C语言
C语言——(基础知识) 变量的作用域和生命周期 and 注释
C语言——(基础知识) 变量的作用域和生命周期 and 注释
52 0
|
自然语言处理 JavaScript
再谈JS闭包
作用域 作用域嵌套 词法作用域(lexicsl scope) 闭包 闭包示例
186 0
|
Web App开发 JavaScript 前端开发
应该如何理解闭包
应该如何理解闭包
应该如何理解闭包
|
存储 Java
【Java编程进阶】常量变量及作用域
Java 变量的定义在程序运行期间,此时可能产生一些临时数据,应用程序会将这些数据保存在一些内存单元中,每个内存单元都用一个标识符来标识。这些内存单元被称为`变量`,定义的标识符就是`变量名`,内存单元中存储的数据就是变量的`值`。
107 0
【Java编程进阶】常量变量及作用域
|
Java C++ Python
详解Python函数的作用域是干嘛的
Python是静态作用域语言,尽管它自身是一个动态语言。也就是说,在Python中变量的作用域是由它在源代码中的位置决定的,这与C有些相似,但是Python与C在作用域方面的差异还是非常明显的。