一位客户报告说,当点击一个按钮后,Chrome关闭了该页面,该页面就失去了响应。我最初认为这可能是Chrome扩展的问题。所以,我在隐身模式下进行了测试,但问题也在隐身模式下重现。
在解决这个问题之后,我花了很多时间阅读和理解JavaScript及其编译器。
因此就有了该篇文章
内存泄漏
内存泄漏是因为不需要的内存,但由于某种原因,它没有被回收返回到内存池。
JavaScript的设计方式是,一旦变量被使用,它将自动删除分配的内存,这个过程称为垃圾收集。
JavaScript编译器使用两种不同的垃圾收集器,一种是主Major GC,另一种是Minor GC。
更多: https://v8.dev/blog/trash-talk
Major GC: Major GC从整个堆中收集垃圾。
Minor GC: 小GC收集年轻代的垃圾。
Major GC:
主GC是主垃圾收集器,它识别活对象和死对象,并删除死对象。但是Major GC是在主线程上运行的,所以如果经常调用GC,页面就会变得无响应。
垃圾回收为什么冻结页面?
Garage collector运行在主线路上,所以它会阻塞所有用户输入,因此页面变得无响应。
好吧,但为什么主线程阻塞所有用户输入?
简单的回答是事件循环。更详细的答案,你需要看这个视频。对事件循环和JavaScript编译器有更详细的解释。
更多:https://www.youtube.com/watch?v=8aGhZQkoFbQ
改善应用程序内存管理的通用建议
1. 全局变量
垃圾收集器从不收集全局变量的内存,因此在开始声明全局变量之前,请三思。
但有时,我们会不小心引入了一个全局变量。
请看代码
function foo(arg) { bar = "this is a hidden global variable"; }
您可能会注意到,该变量没有定义但赋值,因此它将成为一个全局变量。
这里,我们泄漏了一个字符串内存,你可能认为这不会产生大问题,但它会覆盖其他一些全局方法,这会引起新的隐患。
2. 引用没法回收
当涉及到对象和数据绑定时,一定要关注内存。
function run(){ var domObjects = $(".myClass"); domObjects.click(function(){ domObjects.addClass(".myOtherClass"); }); }
一旦函数被调用,JavaScript将删除分配的内存。但是在上面的代码中,GC没有办法收集“domObjects”的内存,因为它绑定到了事件监听器。
如果你想删除这些引用,你可以手动删除它们。
function run(){ var domObjects = $(".myClass"); domObjects.click(function(){ if(domObjects){ domObjects.addClass(".myOtherClass"); domObjects = null; } }); }
3. 字符串连接
这听起来很奇怪,但是字符串连接需要额外的内存。因此,避免字符串连接,而使用模板字面量代替。
4. 避免创建新对象
新的内存被分配给新的对象、数组等。想办法减少这种情况。
让我们看一个jQuery的例子,
当你从不使用jQuery时,它会增加堆中的内存,所以我们可以避免使用jQuery链接方法。
6. JSON Parse
当您的应用程序在页面加载期间基于JSON进行渲染时,请考虑使用JSON.stringify和JSON.parse来处理这些数据。
更多: https://v8.dev/blog/cost-of-javascript-2019#json
7. 避免 try-catch
Try-catch比if分配更多的内存。但是try-catch对于大多数应用程序都是必要的,如果你分析得当,你可以把它们变成try-catch。
请看看下面链接中的问题,由node js环境中的try-catch引起的内存泄漏问题。您将获得一些关于try-catch和内存泄漏的知识
https://github.com/nodejs/node/issues/35048
8. 创建新窗口
当创建一个新窗口时,请把废弃的window引用设置为null。
当关闭窗口时,只调用Minor GC,因此只清除年轻代内存块。
如果您想检查JSHeap内存占用情况,请在您的控制台上输入此代码:
memory.usedJSHeapSize
9. Callbacks回调
如果事件的回调频率非常高,考虑避免它。让我们以滚动为例。当您将滚动事件绑定到窗口用户向下/向上滚动时,回调调用的频率非常高。
可以使用 Underscore.js 中 _.throttle 和 _.debounce 来减少回调执行,避免页面卡顿。
function log( event ) { console.log( $(window).scrollTop(), event.timeStamp ); }; // 控制台记录窗口滚动事件,触发频率比你想象的要快 $(window).scroll( log ); // 控制台记录窗口滚动事件,每250ms最多触发一次 $(window).scroll( _.throttle( log, 250 ) ); function ajax_lookup( event ) { // 对输入的内容$(this).val()执行 Ajax 查询 }; // 字符输入的频率比你预想的要快,Ajax 请求来不及回复。 $('input:text').keyup( ajax_lookup ); // 当用户停顿250毫秒以后才开始查找 $('input:text').keyup( _.debounce( ajax_lookup, 250 ) );
更多请阅读https://blog.coding.net/blog/the-difference-between-throttle-and-debounce-in-underscorejs