程序内存图.

程序从启动到运行的过程

  • 通过双击或者命令启动程序
  • 将程序代码加载到内存中
  • 编译器对程序代码转译成二进制代码
  • 内存根据编译好的内容,给程序提供足够的内存空间运行

下图是java程序的内存示意图

浏览器

当我们打开一个浏览器的时候,系统就会为它分配一定内存空间供它使用.然后浏览器再为执行在它上面的一些特性,例如html,css,js,ajax,为这些内部程序提供相应的内存空间.

JS堆栈

js程序在运行的过程中主要涉及到两个内存块

  • 堆区 heap
  • 栈区 stack

栈区 stack

栈区,也可以叫做 栈内存.

  • 基本类型是保存在栈内存中的简单数据段,它们的值都有固定的大小,保存在栈空间,通过按值访问
  • 函数在栈区执行
  • 遵循后进先出(LIFO)原则,新添加或待删除的元素都保存在栈的末尾,称作栈顶,另一端称作栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。

JS变量的声明

我们可以从一个简单的例子开始

var num = 110;

当执行这段代码的时候,JS将会执行以下流程:

  • 为变量num创建一个标识符
  • 在内存中未改标识符分配一个地址
  • 将110这个值存储到该内存中

如下图:

函数在栈内存中的执行

function fn1(){
console.log("函数1")
}
function fn2(){
console.log("函数2");
}
fn1();
fn2();

嵌套函数调用

function fn1(){
console.log("函数1")
}
function fn2(){
    fn1();
    console.log("函数2");
}
fn2();

栈溢出

由于浏览器分给程序的内存空间是有限.而从有限的空间里分出堆栈也是有限的.当我们无限制地往栈内存压入函数执行,则很快就会导致内存空间用完,出现栈溢出的问题,

  • 当我们使用递归而不给递归设置结束条件,会导致栈溢出
      function fn(){
          fn();
      }
      fn();
    

变量和引用类型数据

var meidi = {
    brand:"美的",
    price: 1600
}

以上代码在运行时经历以下步骤

  • 为meidi创建一个标识符 meidi
  • 在栈内存中为meidi分配一块内存空间
  • 在堆内存中分配一个内存空间和地址
  • 在分配的内存空间中存储分配的值 {}

引用变量赋值

var meidi = {
    brand:"美的",
    price: 1600
}

var meidi2 = meidi;

以上代码在运行时经历以下步骤

  • 为meidi创建一个标识符 meidi
  • 在栈内存中为meidi分配一块内存空间
  • 在堆内存中分配一个内存空间和地址
  • 在分配的内存空间中存储分配的值 {}
  • 为meidi2创建一个标识符 meidi2
  • 将meidi存储的内存地址赋值给meidi2
var meidi = {
    brand:"美的",
    price: 1600
}
function fn(obj){
    var bridge = obj;
    brige.brand = "奥玛";
}
fn(meidi)

以上代码在运行时经历以下步骤

  • 预编译fn函数,为obj创建一个唯一标识符
  • 为meidi创建一个标识符 meidi
  • 在栈内存中为meidi分配一块内存空间
  • 在堆内存中分配一个内存空间和地址
  • 在分配的内存空间中存储分配的值 {}
  • 将meidi存储的内存地址赋值给obj
  • 将obj存储的内存地址赋值给bridge
  • 修改bridge的内存地址指向的对象里的brand;

引用类型数据拷贝

  • 深拷贝
  • 浅拷贝

深拷贝

将对象里的所有属性递归赋值一份出来,存储到另外一个新的内存空间里.形成另外一个完全无关的对象.

实现深拷贝

var meidi = {
    brand:"美的",
    price: 1600,
    colors: ["red","blue"]
}
function deepClone(obj) {
    var target = {};
    for(var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            if (typeof obj[key] === 'object') {
                target[key] = deepClone(obj[key]); 
            } else {
                target[key] = obj[key];
            }
        }
    }
    return target;
}

var newB = deepClone(meidi);
console.log(newB);  
console.log(newB.colors === meidi.colors);

浅拷贝

浅拷贝是拷贝引用, 拷贝后的引用都是指向同一个存放数据位置的指针, 无论操作哪一个都是在操作指向在堆中的那一个数据, 所以双方都会有影响

内存管理

由于分配JS的内存是有限的.因此对有限的内存进行管理是非常有必要的.否则程序将难以正常执行.

垃圾回收引用计数

这是最简单的内存垃圾回收算法。当一个对象被 0 引用,会被标记为 "可回收内存垃圾"。

var o1 = {
  o2: {
    x: 1
  }
};

// 创建两个对象。
// 'o1' 引用对象 'o2' 作为其属性。全部都是不可回收的。

// 'o3' 是第二个引用 'o1' 对象的变量
var o3 = o1;

o1 = 1; // 现在,原先在 'o1' 中的对象只有一个单一的引用,以变量 'o3' 来表示

// 引用对象的 'o2' 属性。
// 该对象有两个引用:一个是作为属性,另一个是 'o4' 变量
var o4 = o3.o2;

// 'o1' 对象现在只有 0 引用,它可以被作为内存垃圾回收。
// 然而,其 'o2' 属性仍然被变量 'o4' 所引用,所以它的内存不能够被释放。
o3 = '374';

o4 = null;
// 'o1' 中的 'o2' 属性现在只有 0 引用了。所以 'o1' 对象可以被回收。

标记-清除算法

为了判断是否需要释放对对象的引用,算法会确定该对象是否可获得。

标记-清除算法包含三个步骤:

  • 根:一般来说,根指的是代码中引用的全局变量。就拿 JavaScript 来说,window 对象即是根的全局变量。Node.js 中相对应的变量为 "global"。垃圾回收器会构建出一份所有根变量的完整列表。
  • 随后,算法会检测所有的根变量及他们的后代变量并标记它们为激活状态(表示它们不可回收)。任何根变量所到达不了的变量(或者对象等等)都会被标记为内存垃圾。
  • 最后,垃圾回收器会释放所有非激活状态的内存片段然后返还给操作系统。

results matching ""

    No results matching ""