Skip to content

堆内存释放机制

如果当前堆内存的16进制地址,被其他事物所引用,则堆内存不能释放掉(强引用) 如果当前堆内存不被其它东西所占用,浏览器会在空闲的时候查找每一个内存的引用情况, 那些未被引用都会被回收释放。

js
const o = {} // 堆内存不会被释放,因为被o给占用了
o = null // 释放

栈内存释放机制

全局上下文是加载页面的时候产生的,只有刷新/关闭页面的时候才释放(刷新是销毁后又开辟一个) 私有上下文(函数/块)一般情况下执行完都会出栈释放掉,因此来优化栈内存,但是有特殊情况, 如果当前上下文中创建的某个东西(一般都是堆内存)被上下文以外的其他事物给占用了, 那么当前上下文不能被释放 特点 1.消耗内存(慎用) 2.由于不被释放,私有上下文中的东西都被保留下来了,以后可以拿来用

js
function fn () {
  return function () {

  }
}
const f = fn() // 这是闭包,return的地址值被f占用,不能释放

function fn () {
  document.body.onclick = function () {

  }
}
fn() // 这也是闭包,fn上下文中的堆内存地址被占用,不能释放

function fn () {}
fn() // 私有上下文执行完销毁

闭包:函数执行产生一个全新的私有上下文,并且这个私有上下文不能被释放掉 1.保护:保护里面的私有变量不受外界的干扰,防止全局变量污染 2.保存:私有上下文不被释放,里面的私有变量和值就保存起来了,可以供下级上下文使用

闭包应用: 单例模式 柯里化 组合函数 惰性函数

js
// example1
let x = 5
const fn = function fn (x) {
  return function (y) {
    console.log(y + (++x))
  }
}
let f = fn(6)
f(7) 
fn(8)(9) 
f(10) 
console.log(x)

// example1
var i = 5
function fn (i) {
  return function (n) {
    console.log(n + (++i))
  }
}
var f = fn(1)
f(2) 
fn(3)(4)
fn(5)(6) 
f(7) 
console.log(i)

// example2
var i = 20
function fn () {
  i -= 2
  return function (n) {
    console.log((++i) - n)
  }
}
var f = fn()
f(1) 
f(2) 
fn()(3) 
fn()(4)
f(5) 
console.log(i)
js
// example2
let a = 0,
  b = 0
function A(a) {
  A = function (b) { // 函数重构 原来的私有上下文中的地址值被A(A是上下文外面的东西)占用,上下文不会被释放
    console.log(a + b++)
  }
  console.log(a++)
}
A(1)
A(2)

// example
function fn (i) {
  return function (n) {
    console.log(n + (i++))
  }
}
var f = fn(10)
f(20)
fn(20)(40)
fn(30)(50)
f(30)

// example
var num = 10
var obj = { num: 20 }
obj.fn = (function (num) {
  this.num = num * 3
  num++
  return function (n) {
    this.num += n
    num++
    console.log(num)
  }
})(obj.num)
var fn = obj.fn
fn(5)
obj.fn(10)
console.log(num, obj.num)

// example
function fun(n, o) {
  console.log(o)
  return {
      fun: function (m) {
        return fun(m, n);
      }
  };
}
var a = fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);
c.fun(2);
js
// example3
// 下面代码是否可以每隔1000ms依次输出0 1 2 3 4
// 如果不可以,是为什么?如何解决
for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i)
  }, (i + 1) * 1000)
}

// 不行,i是全局变量,定时器回调执行的时候循环已经结束了,i = 5

// 方法1
for (var i = 0; i < 5; i++) {
  (function (i) {
    // 闭包,定时器回调被window引用
    setTimeout(function () {
      console.log(i)
    }, (i + 1) * 1000)
  })(i)
}

// 方法2
for (var i = 0; i < 5; i++) {
  // 大函数是自执行,所以能接收到参数,执行上下文产生私有i
  // 同时小函数被window引用,形成闭包,上级上下文是自执行的上下文
  setTimeout((function (i) {
    return function () {
      console.log(i)
    }
  })(i), (i + 1) * 1000)
}

// 等价于上面
var fn = function (i) {
  return function () {
    console.log(i)
  }
}
for (var i = 0; i < 5; i++) {
  // 大函数是自执行,所以能接收到参数,执行上下文产生私有i
  // 同时小函数被window引用,形成闭包,上级上下文是自执行的上下文
  setTimeout(fn(i), (i + 1) * 1000)
}

// 方法3
// 创建长度为5的数组并把其变为密集数组,这样才能forEach
new Array(5).fill(null).forEach((_, index) => {
  // 小函数被window引用,闭包
  setTimeout(function () {
    console.log(index)
  }, (index + 1) * 1000)
})

// 方法4 私有块级上下文
for (let i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i)
  }, (i + 1) * 1000)
}

// 方法5 终极方案 定时器第三个及以后的参数都是传给回调的实参
// 原理:定时器回调定义好了,把实参先存储在一个其他位置中 => 核心原理也是闭包
// 时间到了后把回调执行,形参为之前预先存储起来的值
for (var i = 0; i < 5; i++) {
  setTimeout(function (i) {
    console.log(i)
  }, (i + 1) * 1000, i)
}