高性能js-笔记

来源:互联网 发布:积分返利商城源码 编辑:程序博客网 时间:2024/05/29 12:34

1. 加载和执行

  • 当浏览器遇到script标签时,会停止处理页面,先执行js代码,然后再继续解析和渲染页面。
  • 使用src加载js的过程中,浏览器必须先花时间下载外链文件中的代码,然后解析并执行,在这过程中,页面渲染和用户交互是阻塞的。
  • js下载和执行会阻塞其它资源的下载,比如图片
  • 逐步加载js文件,不会阻塞浏览器,秘诀在于,在页面加载完成之后才加载js代码,这意味着window的load事件出发后再下载脚本

延迟脚本

  • 1.defer
    当一个带有defer属性的js文件下载时,不会阻塞浏览器的其它进程,因此与其它资源并行下载。
<script defer="defer" src="./defer.js"></script>//alert('defer')<script>    alert('script')</script><script>    alert('1')</script><script>    window.onload=function() {        alert('load')    }</script>结果为: script 1 defer load

注意:
defer在src外链js时才起作用,js脚本的执行需要等到文档所有元素解析完成之后,load事件触发执行之前。
多个defer的文件,加载完顺序执行;多个async文件,哪个先加载完执行哪个

  • 2.动态脚本元素
function loadScript(url, callback) {    var script = document.createElement('script');    script.type = "text/javascript";    if(script.readyState) {//IE        script.onreadystatechange = function() {            if(script.readyState === 'complete' || script.readyState === 'loaded') {                script.onreadystatechange = null;                callback()            }        }    }else {//其他浏览器        script.onload = function() {            callback()        }    }    script.src = url;    document.getElementsByTagName('head')[0].appendChild(script);}

文件在该元素被添加到页面时开始下载,文件的下载和执行过程不会阻塞页面其他进程

  • 3.Ajax注入
var xhr = new XMLHttpRequest();;xhr.open('get','./defer.js',true);xhr.onreadystatechange = function() {    if(xhr.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.getElementsByTagName('head')[0].appendChild(script);        }    }}xhr.send(null)

小结

  • 将脚本放在底部,</body>之前
  • 合并脚本,减少页面中外链脚本的数量
  • 无阻塞下载js
    • 使用defer或async属性
    • 动态创建script元素
    • 使用xhr下载js并注入页面中

2. 数据存取

管理作用域

  • 内部属性[[Scope]]包含了一个函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链
  • 执行函数时会创建一个执行环境,它的作用域链初始化为当前运行函数的[[Scope]]属性中的对象。这些值按照它们出现在函数中的顺序,被复制到执行环境的作用域链中,这个过程完成一个活动对象就为执行环境创建好了。执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取或存取数据。当函数执行完毕时,执行环境就被销毁

  • 全局变量总是存在于执行环境作用域链的最末端,可以用局部变量代替全局变量

  • 最好避免使用with语句,因为延长作用域链后访问局部变量将变慢。对于try-catch推荐将错误委托给一个函数处理
try{    method();}catch(ex) {    handleError(ex)}
  • 闭包性能问题:
function assignEvent(){        var id = "xdi9592";        document.getElementById('save-btn').onclick=function(){        saveDocument(id);    }}

当assignEvent函数执行时,一个包含了变量id以及其他数据的活动对象被创建。它成为执行环境作用域链中的第一个对象,而全局对象紧随其后。这个活动对象无法在assignEvent执行后被销毁,因为闭包也引用了它。当闭包执行时会创建一个执行环境,又一个活动对象为闭包自身所创建。

  • 对象成员包括属性和方法。
  • 对象成员搜索会消耗性能

小结

  • 访问字面量和局部变量的速度最快,访问数组元素和对象成员慢
  • 访问局部变量比访问跨作用域变量快
  • 避免使用with和try-catch
  • 嵌套对象成员影响性能
  • 属性或方法在原型链位置越深,访问越慢
  • 将对象保存在变量中

3. DOM编程

  • 减少DOM访问次数,把运算尽量放在ECMAScript这端处理
  • 推荐使用innerHTML更新一大段html
  • html集合一直与文档保持着连接,每次需要最新的信息时,都会重复执行查询过程。如: length属性,所以可以考虑缓存length
  • 访问集合元素时使用局部变量,如var coll = document.getElementById('app')
  • 遍历DOM
function testNextSibling() {//推荐nextSibling来查找DOM节点    var el = document.getElementById('s'),        fir = el.firstElementChild,        name;    do{        name = fir.nodeName;    }while(fir = fir.nextElementSibling);    return name;}function testChildNodes() {    var el = document.getElementById('s'),        children = el.children,        len = children.length,        name;    for(var i = 0;i < len;i++) {        name = children[i].nodeName    }    return name;}
  • 组合查询: var errs = document.querySelectorAll('div.warning,div.notice')
function search() {   var el = document.getElementById('s'),        ch = el.firstElementChild,        className = '',        arr = [];    do{        className = ch.className;        if(className === 'notice' || className === 'warning') {            arr.push(ch);        }    }while(ch = ch.nextElementSibling);    return arr;}
  • 缓存布局信息,比如offsetTop。。。
  • 如果有大量元素使用了:hover,那么会降低响应速度,此问题在ie8中尤为明显
  • 事件委托

重排与重绘

  • DOM树

表示页面结构

  • 渲染树

表示DOM节点如何显示

渲染树中的节点被称为“帧”或“盒”。当DOM变化影响了元素的几何属性,浏览器需要重新计算元素的几何属性,其他元素的几何属性和位置也会因此受到影响。
重排:浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。
重绘:完成重排后,浏览器会重新绘制影响的部分到屏幕中。

重排何时发生:
1. 添加或删除可见的DOM元素
2. 元素位置改变
3. 元素尺寸改变
4. 内容改变
5. 页面渲染器初始化
6. 浏览器窗口尺寸改变

最小化重排与重绘:
1. el.style.cssText+= "..."
2. el.className+= "active"
批量修改DOM:
1. 通过改变display属性,临时从文档中移除某元素
2. 在文档外创建并更新一个文档片段,然后把它附加到原列表中。
3. 先cloneNode,然后replaceChild

小结

  • 最小化dom访问次数
  • 用变量缓存dom节点
  • 小心使用html集合,可将它复制到一个数组
  • 使用更快的api如querySelectorAll和firstElementChild
  • 批量修改样式,离线操作dom树,使用缓存,减少访问布局信息的次数
  • 动画中使用绝对定位,使用拖放代理
  • 使用事件委托

4. 算法和流程控制

循环

  • for-in循环明显慢
  • 除非明确需要迭代一个属性数量未知的对象,否则应避免使用for-in循环。如果需要遍历一个数量有限的已知属性列表,使用其他循环类型会更快。
  • 减少每次迭代处理的事物
  • 减少迭代的次数
var i = items.length % 8;while(i) {    process(items[i]);}i = Math.floor(items.length/8);while(i) {    process(item[i--]);    process(item[i--]);    process(item[i--]);    process(item[i--]);    process(item[i--]);    process(item[i--]);    process(item[i--]);    process(item[i--]);//八个}
  • 将length属性存在变量中
  • 减少属性查找并反转
for(var i = items.length; i > 0; i--) {    process(items[i])}
  • 基于函数的迭代比基于循环的迭代要慢一些
  • 条件较少时用if-else,多时用switch
  • 合并排序数组
    递归:
function merge(left, right) {    var arr = [];    while(left.length && right.length) {        if(left[0] < right[0]) {            arr.push(left.shift())        }else {            arr.push(right.shift())        }    }    return arr.concat(left, right)}function mergeSort(items) {    if(items.length == 1) {        return items;    }    var middle = Math.floor(items.length / 2),        left = items.slice(0, middle),        right = items.slice(middle);    return merge(mergeSort(left), mergeSort(right));}

迭代实现

function merge(left, right) {    var arr = [];    while(left.length && right.length) {        if(left[0] < right[0]) {            arr.push(left.shift())        }else {            arr.push(right.shift())        }    }    return arr.concat(left, right)}function mergeSort(items) {    if(items.length == 1) {        return items;    }    var work = [];    for(var i = 0,len = items.length; i < len; i++) {        work[i] = [items[i]];    }    work.push([])//如果数组是奇数;    for(var lim = len; lim > 1;lim = (lim+1)/2) {        for(var j =0, k = 0; k < lim; j++, k += 2) {            work[j] = merge(work[k], work[k + 1])        }        work[j] = [];    }    return work[0]}
  • 重写factorial
function memorize(fundamental, cache) {    cache = cache || {};    var shell = function (arg) {        if(!cache.hasOwnProperty(arg)) {            cache[arg] = fundamental(arg)        }        return cache[arg]    }    return shell}function factorial(n) {    if(n == 1) {        return 1    }else {        return n*factorial(n-1)    }}var mem = memorize(factorial, {"0":1,"1":1})

小结

  • 避免使用for-in
  • 减少跌代次数和运算量
  • 浏览器的调用栈大小限制了递归算法

5. 字符串和正则表达式

字符串连接

str = "a" + "b" + "c"str = ["a", "b", "c"].join("");//避免重复分配内存和拷贝逐渐增大的字符串str = "a";str += "b";//稍微比concat好str = "a";str = str.concat("b", "c")

6. 快速响应的用户界面

  • 当一个脚本运行时,点击一个按钮将无法看到它被按下的样式,尽管onclick事件处理器会被执行
  • web workers适用于那些处理纯数据,或者与浏览器UI无关的长时间运行的脚本
  • web workers从外部线程中修改dom会导致用户界面出现错误(worker在UI线程之外)
  • 与worker通信
var worker = new Worker('code.js');worker.onmessage = function(event) {    alert(event.data);}worker.postMessage('nicholas');//code.jsimportScripts("file1.js","file2.js")self.onmessage = function(event) {    self.postMessage('hello'+event.data);}

小结

  • 任何js任务都不应执行超过100毫秒
  • 安排代码延迟执行,将任务分解
  • web workers允许在ui线程外执行js

7. Ajax

  • 使用xhr时,post和get对比:经get请求的数据会被缓存,url长度有限制
  • 后台读取图片并将他们转换为base64编码的字符串 "data:images/jpeg;base64,"+data
  • 对于少量数据而言,一个get请求往服务器只发送一个数据包。而一个post请求要发送一个装载头信息,和一个装载post正文。
  • beacons (new Image()).src = url+'?'+params.join(&) 服务器接收到数据并保存下来,无须向客户端发送任何回馈信息

数据格式

  • json(轻量易于解析的数据格式)
    体积更小,结构占用比例小,数据占用多,解析速度快
  • xml
    极佳的通用性,格式严格,易于验证。但是数据占比低,解析消耗多
  • html
    臃肿复杂,传输量大,解析事件长
  • 自定义格式
    把数据用分隔符链接起来,比较轻量

缓存数据

  • 服务器端设置http头确保响应被缓存
  • 客户端,把获取的信息存储到本地,避免再次请求

小结

  • 减少请求数,可合并js和css,或使用mxhr
  • 缩短页面加载时间,用ajax获取次要文件
  • 确保代码错误不会输出给用户,在服务器端处理错误

8. 编程实践

  • 避免双重求值,每次调用eval()时都要创建一个新的解释器/编译器实例
  • 避免重复工作
function addHandler(target, eventType, handler) {    if(target.addEventListener) {        addHandler = target.addEventListener(eventType, handler, false);    }else if(target.attachEvent) {        addHandler = target.attachEvent('on'+eventType, handler)    }    addHandler(target, eventType, handler)}function addHandler = document.body.addEventListener ?                     function(target, eventType, handler) {                        target.addEventListener(eventType, handler, false);                    }:                    function(target, eventType, handler) {                        target.attachEvent("on" + eventType, handler);                    }
  • 虽然js运行速度慢很容易归咎于引擎,然而引擎通常是处理过程中最快的部分,运行速度慢的实际上是你的代码。
    使用位操作符,原生方法,更快一点

小结

  • 避免使用eval和Function构造器来避免双重求值带来的性能消耗。给setTimeout和setInterval传递函数而不是字符串作为参数
  • 尽量使用直接量创建对象和数组。
  • 避免重复工作
  • 尽量使用原生方法

9. 构建js应用

  • 合并多个js文件,大大降低页面渲染所需的http
  • 预处理js文件
  • js文件压缩
  • js的服务器端http压缩。浏览器发送一个Accept-Encoding头,服务器返回Content-Encoding头。可以理解成编码
  • 缓存js
    • http缓存
    • 客户端缓存
    • h5离线应用缓存

当应用升级时,需要确保用户下载到最新的静态内容。可以通过把改动的静态资源重命名。多数情况下,开发者给文件增加一个版本或开发编号,也可以附加一个校验和

  • 使用内容分发网络cdn。相当于在客户端和服务器间设置镜像缓存

函数缓存

var store = {    nextId: 1,    cache: {},    add: function(fn) {        if(!fn.id) {            fn.id = store.nextId++;            return (!!store.cache[fn.id] = fn);        }    }}

自记忆函数

function isPrime(value) {    if(!isPrime.answers) isPrime.answers = {};    if(isPrime.answers[value] != null) return isPrime.answers[value];    var prime = value != 1;    for(var i = 2; i < value; i++) {        if(value % i ==0) {            prime = false;            break;        }    }    return isPrime.answers[value] = prime;}