关于javascript:前端性能优化篇之consolelog

62次阅读

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

前言

前端程序员简直每天不是在打 log 就是在打 log 的路上,最近就在本人实在我的项目中遇到了一个 console.log 的坑,由此记录一下 console.log 过程中你须要理解的常识,少走弯路。

console.log 真的是异步吗?

很多人在开发过程中利用 console.log 调试都被它“骗过”,在监督一些简单对象的时候,log 出现给咱们的值可能与预期不符,如下:

这时有人间接下定义:console.log是异步的!其实这个问题在《你不晓得的 javascript 中卷》第二局部异步和性能 1.1 节异步控制台局部有提及:

并没有什么标准或一组需要指定 console.* 办法族如何工作——它们并不是 JavaScript 正式的一部分,而是由宿主环境(请参考本书的“类型和语法”局部)增加到 JavaScript 中的。因而,不同的浏览器和 JavaScript 环境能够依照本人的志愿来实现,有时候这会引起混同。
尤其要提出的是,在某些条件下,某些浏览器的 console.log(..) 并不会把传入的内容立刻输入。呈现这种状况的次要起因是,在许多程序(不只是 JavaScript)中,I/O 是十分低速的阻塞局部。所以,(从页面 /UI 的角度来说)浏览器在后盾异步解决控制台 I /O 可能进步性能,这时用户甚至可能基本意识不到其产生。

咱们得晓得一点,JS 中对象是援用类型,每次应用对象时,都只是应用了对象在堆中的援用。

当咱们在应用 a.b.c = 2 扭转了对象的属性值时,它在堆中 c 的值也变成了 2,而当咱们不开展对象看的时候,console.log打印的是对象过后的快照,所以咱们看到的 c 属性值是没扭转之前的 1,开展对象时,它其实是从新去内存中读取对象的属性值,所以当咱们开展对象后看到的 c 属性值是 2。

论断 1

由此可见,console.log打印进去的内容并不是肯定百分百可信的内容。个别对于根本类型 numberstringbooleannullundefined 的输入是可信的。但对于 Object 等援用类型来说,则就会呈现上述异样打印输出。
如果改为 console.log(JSON.stringfy(obj)) 则会打印出以后对象的快照,也就能拿到相似同步下的现实后果。更好的解决方案是应用断点进行调试,在那里执行齐全进行,您能够查看每个点的以后值。仅对可序列化和不可变数据应用日志记录。

console.log 会影响性能吗?

通过下面的问题,能够反思一下:为什么你能够在控制台打印程序变量的快照?并且是援用类型的值时,开展对象会从新去内存读取对象的值?程序运行完后难道变量不应该被 gc 回收吗?

在传递给 console.log 的对象是不能被垃圾回收 ♻️,因为在代码运行之后须要在开发工具能查看对象信息。所以最好不要在生产环境 中 console.log任何对象。

案例一

能够在控制台印证这一点:

var Foo = function() {};
console.log(new Foo());

关上控制台 Memory 调试:

案例二

<script>
...

mounted () {
    this.handleSignCallback({
      type: SIGNATURE_TYPES.SIGNATURE_GET_SIGN_STATUS,
      success: true,
      result: {longStr: new Array(1000000).join('*'),
        someMethod: function () {}
      }
    });
},
methods: {handleSignCallback(data) {const { type, result, success} = data;
    // do something...
    console.log(result) // 返回后果打印
    if (true) { // 实在我的项目中用来判断是否满足条件
      this.timer = setTimeout(() => {
      this.handleSignCallback({
        type: SIGNATURE_TYPES.SIGNATURE_GET_SIGN_STATUS,
        success: true,
        result: {longStr: new Array(1000000).join('*'),
          someMethod: function () {}
        }
      })
      clearTimeout(this.timer);
    }, 1000)
   }
  }
}
</script>

实在我的项目在轮询中须要判断某些逻辑,满足条件了,会提早几秒持续去申请后盾,解决一些数据,直到返回的对象没有满足条件的数据才进行去申请拉数据,期间我想打印返回的数据,于是我加了console.log(result),然而如果数据量大的话,执行一会就会程序解体,关上调试控制台 ->Memory:

js Heap 在始终回升,无下限,直至浏览器解体;

然而当我正文 console.log(result) 这行 时,再次察看堆内存状况:

这时堆内存应用状况根本稳固在 150 左右。

论断 2

先给上述问题下个论断:console.log的确会影响网页性能。原因请联合下述开展问题联合深究。

开发小技巧

