关于javascript:js高级之内存管理与闭包

37次阅读

共计 3613 个字符,预计需要花费 10 分钟才能阅读完成。

javacript 中的内存治理

javascript 中不须要咱们手动去分配内存,当咱们创立变量的时候,会主动给咱们分配内存。

  • 创立根本数据类型时,会在栈内存中开拓空间寄存变量
  • 创立援用数据类型时,会在堆内存中开拓空间保留援用数据类型,并将堆内存中该数据的指针返回供变量援用
    var name = "alice"
    var user = {
        name: "kiki",
        age: 16
    }

申明两个不同类型变量在内存中的表现形式如下

垃圾回收机制

内存是无限的,当某些内存不须要应用的时候,咱们须要对其开释,以腾出更多的内存空间,在 javascript 中有两种垃圾回收算法。

1. 援用计数

当对象有援用指向它的时候,计数减少 1,打消指向就缩小 1,当计数为 0 时,对象会主动被垃圾回收器及销毁

这样的回收机制可能存在问题,当两个对象循环援用时,这两个对象都不会被销毁,可能存在内存透露的状况

2. 标记革除

设置一个根对象,垃圾回收器会定期从这个根开始,找所有从根开始有援用到的对象,销毁没有援用到的对象

这样一种算法能够比拟无效的解决循环援用的问题,下图中 MN 从根节点中无奈找到有援用的对象,所以会被垃圾回收器销毁

函数的多种用处

在 javascript 中,函数是十分重要且利用宽泛的,它最罕用有以下几种

1、作为参数传递
函数能够间接作为另一个函数的参数,并且间接调用执行,以下定义了多种计算数字的办法,加减乘,进行不同的运算不须要每次调用不同的函数,只须要扭转传参即可

function calcNum(num1, num2, fn) {console.log(fn(num1, num2))
}
function add(num1, num2) {return num1 + num2}
function minus(num1, num2) {return num1 - num2}
function mul(num1, num2) {return num1 * num2}
calcNum(10, 20, add)  // 30

2、作为返回值
函数也能够返回一个函数,以下函数叫做高阶函数,也成为函数柯里化,能够屡次接管返回并进行对立的解决

function makeAdder(count) {function add(num) {return count + num}
  return add
}
var add5 = makeAdder(5)
console.log(add5(6))
console.log(add5(10))

var add10 = makeAdder(10)
var add100 = makeAdder(100)

3、作为回调函数
在数组中有很多办法都须要咱们自定义回调函数来解决数据

var nums = [10, 50, 20, 100, 40]
var newNums = nums.map(function(item){return item * 10}) 

闭包

如果一个函数,能够拜访到外层的自在变量,那么它就是闭包

如以下代码所示,bar 函数能够拜访到父级作用域的变量 name 和 age

function foo(){
    var name = "foo"
    var age = 18
    function bar(){console.log(name)
        console.log(age)
    }
    return bar
}
var fn = foo()
fn()

以上代码执行后果为

foo
18

依照代码的执行程序来说,foo 函数被执行实现,它的函数上下文曾经从栈中弹出,而 foo 函数中的变量为什么还能被保留下来?

因为 foo 函数执行上下文创立的时候,同时创立 AO 对象,AO 对象依然被 bar 函数的 parentScope 所指向,所以不会被垃圾回收器销毁

