# js里的作用域

作用域(scope)就是变量访问规则的有效范围

  • 全局变量的作用域是全局的
  • 函数作用域:一个变量在全函数里有效
  • 块作用域:代码块里有效;{} 限定变量的作用域范围

# 理解词法作用域和动态作用域

词法作用域也称静态作用域,javascript 采用静态作用域

  • 静态作用域 —— 函数的作用域基于函数创建的位置。
  • 动态作用域 —— 函数的作用域基于函数的使用位置。

# 闭包

闭包:能够读取其他函数内部变量的函数。(应用场景:要获取某函数内部的局部变量)

闭包的优点:

  1. 能够读取函数内部的变量
  2. 让这些变量一直存在于内存中,不会在调用结束后,被垃圾回收机制回收

闭包的缺点:正所谓物极必反,由于闭包会使函数中的变量保存在内存中,内存消耗很大,所以不能滥用闭包, 解决办法是,退出函数之前,将不使用的局部变量删除。

javascript 删除变量:你可以为其赋值为空值,比如 undefinednull 就相当于删除了(标记清除)

闭包其实就是作用域范围,然后 js的作用域是函数作用域,所以闭包也是函数, 本质是 父子函数的引用关系:父函数包含子函数,子函数因为函数作用域又引用父函数,这是个死结; 由于相互引用,会引起内存泄漏,不用的时候把引用设为null,内存释放,死结解开

应用场景:

  1. 匿名自执行函数
  2. 结果缓存
  3. 封装局部变量

# 垃圾回收

垃圾回收其实就是:GC机制

javascript垃圾回收有两种方法:标记清除、引用计数。引用计数不太常用,标记清除较为常用。

标记清除: 其实就是图的拓扑排序,查找连通图

引用计数有个最大的问题:循环引用

function func() {
    let obj1 = {};
    let obj2 = {};

    obj1.a = obj2; // obj1 引用 obj2
    obj2.a = obj1; // obj2 引用 obj1
}
1
2
3
4
5
6
7

解决循环引用的问题,最好是在不使用它们的时候手工将它们设为空。

obj1 = null;
obj2 = null;
1
2

更多GC相关:

  • 经典的GC算法有三种: 引用计数(reference counting)、 标记-清扫(mark-sweep)、 复制收集(CopyandCollection)。
  • Java VM采用的Mark Sweep算法
  • Golang的GC算法主要是基于标记-清扫(markandsweep)算法,并在此基础上做了改进=>三色标记法

# 标记-清扫(Mark And Sweep)算法存在的问题

  • STW,stop the world;让程序暂停,程序出现卡顿。
  • 标记需要扫描整个heap
  • 清除数据会产生heap碎片

这里面最重要的问题就是:mark-and-sweep 算法会暂停整个程序

如何优化?

三色标记法:通过白色、灰色、黑色三色标记,最后只剩黑色和白色,清除白色;使清除操作和用户逻辑可以并发

# 内存泄漏

# 哪些情况会引起内存泄漏?

有了GC同样会出现内存泄露问题!比如如下场景

  1. 全局对象
    • 在 JavaScript 文件头部加上 'use strict'
    • 静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。
  2. 各种IO连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。
  3. 遗忘的计时器或回调函数
    • 计时器和计时器内部引用的资源都不会被释放
  4. 监听器的使用,在释放对象的同时没有相应删除监听器的
  5. 闭包里的局部变量
  6. 占用CPU过高,造成单线程阻塞,堆积内存过高

避免内存泄漏的一些方式:

  • 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收
  • 注意程序逻辑,避免“死循环”之类的
  • 避免创建过多的对象

总而言之需要遵循一条原则:不用了的东西要及时归还

# 如何定位内存溢出

定位内存泄漏,通常有两种情况

  1. 正常使用就可以重现的,在测试环境复现解决; 可以使用--inspect和chrome://inspect进行复现
  2. 偶然发生的内存泄漏,一定与某些特殊输入有关。如果不能通过代码日志定位到这个特殊输入,需要在生产环境打印内存快照
  • 快照工具推荐使用heapdump用来保存内存快照,
  • 使用devtool来查看内存快照

在nodejs中如何解决超出最大的调用栈错误

如何分析Node.js中的内存泄漏

平时工作中怎么调优的,怎么解决爆栈

  • 设置程序运行最大内存
  • 做好监控,重启,发现内存泄漏后使用--inspect和chrome://inspect进行定位,无法定位,打印内存快照分析,heapdump,devtool等工具

# 内存溢出解决方法

  1. 限制最高内存使用,--max-old-space-size--max-new-space-size参数来调整内存大小的使用限制;node --max-old-space-size=8192,意思是将内存调整到8G;node的堆内存上限大概在1.7G这块。
  2. 内存监控,当内存占用达到一定比例,采用优雅退出的方案重启进程

# this

  • this指的是函数运行时所在的环境
  • this指的是对象本身,而不是构造函数
var obj = {
  foo: function () { console.log(this.bar) },
  bar: 1
};

var foo = obj.foo;
var bar = 2;

obj.foo() // 1
foo() // 2
1
2
3
4
5
6
7
8
9
10

# apply,call和bind有什么区别

三者都是用来改变函数中this的指向

  • apply和call方法调用之后会立即执行,而bind方法调用之后会返回一个新的函数,它并不会立即执行,需要我们手动执行。
  • apply传参是数组,call,bind传参逗号隔开

# 为何try里面放return,finally还会执行,理解其内部机制

在 try 语句中,在执行 return 语句时,要返回的结果已经准备好了,就在此时,程序转到 finally 执行了。

在转去之前,try 中先把要返回的结果存放到局部变量中去,执行完 finally 之后,在从中取出返回结果。

因此,即使finally 中对返回的结果进行了改变,但是不会影响返回结果。

它应该使用栈保存返回值