JS 基础语法

2025-12-22

本文参考内容 https://www.bilibili.com/video/BV1fGBFBZE6f/open in new window

行末分号

以下字符开头的代码,前一行行末不能省略分号,可以在行首加上

  • [ (方括号)
  • ( (圆括号)
  • ` (反引号)
  • / (斜杠)
  • +, - 等一元或二元运算符

以下是部分常见的行首增加分号的情况

  • 使用 ES6 解构对变量进行交换
    // 数组乱序
    const arr = [1, 2, 3, 4, 5, 6, 7]
    for(let i = arr.length - 1; i > 0 ; i--) {
        const j = Math.floor(Math.random() * (i+1))  // 0-i 的一个索引值,实现可能交换或不交换
        ;[arr[j], arr[i]] = [arr[i], arr[j]]  // 数组值交换,类似 py - 此处由于开头满足条件,在行首使用分号
    } 
    
  • 自执行函数
    ;(function(){
        console.log(1)
    })()
    ;(function(){
        console.log(1)
    }())
    
  • 非数组对象调用数组方法
    let res = ''
    const str = 'abc'
    ;[].forEach.call(  // `[` 开头,需要分号
        str, c => {res += "\\x" + c.charCodeAt(0).toString(16)}
    )
    console.log(res)
    

作用域

JS 中的作用域说明

代码块

在介绍作用域之前,先了解代码块。以下是三种不同类型的代码块

  • 普通代码块

    • 可以在任何位置使用代码块。以下代码可以正常运行
      {
          console.log(1)
      }
      
    • 可以随意嵌套代码块。以下代码也可正常运行
      {
          {
              {
                  {
                      {
                          console.log(2)
                      }
                  }
              }
          }
      }
      
  • 语句代码块

    for(i = 0; i < 10; i++) {  // for语句的代码块
        console.log(i)
    }
    
  • 函数代码块: 函数在声明时也使用了 {}。函数的块是块的特例

    // 函数每次调用时,会开辟新的空间。因此 **函数内声明的变量** 每次都是全新的。多次调用函数时,变量互不影响
    function test(input) {
        const a = input
        console.log(a)
    }
    test(1)  // 创建 input,创建了 a 接收 input 的值,调用结束后销毁
    test(2)  // 创建了新的 input 与 a
    

变量作用域

  • constlet 声明的变量作用域在其所在的代码块中(代码块中定义,代码块结束时销毁)。如果没有在代码块中,则为全局作用域
    • 例1: 普通代码块
      {
          const a = 1; // 出了该定义语句所在块,则该声明失效
          {
              const b = 2 ;  // 内层块内有效
          }  // 此时销毁 b
          console.log(a)
          // console.log(b)  // 这里会报错
      }  // 此时销毁 a
      // 本例中 由于该块内的子块也属于该块,所以内层块中 a 也有效
      
    • 例2: 语句代码块
      //   i 在 for 语句内,且该语句含有代码, i 的作用域就是 for 语句的代码块
      for(let i = 0; i < 10; i++) {  //与别的 for 语句代码块中的 i 不冲突
          console.log(i)
      }  // 此时销毁 i
      // console.log(i)  // 这里会报错
      
    • 例3: 代码块内不可重复定义
      // 重复声明报错
      let a = 1
      let a = 2  // 报错:Identifier 'a' has already been declared
      
  • var 定义变量。特性:1. 作用域至少为函数语句块 2. 升格到作用域头部 3. 可重复声明同一变量,会覆盖值
    • 例1: 函数代码块
      function testVar() {
          console.log(a)
          {
              var a = 1
              console.log(a)
          }
          console.log(a)
      }
      
      // 由于 var 的作用域是 函数,所以 var 声明会跳出非函数的块。变成如下样子
      /*
      function testVar() {
          var a  // 1. 升格到函数作用域开始处
          console.log(a)  // 3. 还没赋值,输出 undefined
          {
              a = 1  // 2. 赋值保留原始位置
              console.log(a)
          }
          console.log(a)
      }
      */
      
  • 普通代码块 与 语句代码块 中无关键字的变量声明都是 全局变量(函数代码块中会报错)
    {
        g1 = 1
        {
            g2 = 2
        }
    }
    console.log(g1, g2)  // 普通代码块内的全局变量
    for (i = 0; i<10; i++){
        ;
    }
    console.log(i)  // 语句代码块内的全局变量
    

函数作用域

// 1. 变量形式声明的函数作用域符合变量作用域逻辑 // 例如 var 定义函数,就是 var 变量,升格,但赋值位置不变 function func() { console.log(test) // 由于升格会输出 undefined var test = function () {} } func()

// 2. function 关键字定义的函数,作用域类似 var,至少为函数代码块,且升格到作用域头部

// 2.1 函数内容(赋值)如果执行,也会升格 { function func() { // func 会升格到全局 console.log(test) // 由于升格会正常输出一个函数对象,且有函数体,即使定义在后方. console.log(test()) // 由于升格,可正常执行 test 内的 console.log(a) function test() { const a = 1 console.log(a) } } } func() // 此处可正常调用func

// 2.2 函数内容未执行的升格,该函数声明无法执行 if (false) { // func 会升格,但该函数声明不被执行,因此 func 无法完成赋值 function func() {const b = 2} } console.log(func) // 同 var 变量,输出 undefined

作用域链

观察以下示例代码

const x = 1
{  // block 1
    const a = 1
    {  // block 2
        const a = 2
        {  // block 3
            const b = 3
            for (i = 1; i < 10; i++){  // block 4
                const c = 4
                console.log(i, a, b, c, d, x, y)
            }
        }
    }
}

说明: 当代码执行到 console.log(a, b, c, d, x, y) 时,解释器会先在其所在的作用域,即 block 4 中寻找对应变量。如果没有,则往父层作用域中寻找,依次向上,直至全局作用域,寻找目标变量。找到则停止该变量的查找,找不到则报错未定义

本例中几个特殊变量

  • y: 一直往上无法找到。则报错未定义
  • a: 由于依次往上,先找到值为 2 ,因此 a 会是 2。即使 a = 1 对子层来说也是作用域,但 a = 2 会被先找到

此处依次向上查找的过程 block 4 -> block 3 -> block 2 -> block 1 -> 全局 就是作用域链

代码执行到任何位置都会有作用域链,此时可访问作用域链上的所有变量(同名时,内层会覆盖外层,例如本例中指定到最里边时的 a 变量)