JS优化原则

本文总结了浏览器编程中JavaScript优化的五大原则,强调针对IE6(JScript5.6)进行优化的重要性,并详细介绍了如何优化大规模循环、减少函数调用及多级引用、使用语言内建构造和函数、减少对象创建等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JS优化已经讨论了很多了,最近又看到 aimingoo的一篇。大体上,aimingoo的说法都是非常正确的。

除了像aimingoo做个案研究外,这里我想从更一般的角度总结在浏览器编程中JS优化的几个原则。

首先,与其他语言不同,JS的效率很大程度是取决于JS engine的效率。除了引擎实现的优劣外,引擎自己也会为一些特殊的代码模式采取一些优化的策略。例如FF、Opera和Safari的JS引擎,都对字符串的拼接运算(+)做了特别优化。显然,要获得最大效率,就必须要了解引擎的脾气,尽量迎合引擎的口味。所以对于不同的引擎,所作的优化极有可能是背道而驰的。

而如果做跨浏览器的web编程,则最大的问题是在于IE6(JScript 5.6)!因为在不打 hotfix的情况下,JScript引擎的垃圾回收的bug,会导致其在真实应用中的performance跟其他浏览器根本不在一个数量级上。因此在这种场合做优化,实际上就是为JScript做优化!

所以第一原则就是 只需要为IE6(未打补丁的JScript 5.6或更早版本)做优化

如果你的程序已经优化到在IE6下可以接受的性能,那基本上在其他浏览器上性能就完全没有问题。

因此,注意我下面讲的许多问题在其他引擎上可能完全不同,例如在循环中进行字符串拼接,通常认为需要用Array.join的方式,但是由于SpiderMonkey等引擎对字符串的“+”运算做了优化,结果使用Array.join的效率反而不如直接用“+”!但是如果考虑IE6,则其他浏览器上的这种效率的差别根本不值一提。

JS优化与其他语言的优化也仍然有相同之处。比如说,不要一上来就急吼吼的做优化,那样毫无意义。优化的关键,仍然是要把精力放在最关键的地方,也就是瓶颈上。一般来说,瓶颈总是出现在大规模循环的地方。这倒不是说循环本身有性能问题,而是循环会迅速放大可能存在的性能问题。

所以第二原则就是 以大规模循环体为最主要优化对象

以下的优化原则,只在大规模循环中才有意义,在循环体之外做此类优化基本上是没有意义的。

目前绝大多数JS引擎都是解释执行的,而解释执行的情况下,在所有操作中,函数调用的效率是较低的。此外,过深的prototype继承链或者多级引用也会降低效率。JScript中,10级引用的开销大体是一次空函数调用开销的1/2。这两者的开销都远远大于简单操作(如四则运算)。

所以第三原则就是 尽量避免过多的引用层级和不必要的多次方法调用

特别要注意的是,有些情况下看似是属性访问,实际上是方法调用。例如所有DOM的属性,实际上都是方法。在遍历一个NodeList的时候,循环条件对于nodes.length的访问,看似属性读取,实际上是等价于函数调用的。而且IE DOM的实现上,childNodes.length每次是要通过内部遍历重新计数的。(My god,但是这是真的!因为我测过,childNodes.length的访问时间与childNodes.length的值成正比!)这非常耗费。所以预先把nodes.length保存到js变量,当然可以提高遍历的性能。

同样是函数调用,用户自定义函数的效率又远远低于语言内建函数,因为后者是对引擎本地方法的包装,而引擎通常是c,c++,java写的。进一步,同样的功能,语言内建构造的开销通常又比内建函数调用要效率高,因为前者在JS代码的parse阶段就可以确定和优化。

所以第四原则就是 尽量使用语言本身的构造和内建函数

这里有一个例子是 高性能的String.format方法。String.format传统的实现方式是用String.replace(regex, func),在pattern包含n个占位符(包括重复的)时,自定义函数func就被调用n次。而这个高性能实现中,每次format调用所作的只是一次Array.join然后一次String.replace(regex, string)的操作,两者都是引擎内建方法,而不会有任何自定义函数调用。两次内建方法调用和n次的自定义方法调用,这就是性能上的差别。

