作用域详解

167 阅读4分钟

作用域

作用域是什么

几乎所有的编程语言能够将值储存在某个变量中,并且能够对这个变量进行访问或者修改,变量存储在呢,我们该如何找到这些变量

我们需要一套规则来进行约束,这套规则就被称为作用域

编译阶段

parse阶段

解析源代码中的字符,按照一定的规则进行解析生成抽象语法树(AST)

const a = 'zgl'// 经过解析器的处理可能会解析成如下结构const: {
  type: 'keyword',
  value: 'const',
}
a: {
  type: 'identifier',
   value: 'a',
}
​
'zgl': {
  type 'string',
   value: 'zgl',
}

generation阶段

将生成的(AST)抽象语法树转换为可执行代码(机器指令)

const: {
  type: 'keyword',
  value: 'const',
} -> 
a: {
  type: 'identifier',
   value: 'a',
} -> 创建一个a的变量并且将 'zgl'存储到a的指令
​
'zgl': {
  type 'string',
   value: 'zgl',
} -> 在栈内存中开辟固定大小的内存来存储'zgl'的指令

变量查找规则

  • LSH查询

    编译器一般变量赋值阶段执行LSH查询,首先会根据赋值号左边的变量根据当前值的类型是基本数据类型还是引用数据类型进行内存空间的分配,然后会在栈内存中创建赋值号右边的变量,根据基本数据类型和引用数据类型进行相应的保存值或者地址值

    var a = 'zgl';
    ​
    // 我们来进行上面的代码分析,我们根据编译器进行拆分var a;
    a = 'zgl'//这部分就是LHS查询function getName(nmae) { // 将'zgl' 赋值给name的过程也是LHS查询
      return this.name;
    }
    ​
    getName('zgl')
    
  • RHS查询

    编译器一般查询变量阶段进行RSH查询,根据一些特定的规则进行变量查询

    var c = 'zgl';
    ​
    //  我们来进行上面的代码分析,我们根据编译器进行拆分var c;
    c = 'zgl' // 查找变量c的过程就是RHS查询function getName(nmae) { 
      return this.name;
    }
    ​
    getName('zgl') // 查找变量getName的过程就是RHS查询
    

    作用域规则

    无论进行LHS查询或者RHS查询都需要遵循一定的规则,而这一系列的规则就是由作用域制定的

    LHS规则

    • 规则1: 基本数据类型会在栈内存中开辟固定大小的内存空间进行存储
    • 规则2: 保存基本数据类型的变量其实是保存了基本数据类型在栈内存中开辟的内存值
    a = 'name';
    ​
    const stack = {}; // 模拟栈内存
    stack.name = 'name'; // 在栈内存中分配name这个值的内存
    a.value  = 'name'; // 将nam这个值绑定到变量a上
    
    • 规则3: 引用数据类型会在堆内存中开辟动态大小可变的内存空间进行存储
    • 规则4: 保存引用数据类型的变量其实是保存了引用数据类型在堆内存开辟的内存指针
    a = {
      name: 'zgl'
    }
    ​
    const stack = {}; // 模拟栈内存
    stack.address = '0x1111';// 在栈内存中分配堆内存分配对象的指针的内存
    const heap = {}; //模拟堆内存
    ​
    heap['0x1111'] = { // 在堆内存中进行内存分配
      name; 'zgl'
    }
    
    RHS规则
    • 规则1: 查找基本数据类型的时候,会在栈内存中进行变量值的查找

      const stack = {
        a: 'name'
      }
      ​
      console.log(a) => 在查找a变量的时候只会去栈内存中查找是否存在这个变量
      
    • 规则2: 查找引用数据类型的时候,会根据保存在栈中的指针来查找对应在堆内存的值

      const stack = {
        a: '0x11111'
      }
      ​
      const heap = {
        '0x11111': {
          name: 'zgl'
        }
      }
      ​
      console.log(a) => 在查找a变量的时候会先去栈内存中查找相应的指针然后再去查询堆内存对应的值
      
    • 规则3: 如果查找变量的时候没有查询到时会抛出异常

    console.log(b)  // Uncaught ReferenceError: b is not defined
    
    • 规则4: 如果查找到变量,尝试对这个变量的值进行不合理的操作会抛出异常

      const b = null;
      b.a  // Uncaught TypeError: Cannot read properties of null (reading 'a')
      

    作用域分类

    全局作用域

    var a = 'name1';
    ​
    // a在全局作用域中
    

    函数作用域

    function outer() {
      const a = 'name'
    }
    ​
    outer(); // a 在函数作用域中
    

    块级作用域

    for (let i = 0; i < 10 ; i++) {
      console.log(i)
    } // i在全局作用域中
    

    作用域嵌套

    当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。

    function getName() {
      function getAge() {
        for(let i = 0; i < 10; i++)
      }
    }
    

    当我们的作用域与作用域之间发生桥嵌套以后,我们需要为作用域嵌套时定义一套规则

    • 规则: 在当前作用域无法查找到某个变量时,会继续在此外层嵌套的作用域继续查找,直到找到该变量或者抵达全局作用域

      function getName() {
        const age = '24';
        function getAge() {
          console.log(age)
        }
        
        return getAge()
      }
      ​
      getName() // a: 'age' => 首先会现在getAge的作用域中进行查找,如果没有查找到继续会去getName作用域中进行查找,找到了就停止,否则会一直查询下去,直到全局作用域
      

\