第4章 算法和流程控制
内容:
程序算法和流程优化,包括优化循环、递归等等
知识点:
- js有四种循环类型:
for(var i=0; i<10; i++) {}; //初始化、前测条件、后执行体、循环体
while() {} //前侧条件、循环体
do{} while(); //循环体、后测条件
for(var prop in object) {}
for-in循环所返回的对象包括实例属性和从原型链中继承的属性,它是四种循环中最慢的,大约只有其他类型速度的1/7;
2. 优化循环有两种方式:
减少迭代工作量 ->复杂度=0(n)时首选
减少迭代次数 ->复杂度>0(n)时首选
3. 减少迭代工作量方法:
在初始化中将length保存为局部变量,例如:for(let i=0, len=arr.length; i<len; i++)
使用倒序循环,例如:for(let i=10;i--;){}
,while(j--){}
,每次循环减少一次控制条件判断(迭代次数少于上限?值是否为true?=>值是否为true?);
4. 减少迭代次数方法:
“达夫设备”,每次循环执行多次操作,在迭代次数>1000时考虑使用;
5. 大多数情况switch比if-else更快,但只要当条件数量很大时才慢得明显。因为大多数语言对switch采用分支表索引进行优化,并且在js中在switch中使用全等操作符,不会发生类型转换;
6. 条件语句选择:
离散值 => switch
多个值域 => if-else
大量离散值且条件和值存在映射关系 => 查找表(存入数组直接return)
7. 优化if-else方法:最可能出现的条件放在首位,嵌套if-else(二分法);
8. 除IE的调用栈是与系统空闲内存有关,其他浏览器都有固定数量的调用栈限制;
浏览器 | 调用栈错误提示 | 错误类型 |
---|---|---|
IE | Stack overflow at line x | 常规Error |
Firefox | Too much recursion | InternalError |
Safari | Maximum call stack size exceeded | RangeError |
Chrome | 不提示 | RangeError |
Opera | Abort(control stack overflow) | 终止js引擎,不抛出错误 |
9. 两种递归模式:直接递归模式、隐伏模式;
总结:
- 避免使用for-in循环,除非需要遍历一个属性数量未知的对象;
- 改善循环的最佳方式是减少迭代工作量和减少迭代次数;
- 在判断条件较多时,使用查找表比if-else和switch更快;
- 使用递归时注意浏览器调用栈限制;
- 遇到栈溢出错误时可将方法改为迭代算法,或用Memoization避免重复计算。
第5章 字符串和正则表达式
内容:
字符串操作优化和正则表达式查找优化,包括优化字符串连接、正则匹配等等
知识点:
- 例如
str+='one' + 'two'
,js会创建一个临时字符串保存one
和two
的连接然后与str
连接再赋值给str
,例如使用str=str + 'one' + 'two'
可以避免创建临时字符串,因为会从左至右依次连接,但是改变连接顺序例如str='one' + str + 'two'
则优化失效; - IE7及以前版本的字符串连接使用了非常糟糕的方法:每连接一对字符串都把它复制到一块新分配的内存中;IE8的实现则是:记录现有字符串的引用来构造新字符串,当需要使用时才将各部分逐个拷贝到一个新的字符串中,然后用它取代之前的字符串引用;
- Array.prototype.join方法在大多浏览器中比常规字符串连接更慢,但在IE7及更早版本中是一种优化手段;
- 正则优化部分待续
第6章 快速响应的用户界面
内容:
使用各种技术提升应用交互性,包括合理管理UI线程、使用Web Worker进行额外计算等等
知识点:
- 用于执行js和更新UI的进程被称为“浏览器UI线程”,它的工作基于一个简单的队列系统,任务会被保存到队列中直到进程空闲;
- 任务类型包括:运行js代码、执行UI更新;js执行期间不会更新UI,UI更新期间不会执行js;js与UI共享同一进程的部分原因是它们之间会频繁访问;
- 浏览器对脚本运行的限制有:调用栈大小限制、运行时间限制,各浏览器的限制如下:
浏览器 | 限制类型 | 修改方法 |
---|---|---|
IE | 500万条语句 | 修改注册表MaxScriptStatements字段 |
Firefox | 10秒 | 修改浏览器配置,输入about:conf修改dom.max_script_run_time |
Safari | 5秒 | 无法修改,可以禁用定时器 |
Chrome | 依赖通用崩溃检测系统处理 | |
Opera | 无限制 |
4. 单个js操作花费总时间不应该超过100ms;
5. 有些浏览器在js运行期间不会把UI更新任务加入队列,这是为了保证UI页面的动态变化,所以可能出现脚本运行期间点击按钮,无法看到它被按下的样式,但它的onlick事件处理器会被执行;
6. 使用定时器管理UI线程:
- 定时器从调用它时开始算,计时完成后被加入UI队列,但需要注意函数只有在创建它的函数执行完成之后才有可能被执行
- 定时器会重置所有相关的浏览器限制,包括长时间运行脚本定时器,调用栈也会重置为0
- js定时器通常不太精确,相差大约几毫秒。因为例如windows系统定时器分辨率是15ms,即一个15ms的定时器会根据最后一次系统时间刷新而转换为0或15。最小值最好为25ms(实际时间15或30)以确保至少有15ms的延时,再小的延时对大多数UI更新会不够用
7. 记录代码运行时间:
var start = +new Date(),
stop;
process();
stop = +new Date();
var time = stop - start;
- 当任务与UI无关且不能被分解时可考虑使用Worker
Web Worker运行环境:
- navigator,包含属性:appName、appVersion、user Agent、platform
- location,只读
- self,指向全局worker对象
- importScripts(),用来加载外部js文件,阻塞式,直到执行完成才继续
- 所有ES对象,Object、Array、Date等
- XMLHttpRequest
- setTimeout()、setInterval()
- close(),终止Worker运行
- 使用Worker需要单独建立js文件然后使用:
var worker = new Worker('code.js')
,文件会异步下载,文件下载执行完成后才会启动此Worker; - 主进程端:
var worker = new Worker('code.js');
worker.onmessage = function(e) {
console.log(e.data);
}
worker.postMessage("s");
Worker端:
self.onmessage = function(e) {
self.postMessage(e.data);
}
总结:
- 寻求运行速度和用户体验的平衡,当js任务执行超过100ms时,就应该考虑使用定时器或者Web Worker;
- 可以将任务分解成一个一个的小块分别执行;
第7章 Ajax
内容:
使用各种技术提升应用交互性,包括合理管理UI线程、使用Web Worker进行额外
知识点:
1 向服务器请求数据的常用技术:
- XHR
- 动态脚本注入
- Multipart XHR
- iframes
- Comet
2 GET请求具有幂等性,对服务器无副作用,经GET请求的数据会被缓存起来;
3 IE限制URL长度,当URL长度超过2048个字符(4k)时应使用POST;
4 可以使用“流”分段处理返回数据,低版本IE不支持“流”,也不提供readyState为3的状态;
5 使用动态脚本注入有很多限制,包括不能设置请求头,只能用GET,不能设置请求超时或重试,必须等所有数据返回才能访问等等,实现JSONP(JSON with padding)跨域:
//js端
var scriptEle = document.createElement('script');
scriptEle.src = "url";
document.getElementsByTagName('head')[0].appendChild(scriptEle);
function callBack(jsonString) {
var data = eval('(' + jsonString + ')');
}
//服务端返回
callBack({"status":1, "data": 2});
6 可以将参数以var params = ['key1=value1','key2=value2']
格式存为数组,最后params .join(‘&’)拼接;
7 GET只发送一个数据包,而POST至少发送两个(请求头、请求正文),所以GET请求速度会更快;
8 向服务器发送数据技术:XHR、Beacons(信标)
9 Beacons实例:(new Image()).src = url + '?' + params .join('&');
是给服务器回传信息最有效的方式,性能消耗小且服务端错误不会影响客户端;
接收服务器返回数据:监听Image对象的load事件;检查服务器返回图片的宽高(如可以用宽1表示成功,2表示重试);
10 XML优点:
- 极佳的通用性,服务端和客户端都完美支持
- 格式严格
- 易于验证
但也存在过于冗长,语法模糊的问题,解析它也需要提前知道它的结构
11 XHR获取的JSON是字符串格式,而JSON-P本身就被当做JSON对象;
12 两种情形避免使用JSON-P,因为JSON-P必须是可执行js,可能被任何人调用并使用动态脚本注入技术插入任何网站;不能把敏感数据编码在JSON-P中,因为你无法确认它是否保持私有调用状态,即使带有随机URL和做了cookie判断;
13 最快的ajax请求就是没有请求,减少不必要请求方法:
- 服务端,设置HTTP头信息确保响应被浏览器缓存(设置Expires头信息,GMT日期格式)
- 客户端,把获取的信息存储到本地,避免再次请求
14 大多数浏览器支持XMLHttpRequest对象,老版本浏览器使用ActiveX对象,并需要传入版本号,通用获取xhr对象方法:
function createXhrObject(){
var msxml_progid = [
'MSXML2.XMLHTTP.6.0',
'MSXML3.XMLHTTP',
'Microsoft.XMLHTTP', //不支持readyState 3
'MSXML2.XMLHTTP.3.0', //不支持readyState 3
];
var req;
try{
req = new XMLHttpRequest();
}
catch(e){
for(var i = 0, len = msxml_progid.length; i<len; i++){
try{
req = new ActiveXObject(msxml_progid[i]);
break;
}
catch(e2){}
}
}
finally{
return req;
}
}
15 各种交互手段比较:
- XML支持良好,但笨重且解析缓慢;JSON轻量级解析快,但需要编写额外的服务端构造程序;
- 动态脚本注入允许跨域请求和本地执行js和JSON,但它的接口不安全,而且不能读取头信息或响应代码;
- Multipart XMR可以减少请求数并处理一个响应中的各种文件类型,但不能缓存接收到的响应
总结:
- 减少请求数,合并js和css文件或使用MXHR;
- 页面主要内容加载完成后用ajax获取次要文件;
- 确认代码错误不会输出给用户,并在服务端处理错误;
- 可以使用ajax类库,但必要时可编写自己的底层ajax代码。
第8章 编程实践
内容:
一些编程中需要注意的优化细节,包括条件预加载、使用底层方法等等。
知识点:
1 传入字符串并动态执行的方法:eval()、Function()构造函数、setTimeout()、setInterval();
2 每次调用eval()都要创建一个新的解释器实例;
3 避免重复的条件判断:延迟加载、条件预加载
当一个函数在页面中不会立刻调用时,延迟加载是最好的选择:
function addHandler(target, eventType, handler){
if(target.addEventListener){
//复写函数
addHandler = function(target, eventType, handler){
target.addEventListener(eventType, handler, false);
}
}else{
addHandler = function(target, eventType, handler){
target.attachEvent("on" + eventType, handler);
}
}
//调用新函数
addHandler(target, eventType, handler);
}
条件预加载:函数定义时判定
var addHandler = document.body.addEventListener?
function(target, eventType, handler){
target.addEventListener(eventType, handler, false);
}:
function(target, eventType, handler){
target.attachEvent("on" + eventType, handler);
}
}
4 js引擎是由低级语言构建的而且经过编译,使用位操作符和原生方法例如Math中的方法和querySelectorAll()速度更快;
5 位操作符应用:i&1
可以判断奇偶,奇时返回true,偶时返回false
总结:
- 避免使用给eval()、Function()构造器、setTimeout()、setInterval()传入字符串来造成双重求值,因为创建新的解释器会带来性能损耗,setTimeout()和setInterval()应传递函数;
- 尽量使用直接量创建对象和数组,直接量的创建和初始化比非直接量形式快;
- 使用延迟加载和条件预加载避免重复的检测工作;
- 进行数学运算时考虑使用直接操作数字的二进制形式的位运算,尽量使用原生方法。