以上代码在内存中的执行过程如下

  1. Javascript –> AST

    • 在内存中开拓空间 0x100 保留函数 foo,其中保留父级作用域(parentScope)指向 GO
    • 内存中存在 GO(Global Object)对象,其中包含了内置的模块如 String、Number 等,同时将定义的全局变量保留至 GO 中,这里将 fn 增加到 GO 中,值为 undefined,函数 foo 增加到 GO 中,值为 0x100
  2. Ignition 解决 AST

    • V8 引擎执行代码时,存在调用栈 ECStack,创立全局执行上下文,VO 指向 GO
    • 创立函数 foo 的函数执行上下文,**VO(variable object)指向 foo 的 AO(
      active object)**,执行函数体内代码
    • 创立 foo 的 AO 对象,将 name 和 age 增加到 AO 中,值为 undefined,
    • 执行代码前,给 foo 内的函数 bar 开拓内存空间 0x200,bar 函数的父级作用域指向 AO
    • 将 foo 增加到 AO 对象中,值为 0x200
  3. 执行代码

    • 函数 foo 的返回值 bar 函数赋值给 fn,所以fn 的值为 bar 函数的内存地址 0x200
    • 执行 foo 函数,将 foo 的 AO 对象中的 name 赋值为 foo,age 赋值为 18
    • 执行 fn 函数前,bar 函数创立函数执行上下文,VO 指向 bar 的 AO
    • 创立 bar 的 AO,bar 函数内没有定义变量,所以 AO 为空
    • 执行 fn 函数,输出 name 和 age
  4. 执行实现

    • foo 函数被执行实现,foo 函数的执行上下文弹出调用栈
    • bar 函数被执行实现,bar 函数的执行上下文弹出调用栈
    • bar 的 AO 对象是函数执行上下文存在时创立,此时也没有被其它中央援用,所以会被垃圾回收器销毁
    • bar 函数赋值给了全局变量,不会被销毁,并且 bar 的父级作用域指向 foo 的 AO 对象,因而foo 的 AO 对象也不会被销毁,所以在 bar 函数中能拜访到 foo 中的变量

图示如下

AO 优化

以上 foo 的 AO 对象有被援用,所以没有销毁,如果此时 AO 对象只是局部变量被援用,而其它变量没有用到呢,那没有用到的变量会被销毁吗?比方以下 foo 函数的变量 age

function foo(){
  var name = "foo"
  var age = 18
  return function(){console.log(name)
  }
}
var fn = foo()
fn()

依照 ECMAScript 标准是不会的,因为整个 AO 对象都被保留在内存中了,然而 JS 引擎可能会做一些优化,比如说 Chome 浏览器应用的 V8 引擎

在以上闭包中减少 debugger 进行调试

function foo(){
  var name = "foo"
  var age = 18
  return function(){
    debugger
    console.log(name)
  }
}
var fn = foo()
fn()

能够用两种办法测试到 foo 的变量 age 没有被保留下来

1. 在 Sources 中查看 Closure 保留的变量
代码执行到 debugger 处,能够查看到闭包此时的作用域,父级作用域 foo 中只保留了变量 name

2. 在 Console 中输入变量
当代码执行到 debugger 处,此时 Console 就是在闭包的执行环境中,能够间接打印变量,name 能够间接被打印进去,而打印 age 则间接保留未定义

内存透露

如上述例子中被保留到全局的闭包,因为有相互的援用,不会被销毁,如果后续不再应用,就可能呈现内存透露的状况。
用以下代码测试一下

function createFnArray() {var arr = new Array(1024 * 1024).fill(1)
  return function () {console.log(arr.length)
  }
}

var arrayFns = []
for (var i = 0; i < 100; i++) {setTimeout(() => {arrayFns.push(createFnArray())
  }, i * 100)
}

setTimeout(() => {for (var i = 0; i < 50; i++) {setTimeout(() => {arrayFns.pop()
    }, 100 * i)
  }
}, 10000)

以上代码每隔 0.1s 创立一个内存容量为 1024*1024 的数组(约 4M)保留到全局变量中,共计 100 个,再隔 10s 后将每隔 0.1s 从数组底部弹出一个元素,共计 50 个。

这样操作在内存中的体现应为前 10s 陆续减少内存的应用,第 10s 时,内存占用约为 400M,等到 15s 后,内存占用缩小一半,因为垃圾回收器不会马上回收或销毁垃圾,所以可能会有肯定的时间延缓

开释内存

内存的大量占用会造成内存透露,当不须要应用的时候,要及时的开释,只须要将变量指向 null,即可开释内存

var fn = foo()
// 无需应用时
fn = null

以上就是对于内存和闭包的了解,对于 js 高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~

正文完
 0