很多Javascript引擎,如Google的V8引擎(被Chrome和Node所用),是专门为需要快速执行的大型JavaScript应用所设计的。如果你是一个开发者,并且关心内存使用情况与页面性能,你应该了解用户浏览器中的JavaScript引擎是如何运作的。无论是V8,SpiderMonkey的(Firefox)的Carakan(Opera),Chakra(IE)或其他引擎,这样做可以帮助你更好地优化你的应用程序。这并不是说应该专门为某一浏览器或引擎做优化,千万别这么做。
但是,你应该问自己几个问题:
加载快速的网站就像是一辆快速的跑车,需要用到特别定制的零件. 图片来源:dHybridcars.
编写高性能代码时有一些常见的陷阱,在这篇文章中,我们将展示一些经过验证的、更好的编写代码方式。
那么,JavaScript在V8里是如何工作的?
如果你对JS引擎没有较深的了解,开发一个大型Web应用也没啥问题,就好比会开车的人也只是看过引擎盖而没有看过车盖内的引擎一样。鉴于Chrome是我的浏览器首选,所以谈一下它的JavaScript引擎。V8是由以下几个核心部分组成:
垃圾回收
垃圾回收是内存管理的一种形式,其实就是一个收集器的概念,尝试回收不再被使用的对象所占用的内存。在JavaScript这种垃圾回收语言中,应用程序中仍在被引用的对象不会被清除。
手动消除对象引用在大多数情况下是没有必要的。通过简单地把变量放在需要它们的地方(理想情况下,尽可能是局部作用域,即它们被使用的函数里而不是函数外层),一切将运作地很好。
垃圾回收器尝试回收内存. 图片来源:Valtteri Mäki.
在JavaScript中,是不可能强制进行垃圾回收的。你不应该这么做,因为垃圾收集过程是由运行时控制的,它知道什么是最好的清理时机。
“消除引用”的误解
网上有许多关于JavaScript内存回收的讨论都谈到delete这个关键字,虽然它可以被用来删除对象(map)中的属性(key),但有部分开发者认为它可以用来强制“消除引用”。建议尽可能避免使用delete,在下面的例子中delete o.x 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
123 | var o = { x: 1 }; delete o.x; // true o.x; // undefined |
你会很容易地在流行的JS库中找到引用删除——这是具有语言目的性的。这里需要注意的是避免在运行时修改”hot”对象的结构。JavaScript引擎可以检测出这种“hot”的对象,并尝试对其进行优化。如果对象在生命周期中其结构没有较大的改变,引擎将会更容易优化对象,而delete操作实际上会触发这种较大的结构改变,因此不利于引擎的优化。
对于null是如何工作也是有误解的。将一个对象引用设置为null,并没有使对象变“空”,只是将它的引用设置为空而已。使用o.x= null比使用delete会更好些,但可能也不是很必要。
1234 | var o = { x: 1 }; o = null ; o; // null o.x // TypeError |
如果此引用是当前对象的最后引用,那么该对象将被作为垃圾回收。如果此引用不是当前对象的最后引用,则该对象是可访问的且不会被垃圾回收。
另外需要注意的是,全局变量在页面的生命周期里是不被垃圾回收器清理的。无论页面打开多久,JavaScript运行时全局对象作用域中的变量会一直存在。
1 | var myGlobalNamespace = {}; |
全局对象只会在刷新页面、导航到其他页面、关闭标签页或退出浏览器时才会被清理。函数作用域的变量将在超出作用域时被清理,即退出函数时,已经没有任何引用,这样的变量就被清理了。
经验法则
为了使垃圾回收器尽早收集尽可能多的对象,不要hold着不再使用的对象。这里有几件事需要记住:
函数
接下来,我们谈谈函数。正如我们已经说过,垃圾收集的工作原理,是通过回收不再是访问的内存块(对象)。为了更好地说明这一点,这里有一些例子。
1234 | function foo() {
var bar = new LargeObject();
bar.someCall(); } |
当foo返回时,bar指向的对象将会被垃圾收集器自动回收,因为它已没有任何存在的引用了。
对比一下:
1234567 | function foo() {
var bar = new LargeObject();
bar.someCall();
return bar; } // somewhere else var b = foo(); |
现在我们有一个引用指向bar对象,这样bar对象的生存周期就从foo的调用一直持续到调用者指定别的变量b(或b超出范围)。
闭包(CLOSURES)
当你看到一个函数,返回一个内部函数,该内部函数将获得范围外的访问权,即使在外部函数执行之后。这是一个基本的闭包—— 可以在特定的上下文中设置的变量的表达式。例如:
12345678910 | function sum (x) {
function sumIt(y) {
return x + y;
};
return sumIt; } // Usage var sumA = sum(4); var sumB = sumA(3); console.log(sumB); // Returns 7 |
在sum调用上下文中生成的函数对象(sumIt)是无法被回收的,它被全局变量(sumA)所引用,并且可以通过sumA(n)调用。
让我们来看看另外一个例子,这里我们可以访问变量largeStr吗?
123456 | var a = function () {
var largeStr = new Array(1000000).join( 'x' );
return function () {
return largeStr;
}; }(); |
是的,我们可以通过a()访问largeStr,所以它没有被回收。下面这个呢?
1234567 | var a =
学习交流
热门图片
猜你喜欢的新闻
新闻热点 2024-04-27 13:35:46
2024-04-27 13:33:47
2024-04-24 22:53:44
2024-04-23 19:32:50
2024-04-23 19:25:50
2024-04-23 19:13:19
疑难解答 |