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高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~

js高级之内存管理与闭包的更多相关文章

  1. 「中高级前端必须了解的」JS中的内存管理

    前言 像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()用于分配内存和释放内存. 而对于JavaScript来说,会在创建变量(对象,字符串等)时分配内存,并且在不 ...

  2. setter 和 getter 高级 以及内存管理初级

    setter 和 getter 的演变,紧接setter 和 getter 初级 1.@property 和  @synthesize 这两个关键字的出现,就是为了剔除代码中的setter方法和get ...

  3. js 作用域链&内存回收&变量&闭包

    闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等 一.作用域链:函数在定义的时候创建的,用于寻找使用到的变量的值的一个索引,而他内部的规则是,把函数自身的本地变量放在 ...

  4. JS性能方面--内存管理及ECMAScript5 Object的新属性方法

    Delete一个Object的属性会让此对象变慢(多耗费15倍的内存) var o = { x: 'y' }; delete o.x; //此时o会成一个慢对象 o.x; // var o = { x ...

  5. js高级-变量内存分析

    var  a = 9, b, c={age:9}, d; b = a; b = 19; console.log(a) console.log(b) d = c; d.age = 22; console ...

  6. 闭包 与 js内存管理

    参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Memory_Management            https://blog ...

  7. 【JS】324- JS中的内存管理(中高级前端必备)

    前言 像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()用于分配内存和释放内存.而对于JavaScript来说,会在创建变量(对象,字符串等)时分配内存,并且在不再 ...

  8. JS高级:闭包

    1 如何产生闭包? 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时, 就产生了闭包(closure) 2 闭包到底是什么? 使用chrome调试查看 理解一: 闭包是嵌套的内部函 ...

  9. 转---单页面应用下的JS内存管理

    正文从这开始- 内存问题对于后端童鞋而言可能是家常便饭,特别是C++童鞋.我在实习时做过半年的c++游戏客户端开发(也是前端开发哦),也见识了各式各样的内存问题,就说说我的第一个坑,当时做个需求,就是 ...

  10. Tensoflw.js - 02 - 模型与内存管理(易懂)

    Tensoflw.js - 02 - 模型与内存管理(易懂) 参考 W3Cschool 文档:https://www.w3cschool.cn/tensorflowjs/ 本文主要翻译一些英文注释,添 ...

随机推荐

  1. 掌握这些GitHub搜索技巧,你的开发效率将翻倍!

    作为开发it行业一员,学习借鉴他人项目是很有必要的,所以我们一般都会从github或者 Gitee 上面去参考借鉴他人的项目来学习增加自己的项目经验 但是github你真的用对了嘛,他的功能其实很强大 ...

  2. Grafana 系列-统一展示-3-Prometheus 仪表板

    系列文章 Grafana 系列文章 知识储备 Prometheus Template Variables 你可以使用变量来代替硬编码的细节,如 server.app 和 pod_name 在 metr ...

  3. C++ ATL + WTL 选择文件

    1 #include "stdafx.h" 2 #include "CStringHelper.h" 3 #include "AFileEngine. ...

  4. Protobuf: 高效数据传输的秘密武器

    当涉及到网络通信和数据存储时,数据序列化一直都是一个重要的话题:特别是现在很多公司都在推行微服务,数据序列化更是重中之重,通常会选择使用 JSON 作为数据交换格式,且 JSON 已经成为业界的主流. ...

  5. 2020-11-06:go中,谈一下调度器。

    福哥答案2020-11-06:·MPG模型:goroutine的并发模型可以归纳为MPG模型:·MPG概念:线程(machine,系统线程,物理线程)-内核(processor)-协程(gorouti ...

  6. 2022-05-16:A -> B,表示A认为B是红人, A -> B -> C,表示A认为B是红人,B认为C是红人,规定“认为”关系有传递性,所以A也认为C是红人, 给定一张有向图,方式是给定M个有

    2022-05-16:A -> B,表示A认为B是红人, A -> B -> C,表示A认为B是红人,B认为C是红人,规定"认为"关系有传递性,所以A也认为C是红 ...

  7. 2021-10-29:除自身以外数组的乘积。给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之

    2021-10-29:除自身以外数组的乘积.给你一个长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i ...

  8. 「P2」试下1个半月能不能水出个毕设

    0.目标 将上个 springboot 项目 + html 中的html用Vue来重写,也就是在原springboot项目中集成Vue 1.在界面上,将html改成vue的形式 1.1.原html & ...

  9. 【LeetCode双向链表】LRU详解,双向链表实战

    LRU缓存 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构. 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity ...

  10. HashMap 以及多线程基本感念

    接口 Map :映射项,(键值对 ) 的容器注意: 键 是唯一的 值 是可以重复的实现类 HashMap :哈希表结构 允许使用null值 和 null 键 线程不安全 键唯一 无序 linkedHa ...