✨JS作用域【学习笔记】✨

101 阅读6分钟

OK啊,老铁们!今天我们要了解的是js中的作用域。作用域是指在程序中定义变量的区域,决定了这些变量在代码中的可见性和访问性。作用域规定了变量在哪里和如何被查找,我们今天要了解的有全局作用域函数作用域块级作用域欺骗词法作用域

全局作用域&&函数作用域

var a = 1

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

foo()

// 执行结果: 1

执行引擎v8执行代码时是从上往下执行的,在这段代码中v8会先看到var a = 1一个变量a的值为1,然后看到function foo(){ console.log(a); }一个函数的声明,这里只是定义了一个函数,并不会去执行它,最后v8看到foo()一个函数的调用,这时执行引擎便会在整段代码中去找代码中有没有foo()这个函数,找到了这个函数之后才开始去执行function foo(){ console.log(a); }这个函数

image.png 可以把function foo(){ console.log(a); }比喻为一个箱子,foo()比喻为箱子的钥匙,在拿到箱子的钥匙之前,我们拿到箱子(函数),只是看见了箱子的外形(函数声明),需要拿到钥匙之后才可以看到箱子的内部(函数内部),而像定义在全局的var a = 1 就可以比喻为一个碗,在拿到碗的时候就可以看见碗里装的东西了,不需要钥匙

从这里我们便可以引出一个全局作用域和函数作用域的概念

e5810607d77b2cb8eb53320e7f4e3a5.png

图中红色框部分是全局作用域,全局作用域是整个程序中声明的变量和函数都可以被访问的范围蓝色部分是函数作用域,函数作用域是指在函数内声明的变量只能在该函数内访问在这段代码中foo()中的console.log(a);在函数作用域内,是可以访问到全局作用域中的var a = 1,是因为变量的查找是可以从内部作用域往外部作用域查找的,不能由外到内查找

function foo(){
    var c = 1
}

foo()

console.log(c);

// 报错

来看一下一个由外部作用域往内部作用域查找的错误例子,在这段代码中,全局作用域中的console.log(c);要去访问到函数作用域中的c,执行结果为报错,因为变量的查找是可以从内部作用域往外部作用域查找的,不能由外到内查找 我们来想象这么一个场景,全局作用域相当于小区的公共区域,而函数作用域相当于小区的别人的家里,假设我丢了一百块钱,而我处在小区内,我只能在小区的公共区域(全局作用域)里去找这一百块钱,而不能跑到别人家(函数作用域)内去找这一百块钱

var a = 1

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

    bar()
}

foo()

//执行结果: 2

这段代码可以很好的帮我们理解由内部作用域往外部作用域查找变量,在bar中console.log(a);执行时,它需要去访问a的值,而bar中找不到a这个变量,它便会往外一层的作用域foo()的函数作用域中去找a,找到了var a = 2,这时便直接打印a的值为2,在这里,虽然在全局作用域中也有一个var a = 1,但是变量的查找是一层一层由内往外查找的,会先访问到var a = 2

var b = 1

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

foo(2)

// 执行结果:NAN

在这段代码中,调用foo时,执行引擎会进行对函数foo的编译,编译要做的是找到当前域中的有效标识符在调用时,foo(2)将2的值传给a,而没有把任何值传递给b,但在编译时,执行引擎找到了a和b两个有效标识符,所以现在在foo函数中可以找到b,便不会去找全局中那个值为1的b,但是没有把任何值传递给b,所以其实打印的是2+undefined,值为NAN,NAN是数字类型表示 "Not-a-Number",它是一个特殊的数值,用于表示不是有效数字的情况。当进行数学运算,而结果无法被定义或解释为有效的数字时,就会得到 NaN。例如,0 除以 0 或者将非数字字符串转换为数字时可能会得到 NaN。在某种程度上,NaN 是一个表示错误或未定义数学运算结果的标识符。

块级作用域

块级作用域是指由一对花括号({})包裹起来的代码块,这些代码块可以是在控制流结构(如条件语句或循环)中,也可以是在函数体内部。在块级作用域中声明的变量在该作用域内有效,超出这个范围就无法访问。

在ES6之前,JavaScript只有全局作用域和函数作用域,缺乏块级作用域。引入letconst关键字后,允许在块级作用域内声明变量,从而改变了这一情况。

使用letconst声明的变量将在包含它们的块级作用域内部有效,而不会被提升到函数或全局作用域。这样的特性有助于更细粒度地控制变量的可见性,避免了一些潜在的问题。

function example() {
    if (true) {
      var x = 10; // 不是块级作用域,会提升到函数作用域
      let y = 20; // 块级作用域,只在if语句内部有效
      const z = 30; // 也是块级作用域
    }
  
    console.log(x); // 10
    console.log(y); // ReferenceError: y is not defined
    console.log(z); // ReferenceError: z is not defined
  }

  example()

在这个例子中,x由于使用var声明,它的作用域提升到了整个函数作用域,而yz由于使用letconst,它们只在if语句块内部有效。

  1. let 和 const 的块级作用域: 使用 letconst 声明的变量具有块级作用域,它们只在包含它们的块(花括号 {} 内部)内有效。这有助于更好地控制变量的可见性。
  2. 避免变量污染: 块级作用域有助于避免变量污染的问题,即在不同的块级作用域中可以使用相同的变量名而不会相互影响。
  3. 更安全的代码: 使用块级作用域可以提高代码的安全性和可维护性,减少不必要的变量泄露和冲突。

欺骗词法作用域

"欺骗词法作用域"(Lexical Scope Cheating)是指在 JavaScript 中通过一些机制违反正常的词法作用域规则,从而达到在运行时改变作用域的目的。

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

foo('var b = 3', 1)

// 执行结果:1 3

在这个例子中,通过 eval 函数在函数 foo 中动态执行了字符串 var b = 3,从而在 foo 函数的词法作用域中创建了一个变量 b。这违反了正常的词法作用域规则,因为在 foo 函数的定义中并没有明确声明 b 变量

如果觉得有收获,别忘了给我点个赞哦