作用域

104 阅读3分钟

什么是作用域

它体现了JS引擎对变量名的处理机制。这个机制在不同语言中有不同实现,大体可以分两类:静态作用域(也称词法作用域)与动态作用域。

静态作用域(词法作用域)里,对于函数体中的一个符号,不会逐层检查函数的调用链,而是检查函数定义时的外部环境,即捕捉的是函数定义时该符号的绑定。

JS采用的是静态作用域

let a = 10;
function func (){
    console.log(a);
}
function func1 (){
    let a=5;
    func()
}

由于JS采用静态作用域,所以func定义时保留了对外层a变量的引用,结果为10。

JS作用域的特点

在JS中,作用域就像套娃一般,外层套内层。变量在调用中将会由调用点出发,向外层寻找符合变量名的变量,直至到达全局作用域。

一共有三种方式可以形成作用域,全局、函数、块。全局作用域在运行时便默认存在,我们可以使用的是函数与块作用域

//全局
function foo(){
    let vari = 10;//函数作用域
    if(vari==10){
        let vari = 20//块级作用域
        console.log(vari)
    }
    {
        let abc = 10//就算只有花括号也可以构成块级作用域
    }
    console.log(abc) 
    console.log(vari)
}
foo()

// 20
// Uncaught ReferenceError: abc is not defined

上面这个例子简单展示了块级作用域与函数作用域。

此外,函数(包括回调函数)及对象方法将会使用其被定义时所处的作用域,而非使用其实际调用时的作用域。这也引出了闭包,不过这里我们先不展开讲闭包。一个函数作用域的例子:

function foo(){
    console.log(a)
}
function bar(func){
    let a =10;
    func()
}
bar(foo)
// Uncaught ReferenceError: a is not defined

注意,块级作用域只在使用let与const声明变量时可用,使用var声明变量不会触发块级作用域。在正常情况下我们推荐使用let声明变量,它拥有块级作用域,在重复定义时let会报错,而var只会默默地进行赋值操作。

function foo(){
    var a=10;
    if(a){
        var a=20;
        console.log(a)
    }
    console.log(a);
}

// 20
// 20

变量提升

在引擎层面上,JS会将每个函数作用域中的变量声明提升至函数顶端,无论他在哪(哪怕是不可能被执行的代码块),而对变量的赋值则会推迟到变量语句实际所处的位置。let与const也会有提升情况,不过这两个存在暂时性死区(TDZ)现象以模拟块级作用域。

function foo(){
    console.log(a)
    {
        console.log(a)
        let a=10;
    }
}
// Uncaught ReferenceError: a is not defined

全局作用域

在JS中,全局作用域可以使用globalThis进行引用。除此之外在各个环境中还有不同的引用全局作用域的方法,比如浏览器的window,Node.js的global,web Worker的self。

由于模块(module)机制的出现,全局作用域的重要性逐渐降低,语句更多的是在自己所属的函数作用域下执行。