深层理解Javascript中的作用域!

197 阅读3分钟

前言:

开始前,先了解编辑器是如何解读代码的?如代码var a = 2

  1. 先词法分析,判断词法单元:如var,a ,=,2以及空格。
  2. 解析:将词法单元转换成一个逐级嵌套的程序语法结构树---抽象语法树,这里自行去熟知树的概念。
  3. 生成代码: 即var a = 2

在JS中,作用域可以分为三大部分:全局作用域,函数体作用域,块级作用域。直接来上代码解析:

引擎: 在计算机中,计算机本身是不能够解读代码的,而是要通过计算机中的浏览器引擎来解读代码,这里推荐使用谷歌引擎【Google引擎现如今是最完善,功能最强的引擎】,而计算机中有个识别JS中的V8引擎,接下来根据V8引擎介绍。

正文

AO 在介绍作用域之前,有必要了解有效标识符,这里不多重复标识符的概念,通过一个简单代码来理解:

function foo() {
    var b = 2
    console.log(a + b)
}
foo(a)

a,b 是foo 的有效标识符

按代码所示这样来理解有效标识符,有效标识符:在一个函数内声明的变量或者函数自己内的参数就是这个函数体的有效标识符,即在一个空间中有意义的东西。

JS中,作用域只有三种:全局作用域,函数作用域和块级作用域

全局作用域

位于全部作用域内,在JavaScript中,全局作用域是指在代码中任何地方都可以访问的范围。在浏览器环境中,全局作用域通常是指window对象,在Node.js环境中则是指global对象。

function foo(a) {
    console.log(a + b);
}


function bar() {
    var b = 2
}

bar()
foo(1)

根据自己分析,先进行两个函数的声明,foo 和 bar ,到 bar函数的调用,赋值 bar 为2 ,再是 foo 函数的调用,a 为1 ,在执行a+b的打印为3。可是,结果是这样吗?看一下

image.png

执行报错,b 并没有找到,这是为什么?因为b在内层作用域内,无法访问外层作用域。下面在介绍一个概念。

函数作用域

内层作用域是可以访问外层作用域的,反之则不行

function foo() {
    var a = 1
}

foo()

console.log(a);

因为变量a在内层作用域内,外层无法访问内层作用域,所以a找不到,无法打印,改变一下代码

var a = 1

function foo() {
    console.log(a);
}

foo()

将变量a放入全局中,a 的执行由内层往外层访问,就可以找到执行。

image.png

var a = 1


function foo() {
    var a = 2
    console.log(a);
}

foo()

这里a的打印结果是2,由内层作用域访问外层作用域。真正可以这样来理解:

81904bbd008b3b6b6543a661860d495a.png

代码执行过程中,V8会创建一个调用栈,栈内先创建全局执行上下文,存入a值和foo值,在创建foo的执行上下文,存入a值。由于栈的性质先入后出,所以就先访问foo中a的值。

块级作用域:{} 中 + let

console.log(a);

var a = 1

这种古怪的编译方式并不是报错,而是undefined。这是var情况下的声明提升。

let vs var

  1. var 存在声明提升,let 不存在
  2. let 会和 {}形成块级作用域
  3. var 可以重复声明变量,let 不可以

欺骗词法作用域

eval() 将原本不属于这里的代码变成就像天生定义在这里一样

function foo(str,a) {
    eval(str)
    console.log(a,b);
}

var b = 2

foo('var b = 3',1)

这里打印的就是1和3了。

with() {} 用于修改一个对象中的属性值,但如果修改的属性在原对象中不存在,那么该属性就会被泄露到全局

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

var o1 ={
    a:3
}

var o2 ={
    b:3
}

这里打印的就是2。

屏幕截图 2024-05-03 211736.png

总结:

三种作用域的布局安排,全局作用域,函数体作用域,块级作用域,以及各个作用域之间的访问情况就讲到这里了。

image.png