JS中的作用域与底层逻辑

93 阅读4分钟

前言

在编程的世界里,底层逻辑如同灯塔,引领着开发者们在代码的海洋中航行。它们确保了程序的可读性、可维护性和正确性。本文将深入探讨几个核心概念:词法分析作用域、以及变量声明的差异,以及varletconst的区别。

⚡ 词法分析:

词法分析词法分析也被称为分词。例如,一个简单的加法表达式a + b,在词法分析后会被识别为三个元素:标识符a、运算符+和标识符b。这一步骤为后续的语法解析奠定了基础,确保每一部分都能被正确解析和处理。

⚡ 作用域:

在代码执行的舞台上,作用域扮演着至关重要的角色,它定义了变量的可见性和生命周期。作用域分为两种基本类型:全局作用域函数作用域

全局作用域中的变量在整个程序中都可被访问,而函数作用域内的变量仅在该函数内部有效。这一规则确保了变量使用的清晰隔离,减少了命名冲突的可能性。

var a = 1

function damn(){
    var a = 2
}

damn()
console.log(a);

例如上面这个例子,大家认为输出的a的值应该是多少呢?很简单,a = 1,虽然函数中已经声明了a = 2并且已经调用了函数,但是输出方法在全局作用域中,浏览器的计算引擎会在全局环境中寻找a的值,故结果为a = 1.通过下图可以加强理解

image.png

⚡ 词法作用域:变量声明的地方。这意味着变量的可用性在其声明时就已经确定,无论函数如何调用或代码如何执行。

⚡ 欺骗词法作用域:eval()与with(){}

JavaScript中,eval()函数和with语句能够“欺骗”词法作用域。

eval()能够执行一个字符串作为代码,使其所在的作用域能够访问到原本不可见的变量。让原本不属于这里的代码,变得好像天生就定义在这里了一样。

 function damn(a,str){
     eval(str);   // var b = 2
     console.log(a,b);
 }

输出结果如下:

image.png

with语句通过临时修改作用域链,使得对未定义属性的访问可能意外地创建全局变量,当修改的对象中不存在的属性时,这个属性会被泄露到全局变成全局变量。

function foo(obj){
    with(obj){
    a = 2;
}
}

var o1 = {
    b: 1,
}
foo(o1)

console.log(a);  // 2

输出结果如下:

image.png

其实这里本来应该是报错,但是with(){}的使用使变量a泄露到全局当中了,所以能被输出。

所以在日常的开发过程中可以尽量避免使用eval()与with(){}

⚡ var, let与const:

var声明的变量具有两个特点:

1.var 声明的变量会存在声明提升

  1. var 在全局声明的变量会被添加到window对象上。

image.png

相比之下,ES6引入的letconst提供了更精细的控制。let允许在块级作用域(如循环体或条件语句内)声明变量,解决了var重复声明和变量提升的问题。

const声明的是常量,不可以修改值,任何尝试修改其值的操作都会引发错误,增强了代码的安全性和可读性。

⚡ 块级作用域

{} + let || const -> {let} {const}

块级作用域是指变量、常量或者某些类型的语句(比如if、for循环)的有效范围仅限于该代码块内部。在这一作用域内声明的变量,在块外是无法访问的。

由此我们引申出一个概念:暂时性死区

let a = 1 
{ 
console.log(a); // 暂时性死区 
let a = 2 
}

可能大部分人都认为上述代码输出结果为a = 1,但是实际输出结果却为:Cannot access 'a' before initialization,无法在a初始化之前访问它。

因为{let}形成了一个块级作用域,会先访问自身作用域内的a,而由于先输出再声明a = 2,导致自身作用域内的a无法被访问,然后在全局中又访问不到变量a,所以结果输出报错。

结语

今天有关JS中的作用域与底层逻辑的分享到这里就结束了,希望对你有所帮助。😉