同样是内建特性,性能上也还是有差别的。例如在JScript中对于arguments的访问性能就很差,几乎赶上一次函数调用了。因此如果一个可变参数的简单函数成为性能瓶颈的时候,可以将其内部做一些改变,不要访问arguments,而是通过对参数的显式判断来处理。

比如:
Java代码 复制代码
  1. function sum() {   
  2.     var r = 0;   
  3.     for (var i = 0; i < arguments.length; i++) {   
  4.         r += arguments[i];   
  5.     }   
  6.     return r;   
  7. }  
	function sum() {
		var r = 0;
		for (var i = 0; i < arguments.length; i++) {
			r += arguments[i];
		}
		return r;
	}

这个sum通常调用的时候个数是较少的,我们希望改进它在参数较少时的性能。如果改成:
Java代码 复制代码
  1. function sum() {   
  2.     switch (arguments.length) {   
  3.         case 1return arguments[0];   
  4.         case 2return arguments[0] + arguments[1];   
  5.         case 3return arguments[0] + arguments[1] + arguments[2];   
  6.         case 4return arguments[0] + arguments[1] + arguments[2] + arguments[3];   
  7.         default:   
  8.             var r = 0;   
  9.             for (var i = 0; i < arguments.length; i++) {   
  10.                 r += arguments[i];   
  11.             }   
  12.             return r;   
  13.     }   
  14. }  
	function sum() {
		switch (arguments.length) {
			case 1: return arguments[0];
			case 2: return arguments[0] + arguments[1];
			case 3: return arguments[0] + arguments[1] + arguments[2];
			case 4: return arguments[0] + arguments[1] + arguments[2] + arguments[3];
			default:
				var r = 0;
				for (var i = 0; i < arguments.length; i++) {
					r += arguments[i];
				}
				return r;
		}
	}

其实并不会有多少提高,但是如果改成:
Java代码 复制代码
  1. function sum(a, b, c, d, e, f, g) {   
  2.     var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d + e : a + b + c + d : a + b + c : a + b : a : 0;   
  3.     if (g === undefined) return r;   
  4.     for (var i = 6; i < arguments.length; i++) {   
  5.         r += arguments[i];   
  6.     }   
  7.     return r;   
  8. }  
	function sum(a, b, c, d, e, f, g) {
		var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d + e : a + b + c + d : a + b + c : a + b : a : 0;
		if (g === undefined) return r;
		for (var i = 6; i < arguments.length; i++) {
			r += arguments[i];
		}
		return r;
	}

就会提高很多(至少快1倍)。

最后是第五原则,也往往是真实应用中最重要的性能障碍,那就是 尽量减少不必要的对象创建

本身创建对象是有一定的代价的,但是这个代价其实并不大。最根本的问题是由于 JScript愚蠢之极的垃圾回收调度算法,导致随着对象个数的增加,性能严重下降(据微软的人自己说复杂度是O(n^2))。

比如我们常见的字符串拼接问题,经过我的测试验证,单纯的多次创建字符串对象其实根本不是性能差的原因。要命的是在对象创建期间的无谓的垃圾回收的开销。而Array.join的方式,不会创建中间字符串对象,因此就减少了那该死的垃圾回收的开销。

因此,如果我们能把大规模对象创建转化为单一语句,则其性能会得到极大的提高!例如通过构造代码然后eval——实际上PIES项目中正在根据这个想法来做一个专门的大规模对象产生器……

好了上面就是偶总结的JS优化五大原则。

