一、加载执行
1.脚本
1.1 推荐将所有的script标签尽可能放在body标签底部: 由于多数浏览器使用单一进程处理UI界面渲染和脚本执行,所以脚本下载、执行会阻塞其他页面渲染,导致刚开始页面空白,让用户感觉加载时间较长,影响用户体验(*且内嵌脚本不要紧跟在link标签后)
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" type="text/css" href="styles.css">
<title>Document</title>
</head>
<body>
<p>hello world!</p>
<-- 推荐的脚本存放位置 -->
<script type="type/javascript" src="file1.js"></script>
<script type="type/javascript" src="file2.js"></script>
<script type="type/javascript" src="file3.js"></script>
</body>
</html>
1.2 尽量减少script标签的引入:
如有多个js依赖文件可以合并成一个,再进行引入
*合并文件可以用离线打包工具或Yahoo!combo handler等实时在线服务实现
1.3 延迟脚本 defer:
对应的js文件再页面解析到该标签时开始下载,但不会立即执行,而是dom加载完成(onload事件触发前)再执行。且在带有defer属性的js文件下载时,不会造成阻塞进程,可以与其他类资源并行下载
<script type="type/javascript" src="file1.js" defer></script>
1.4 js动态创建script:
无论何时启动下载,都不会阻塞其他进程 (跨浏览器兼容性)
// IE特有实现方法 ==> 函数封装
function loadScript (url, callback) {
var script = document.createElement("script")
script.type = "text/javascript"
if (script.readyState) { // 判断IE
script.onreadystatechange = function() {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null
callback()
}
}
} else { // 其他浏览器
script.onload = function() {
callback()
}
}
script.src = url
document.getElementsByTagName("head")[0].appendChild(script)
}
// 调用
loadScript ('file.js', function() {
alert("loaded!")
// 无阻塞模式:当调用直接嵌入当前页面时执行下面语句,可避免多产生一次HTTP请求(如使用该语句,需要使用YUI压缩初始化代码)
// Application.init()
})
1.5 XMLHttpRequest 脚本注入:
使用XHR对象获取js代码,通过创建动态script元素注入到页面 (文件必须同域)
var xhr = new XMLHttpRequest()
xhr.open("get", "file.js", true)
script.onreadystatechange = function() {
if (script.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
var script = document.createElement("script")
script.type = "text/javascript"
script.text= xhr.responseText
document.body.appendChild(script)
}
}
}
1.6 补充
YUI3
LazyLad类库
LABjs
二. 数据存取
三. DOM
四. 算法和流程控制
4.1 循环
基于循环的迭代
- 标准for循环:for (let i = 0; i < item.length, i++) {}
- while
- do-while
- for-in:避免使用,除非遍历属性数量未知的对象
*除for-in外,循环模式的选择要基于需求,而非性能
基于函数的迭代
- forEach
改善循环性能
- 减少迭代数量(工作量)
// 把值存储到局部变量:可提速25%,IE中甚至可达到50%
// item.length一直不变,但是每次循环都要查找,导致性能损耗
for (let i = 0, len = item.length; i < len, i++) {}
// 倒序循环:可提速50% - 60%
// 控制条件与true比较,任何非零数自动转为true, 0等同于false: 等同于每次条件都与0比较
for (let i = item.length; i--) {}
- 减少迭代次数:减少循环嵌套
4.2 条件语句
- if-else :数量较少时,推荐 —> 最可能出现的条件放在首位
- swich : 数量越多,越推荐
- 查找表:判断条件较多时,离散数据量大时,更推荐
eg:
// 将返回值集合存入数组
let results = [result0, result1, result2, result3, result4, result5, result6, result7, result8]
// 返回结果 ---> 通过键值对逻辑映射
return results[value]
esults[value]
4.3 递归
容易出现堆栈溢出,解决方法:改为迭代算法,或使用Memoization避免重复计算
Memoization使用
定义一个封装了基础功能的memoize()函数, eg:
function memoize (fundamental, cache) {
cache = cache || ()
let sgell = function (arg) {
if(!cache.hasOwnProperty(arg)) {
cache[arg] = fundamental(arg)
}
return cache[arg]
}
return shell
}
// 调用
// 缓存该函数
let memfactorial = memoize(factorial, {"0": 0, "1": 1}) // factorial()阶乘函数是递归的典型事例
// 调用新函数
let fact5 = memfactorial (5)
五.字符串和正则表达式
5.1 字符串合并的方法
- str = ‘a’ + ‘b’
- str = ‘a’; str += ‘b’;
- array.join() : str = [‘a’, ‘b’].join(‘’)
- string.concat : str = a; str = str.concat(‘b’ , ‘c’) —> 最灵活的方式,但速度较慢
eg : str += ‘one’ + ‘two’
优化
// 避免产生临时字符串(‘onetwo’),提速 10% - 40%
str += ’one‘
str += ‘two’
等价于
str = ((str + ‘one’) + ‘two’)
5.2 数组项合并(数量较大时,适用于IE7以及更早版本,不考虑IE7及之前版本的话其他版本浏览器不适用会很慢)
解决性能问题
// 该方式IE7随时间和内存消耗以平方关系递增,其他浏览器问题不大,相对普通数组项合并算法有优化
let str = 'I`am a thirty-five character string'
let newStr = ''
let appends = 5000
while (appends--) {
newStr += str
}
较上面方法性能提升,IE7也可
let str = 'I`am a thirty-five character string'
let strs = []
let newStr = ''
let appends = 5000
while (appends--) {
strs[str.length] = str
}
newStr = strs.join('')
5.3 正则表达式
回溯:在正则表达式的是实现中回溯是匹配过程的基础组成部分,但是会产生昂贵的计算消耗,一不小心就会失控
回溯失控:当正则表达式导致浏览器假死数秒,淑芬,甚至更久,很可能是因为回溯失控
解决方法:
- 具体化:尽可能的具体化分隔符之间的字符串匹配模式, 如:".?" 匹配由双引号包围的字符串,替换为更具体的 [^"\r\n]
- 预查和反向引用的模拟原子组
原子组的写法:(?>…) 省略号表示任意正则表达式的模式
优化方式:预查的表达式封装在捕获组中并给它添加一个反向引用的方法
(?=(pattern to make atomic))\1
六.快速响应的用户界面
浏览器UI线程: 用于执行js和更新界面的进程(基于队列)
6.1 使用定时器控制js任务,让出UI线程控制权(最少25ms,不然对ui更新来说不够用)
setTimeout()
setInterval()
*使用定时器序列,同一时间只能有一个定时器,定时器过度使用也会对性能造成负面影响
6.2 分割任务
6.3 Web Workers(新版浏览器支持的特性)
J5最初的一部分Web Workes API引入了一个接口,能使代码运行不占用浏览器UI线程时间,现已成为独立的规范(https://www.w3.org/TR/workers)
6.5 加载外部文件
// 调用过程为阻塞式的,当所有文件加载并执行完成,脚本才会运行,不会影响UI响应
importScripts('file1.js', 'file2.js')
七. Ajax
7.1 请求数据的常用技术
- XMLHttpRequest(XHR): 最常用,允许异步发送和接收数据
let url = '/data'
let param = ['id = 1234', 'limit=20']
let req = new XMLHttpRequest()
req.onreadystatechange = function () {
if (req.readyState === 4) { // 4: 所有消息接收完毕, 3: 接收到部分信息,但不是所有
let responseHeaders = req.getAllResponseHeaders() // 获取响应头
let data = req.responseText // 获取数据
}
}
req.onerror= function () {
// 出错
}
req.open('GET', url + '?' + params.join('&'), true)
req.setRequestHeader('X-Requested-with', 'XMLHttpRequest') // 设置请求头
req.send(null) // 发送请求
- Dynamic script tag insertion 动态脚本注入: 跨域请求数据,速度很快(不能设置请求头,且只能是GET请求,不能设置超时处理或重试,失败了也可能不知道)
let scriptElement = document.createElement('script')
scriptElement .src = 'http://.....lib.js'
document.getElementsByTagName('head').appendChild(scriptElement)
// 无论哪种格式的数据都需要封装在一个回调函数中
function jsonCallback(jsonString) {
let data = eval('(' + jsonString + ')')
}
- iframes
- Comet
- Multipart XHR : 客户端一个请求从服务端传送多个资源
7.2 发送数据(不关心接收的问题):XHR和信标(beacons)
信标(beacons): 类似动态脚本注入
7.3 XML
7.4 JSON、JSONP
json:
[{'id': 1, 'name': 'test1'}, {'id': 3, 'name': 'test3'}, {'id': 3, 'name': 'test3'}]
// 优化(传输速度更快):
[[1, 'test1'], [2, 'test2'], [3, 'test3']]
jsonp: (相比于普通json数据,解析耗时更低):敏感数据不适用 —> 会被人使用动态脚本插入技术放到任何网站
parseJSON([{'id': 1, 'name': 'test1'}, {'id': 3, 'name': 'test3'}, {'id': 3, 'name': 'test3'}])
7.5 自定义格式(最优,速度最快的方式)
以特定字符串的方式传值,eg: name;age;email;address
可用split()分隔为数组
合并多个js文件
Apache Ant提供了合并多个文件的concat任务
827





