4 javaScript中变量、作用域与内存

90 阅读5分钟

原始值与引用值

  1. 无论什么值,必须要将其转换成二进制然后保存在内存中
  2. 不同类型的值占据的内存大小是不一样
  3. 变量是栈内存别名,用于存放引用/地址以及部分原始值
  4. v8引擎优化中,如果原始值能直接放入内存槽中(如小整型12),则将值直接放入,而不需要去堆上开辟内存,然后将值放入变量中

js中的变量是固定栈内存槽别名,基本上存储的就是值的引用,值在js中有七种类型,但是这七种类型在拷贝赋值时不一样,分为原始值和引用值:

  1. 定义
    • 原始值:值会优先直接存入变量中,但如果存不下则放入堆中,变量也是存放引用,但是在性质和行为上与引用值不同,具体有以下类型Number、Boolean、String、Symbol、Undefined和Null的字面量
    • 引用值:变量中存放的是指向堆内存的引用,如Object类型.
特性原始值引用值
数据类型stringnumberboolean 等ObjectArrayFunction 等
可变性不可变可变
存储位置堆/栈内存(内联优化)堆内存
变量存储内容值本身(或堆地址,若需间接存储)堆内存地址
赋值行为复制值,新旧独立复制引用,共享对象
比较方式比较值是否相同比较引用是否指向同一对象
  1. 引用
    • 定义:变量别名

    • 本质:也是指针,只是其行为和普通变量一致

    • 引用并不占据内存,声明后必须立即赋值,之后不可更改

      注意js中变量存储的值是引用,cpp中变量是引用

      //cpp中引用完全就是别名,无论如何操作,两者都是同步的
      #include <iostream>
      class MyClass {
      public:
          int value;
          MyClass(int val = 0) : value(val) {} // 构造函数,初始化value
          void display() const { // 成员函数,用于输出value
              std::cout << "Value: " << value << std::endl;
          }
      };
      int main() {
          // 给obj赋值一个对象
          MyClass obj(10);
          // 给obj一个引用
          MyClass& ref = obj;//变量ref是引用,声明后立即赋值,且后续不可更改
          ref.display(); // 输出 "Value: 10"
          // 给obj重新赋值一个对象
          obj = MyClass(20);
          ref.display(); // 输出 "Value: 20",因为ref是obj的引用,所以会反映obj的最新状态
          return 0;
      }
      
      //js中引用是变量的值,虽然引用不可变,但是变量的值是可变的
      let a = {a:1};//a中存储的是对象{a:1}的引用
      let ref = a;//将引用赋值给ref
      a = {a:2};//a中的值变为对象{a:2}的引用
      console.log(ref.a);//但是ref中并没有改变
      
  2. 对引用值的操作
    1. 对引用值的操作,都是相当于对对象进行操作,如属性添加
    2. 引用值之间的传递(包括函数的传参)
    let obj1 = {a:1};
    let obj2 = obj1;//传递的是对象Object的引用
    

image.png

执行上下文与作用域

  1. 变量、函数都有上下文,通过上下文才能知道他们能访问哪些变量和函数;
  2. 每个上下文都有一个关联的变量对象,这个上下文中定义的所有变量、函数都会存在于这个对象上,如全局的window对象,但是注意函数内部的变量对象是无法直接访问的;
  3. 由于var变量提升,所以这个变量对象在开始运行时就已经将用var声明的函数、变量存入;
  4. 上下文中代码执行的时候,会创建变量对象的作用域链:正在执行的上下文的变量对象位于作用域链最前端,然后是包含上下文,以此类推
var color = "blue";
function changeColor(){
    let anotherColor = "red";
    function swapColors(){
        let tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }
    swapColors();
}
changeColor();

image.png
5. 作用域链增强:某些语句(try-catch中的catch/with)导致作用域链前端临时添加一个上下文,代码块执行完后会删除添加的上下文。如catch中,对作用域链增强以包含错误对象的声明与初始化。

try {
  throw new Error("Something went wrong!");
} catch (e) {
  console.log(e instanceof Error); // 输出:true,因为e是Error对象的一个实例
  console.log(e.message); // 输出:"Something went wrong!",访问异常对象的message属性
  console.log(e.name); // 输出:"Error",访问异常对象的name属性
  console.log(e.stack); // 输出异常的调用栈
}
  1. 变量声明
    • 如果在函数中直接使用没有声明的变量,则该变量会被添加到全局上下文中
    • var:存在变量提升、且全局上下文中执行声明会被添加到window对象中
    • let/const:存在变量提升,但由于临时性死区,可认为没有提升,且不允许声明多次;全局上下文中声明执行后变量并不会被添加到window对象中
    // 全局作用域
    var globalVar = 'I am a global variable using var';
    let globalLet = 'I am a global variable using let';
    const globalConst = 'I am a global variable using const';
    
    console.log(window.globalVar); // 输出:'I am a global variable using var'
    console.log(window.globalLet); // 输出:undefined
    console.log(window.globalConst); // 输出:undefined
    

垃圾回收

  1. 基本思路:确定哪个变量不再使用,然后释放它占用的内存;
  2. 方法:
    • 标记清理
      1. 标记内存中存储的所有变量
      2. 将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉
      3. 清除有标记的变量
    • 引用计数
      1. 每个 都有引用值,声明时赋值为1
      2. 值被另一个变量值引用时+1,反之-1
      3. 为0时清理
  3. 内存泄漏
    • 意外声明全局变量
    • 定时器
    • 闭包