重温面试常客,JS基础学习之 作用域

151 阅读5分钟

什么是作用域

作用域是当前执行上下文,它决定变量和表达式能否被访问到,作用域是可以嵌套的形成层次结构,内层作用域可以访问外层作用域的变量或表达式,反之则不行

JavaScript 的作用域分大概可以分成几种:

  1. 全局作用域:脚本模式运行所有代码的默认作用域。
  2. 函数作用域:由函数创建的作用域。
  3. 块级作用域:ES6时引入的概念,可以用let和const创建。

词法作用域

词法作用域也叫静态作用域,它在变量或者表达式定义时就已经决定了,不会再改变;有静态作用域当然也有动态作用域,动态作用域时调用时决定的;不过Js只具有静态作用域,并不具有动态作用域

来看下面这个代码例子:

var x = 1 

function test() {
    
    var y = 2

    console.log(x); // 1

    function test1() {
        
        var z = 3

        console.log(x); //1 
        
        console.log(y); // 2
    }

    test1()

    try {
        console.log(z); 
    } catch (error) {
        console.log(error); // z is not defined
    }
}

test()

console.log(x);  // 1

console.log(y); // y is not defined
复制代码

通过上面的例子可以可以了解到两个作用域全局作用域函数作用域

全局作用域

全局作用域顾名思义就是存在于全局的作用域,在例子中,可以看到定义了变量x,它的值为1;因为它是在全局中定义的因此属于全局作用域的变量,挂载在window中,可用window.x调用。

全局作用域属于最顶层的作用域,因此不仅可以在全局中调用到变量x,还可以在函数test甚至嵌套函数test1中调用到,因此可以知道在正常情况下可以在当前环境下运行的代码都可以访问到全局作用域中的变量或表达式

函数作用域

函数作用域就是函数创建时产生的作用域,在例子中,存在两个函数分别时test和test1,它们时相互嵌套的函数,test是外层、test1是内层,并且各自有自己的函数作用域。

从例子中可以看到,内层函数test1可以访问到外层函数test的变量y,但是反过来外层函数test却无法访问内层函数test1的变量z,因此可以知道内层作用域可以访问外层作用域的变量或表达式,反之则不行

总结

  1. 全局作用域为最外层作用域、可供全局访问。
  2. 内层作用域可以访问外层作用域的变量或表达式,反之则不行

打个比方我们可以将作用域比作一个个住处,全局作用域是最大的房间、函数作用域、嵌套函数作用域相当于内层房间。外面的房间没有内层房间的门钥匙,因此进不去内层房间,但是内层房间肯定可以开门访问外层房间,但是它每层只有一把钥匙,没有去其他房间的钥匙,所以无法进其他房间。

块级作用域

块级作用域是ES6引入的概念,在日常的工作中通常使用let和const创建。

可以看一个经典的例子:

for (var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);  // 5 5 5 5 5
    },0)
}

for (let i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);  // 1 2 3 4 5
    },0)
}
复制代码

可以看到使用var定义的i,因没有打印出希望的1 2 3 4 5,而是打印了5个5,这是因为var及那个i定义在了外部作用域中(函数作用域或者全局作用域),当setTimeout还没有开始执行,for循环已经执行完了,因为i并没有本地化,也就是定义在内部。因此当for执行完后i已经变成了5,最后打印也就是5个5.

使用let后输出正常,就是因为let声明了块级作用域将变量i绑定到了循环内部,每次循环都是新的变量i,而不是操作同一个变量i。

再看下面的例子:

var x = 1

function test() {

    console.log(x); // undefined

    var x = 2
}

test()
复制代码

例中x为什么不是打印1而是undefined呢? 这是因为在js中存在一个缺陷变量提升,在例子的函数test中,又定义了一个x,在预编译时会在函数test最前面先将x定义了但是没有赋值,因此实际上相当于x = 1被新定义的x覆盖了,但是又没有赋值,因此打印出了undefined。

使用let或者const可以避免变量提升,防止变量在不小心被覆盖。

形参作用域

函数接受形参的时候会参数一个临时的作用域,形参作用域。

可以看下面例子:

var x = 1

function test(a, y = function() {
    console.log(x); // 1
}) {
    y()
}
test()

function test1(x, y = function() {
    console.log(x); // undefined
}) {
    y()
}
test1()
复制代码

从例子看出将函数y当成函数test的形参,函数y中没有x参数因此会在外层函数中找,因此可以拿到全局作用域的x打印1

在test1中在参数中有x形参,但是没有值,这次函数y并没有打印出全局x,而是打印了形参x的值,因此可以知道形参x和y函数有一个共同的作用域形参作用域。

作用域链

1669410615164.png

作用域是可以嵌套的层级结构,内层作用域在调用变量或者表达式时,会先从自己的作用域中找,没有找到就会一层一层往外的作用域找,这种查找方式形成的链条就叫作用域链。