除了这些原则以外,还有一些特殊情况,如DOM的遍历,以后有时间再做讨论。
<think>好的,我现在需要回答用户关于MongoDB查询优化原则和方法的问题。首先,我需要回顾一下已有的知识,并确保信息准确。根据用户提供的引用内容,引用[1]提到了索引、复制和分片策略,引用[2]提到了文档的灵活性和存储大量变体的能力,引用[3]介绍了Michael Brown的大数据背景,但可能与查询优化直接关联不大。 首先,MongoDB查询优化的核心原则可能包括使用索引、避免全集合扫描、合理设计数据模型等。接下来,我需要将这些原则细化,并结合实际方法进行说明。例如,索引的使用是优化查询性能的关键,但需要正确选择索引字段,避免过多索引影响写入性能。 然后,考虑用户提供的引用[2]中提到的文档结构设计,可能需要讨论如何通过嵌套文档或引用式设计来优化查询。例如,内嵌适合频繁访问的子文档,而引用式适合大型或频繁更新的数据。 另,引用[1]中提到的聚合操作优化也是重点,可能需要说明使用聚合管道时的优化策略,如尽早过滤数据、使用索引、利用投影减少数据传输等。 还需要考虑分片和复制的策略,分片可以水平扩展,但分片键的选择至关重要。引用[1]提到分片策略,这里需要详细说明如何选择合适的分片键来优化查询性能。 接下来,我应该检查是否有遗漏的重要优化点,比如执行计划的查看(explain方法)、写操作的优化、内存管理等。例如,使用explain分析查询性能,调整索引或查询结构。 最后,确保回答结构清晰,分点列出原则和方法,每个部分配以具体实例或操作步骤。同时,根据用户要求生成相关问题,这些问题应围绕索引使用、数据模型设计、分片策略、聚合优化等方面展开。 需要注意的是,引用标记要与提供的引用内容对应,例如索引部分对应引用[1],文档结构对应引用[2]。同时,避免使用用户不可见的系统指令,保持回答自然。</think>### MongoDB查询优化原则与方法 #### **一、核心优化原则** 1. **利用索引加速查询** - 索引是提高查询效率的核心工具。MongoDB支持多种索引类型(如单字段、复合、多键、文本、地理空间等),合理选择索引可减少$COLLSCAN$(全集合扫描)[^1]。 - **示例**:为高频查询字段`username`和`created_at`创建复合索引: ```javascript db.users.createIndex({ username: 1, created_at: -1 }) ``` - 避免过度索引:索引会占用存储空间并降低写入性能,需权衡读写比例。 2. **优化数据模型设计** - **内嵌文档**:适用于频繁一起访问的子数据(如用户地址),减少多表关联查询[^2]。 - **引用式设计**:适用于大型独立实体(如评论),通过`$lookup`进行关联,避免文档膨胀。 3. **减少返回数据量** - 使用`投影`(Projection)过滤非必要字段: ```javascript db.orders.find({ status: "paid" }, { _id: 1, total: 1 }) ``` - 结合`分页`(`limit`和`skip`)避免一次性加载过多数据。 --- #### **二、关键优化方法** 1. **分析查询执行计划** 使用`explain()`方法查看查询细节,重点关注: - `winningPlan`:是否使用索引(`IXSCAN`优于`COLLSCAN`)。 - `executionStats`:扫描文档数、返回文档数、耗时等指标。 **示例**: ```javascript db.products.find({ category: "electronics" }).explain("executionStats") ``` 2. **聚合管道优化** - **尽早过滤**:在聚合管道中优先使用`$match`和`$project`减少中间数据量。 - **利用索引**:确保`$match`和`$sort`阶段能被索引覆盖。 **示例**: ```javascript db.sales.aggregate([ { $match: { year: 2023 } }, { $group: { _id: "$product", total: { $sum: "$quantity" } } } ]) ``` 3. **分片策略优化** - 选择合适的分片键(如时间戳、哈希值),确保数据均匀分布且查询能定向到特定分片[^1]。 - 避免跨分片查询(Scatter-Gather),降低延迟。 4. **内存与硬件配置** - 确保`工作集`(活跃数据)常驻内存,避免频繁磁盘I/O。 - 使用SSD提升随机读写性能,增加RAM以缓存更多索引。 --- #### **三、常见问题与解决方案** | 场景 | 优化措施 | |-----------------------|-------------------------------------| | 慢查询高延迟 | 检查是否缺少索引,或索引未被命中。 | | 聚合操作超时 | 优化管道顺序,增加`allowDiskUse`参数。 | | 写入性能下降 | 减少冗余索引,使用批量写入(`bulkWrite`)。 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值