一文彻底搞懂作用域

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

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


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


作用域链


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


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


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


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

相关文章
全局变量初始化顺序探究
全局变量初始化顺序探究
|
5月前
|
C#
一文搞懂C#中类成员的可访问性
一文搞懂C#中类成员的可访问性
62 5
|
7月前
|
JavaScript 前端开发
经典面试题【作用域、闭包、变量提升】,带你深入理解掌握!
经典面试题【作用域、闭包、变量提升】,带你深入理解掌握!
|
3月前
|
搜索推荐
用通俗易懂的方式解释一下多态
【10月更文挑战第13天】多态,就像是编程世界里的一场神奇魔术,它让不同的对象在面对相同的操作时,能够展现出各自独特的表现。
31 2
|
7月前
|
编译器 C++ 开发者
通俗讲解 初学者一文看懂!虚函数、函数重载、重写的区别
函数重载允许在同一作用域内定义同名但参数列表不同的函数,提高代码灵活性和可读性,避免命名冲突。通过参数类型自动选择合适版本,如C++中的`print()`可处理整数、浮点数和字符串。虚函数实现运行时多态,基类指针调用时调用实际对象的版本。抽象类至少有一个纯虚函数,不能实例化,用于定义接口规范。抽象类和纯虚函数是构建多态和继承体系的基础,提供接口标准,减少代码冗余,增强代码清晰性和可维护性。
|
8月前
|
JavaScript 前端开发 Java
什么是作用域,它的作用是什么?
什么是作用域,它的作用是什么?
216 1
|
8月前
|
存储 自然语言处理 前端开发
【面试题】三道面试题让你掌握JavaScript中的执行上下文与作用域以及闭包
【面试题】三道面试题让你掌握JavaScript中的执行上下文与作用域以及闭包
一文搞懂原型和原型链
一文搞懂原型和原型链
129 0
一文搞懂原型和原型链
何为构造函数
Java构造函数,也叫构造方法,是Java中一种特殊的函数。函数名与对应类名相同,无返回值。
150 0
|
设计模式 SQL 缓存
二十三天搞懂设计模式之单例模式的七种写法
二十三天搞懂设计模式之单例模式的七种写法