针对论断二,大家应该都晓得了,生产模式下尽量不要留 console.log,兴许大家当初公司里可能都是这么做的,然而你真的晓得为什么这么做吗?上面举例Vue 我的项目如何对立去除坑爹的console.log:

  1. 装置 babel-plugin-transform-remove-console

    npm i babel-plugin-transform-remove-console --dev

  2. 配置 babel.config.js 插件

    // babel.config.js 文件
    const plugins = []
    // 生产环境移除 console
    if(process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') {plugins.push("transform-remove-console")
    }
    module.exports = {
      presets: ['@vue/app'],
      plugins: [...plugins]
    }
    

联合案例二做性能调试

这里联合 Chrome 的 Devtools–>Performance 做一些剖析,操作步骤如下:

  1. 开启 Performance 的记录
  2. 执行 CG 按钮,创立基准参考线
  3. 屡次点击【click】按钮,新建 Leaker 对象
  4. 执行 CG 按钮
  5. 进行记录这里联合 Chrome 的 Devtools–>Performance 做一些剖析,操作步骤如下:

    1. 开启 Performance 的记录
    2. 执行 CG 按钮,创立基准参考线
    3. do something or create Object
    4. 执行 CG 按钮
    5. 进行记录

开启 log 模式下:

阐明:测试代码因为模仿轮询场景,定时器触发频繁,可能呈现 gc 的工夫点 js Heap 高于基准线一点,次要看最初一次的 js Heap 跟第一次触发的线是不是差不多持平

能够看到这里的js Heap(蓝色线,绿色线是 nodes 节点数量)始终在升高,并且我手动回收垃圾(gc)也杯水车薪,很显著,产生了内存泄露!

敞开 log 模式下:

敞开 log 后,察看 js 堆内存状况如上:能够看到,手动 GC 后两个工夫点,js Heap 根本复原到同一水平线。

在一般场景下测试,例如开启记录 ->gc-> 点击事件,创建对象(N 遍)->gc-> 进行记录,js Heap 两个 gc 工夫点根本重合。

内存泄露排查办法二

Heap Profiling 能够记录以后的堆内存(heap)的快照,并生成对象的形容文件,该形容文件给出了过后 JS 运行所用的所有对象,以及这些对象所占用的内存大小、援用的层级关系等等。

JS 运行的时候,会有栈内存(stack)和堆内存(heap),当咱们 new 一个类的时候,这个 new 进去的对象就保留在 heap 里,而这个对象的援用则存储在 stack 里。程序通过 stack 的援用找到这个对象。例如:var a = [1,2,3],a 是存储在 stack 中的援用,heap 里存储着内容为 [1,2,3] 的 Array 对象。

关上调试工具,点击 Memory 中的 Profiles 标签,选中“Take Heap Snapshot”, 点击“start”按钮,就能够拍在以后 JS 的 heap 快照了。

左边视图中列出了 heap 里的对象列表。

constructor:类名
Distance:对象到根的援用层级间隔
Objects Count:该类的对象数
Shallow Size:对象所占内存(不蕴含外部援用的其余对象所占的内存)
Retained Size:对象所占的总内存(蕴含····················································)
点击上图左上角的黑圈圈,会呈现第二个内存快照

将上图框框切换到comparison(对照)选项,该视图列出了以后视图与上一个视图的对象差别

New:新建了多少对象
Deleted:回收了多少对象
Delta:新建的对象个数减去回收的对象个数
重点看 closure(闭包),如果 #Delta 为负数,则示意创立了闭包函数,如果多个快照中都没有变正数,则示意没有销毁闭包。

乏味 (无聊) 的问题

while (true)
    console.log("hello... there");

运行以上代码,在大概 3 分钟左右的工夫内,节点耗费了 1.5GB 的内存。

有人会说,死循环了能不引起内存飙升吗?

那看上面这个:

def hello():
    while True:
        print "hello world..."

hello()

Python 永远放弃在 3.3MB。

有人可能会想到后面说的,console.log会引起内存泄露,然而后面说了是援用类型会造成内存泄露,这里间接打印字符串,为什么性能也会这么差?

简短答复:控制台输入是缓冲和异步的。并且除了全局内存限度外,缓冲区不受任何限度。因而,它填满缓冲区并死亡。
更多信息在 #1741。

“缓冲区”是指实际上是异步操作队列的隐式缓冲区。

  1. 连贯的查看器客户端将导致 console.log() 缓冲 无限 数量的音讯
  2. 当您打印的速度比接收器(tty、管道、文件等)能够应用它们的速度快时,Libuv 能够缓冲音讯。这是您能够通过调整写入速度来解决的问题。

Libuv Libuv 是 一个高性能的,事件驱动的异步 I / O 库,它自身是由 C 语言编写的,具备很高的可移植性。

论断 3

node 开发时,在咱们写入大型日志条目标状况下,写入文件进行日志记录是迄今为止最好的办法。服务稍后读取日志文件并将其聚合到适当的服务。

正文完
 0