zoukankan      html  css  js  c++  java
  • 前端代码性能优化

    前端代码优化
    前端标准html、js,查这里mozilla标准(w3c给的是纸面标准,这里是业界实际使用的标准)
    developer.mozilla.org/zh-CN/

    》作用域链越长,执行性能越差
    当函数执行时,会形成自己的执行环境,执行环境会与函数的作用域链进行链接,并创建与之关联的活动对象(activation object)。执行环境中定义的所有变量和函数都保存在这个对象中。当函数执行完成后,这个对象会被系统销毁。

    》避免函数内部多次逐级向上查找变量,使用局部变量存储一下
    例如在函数开头var doc=document

    》避免使用with
    with关键字指定其内部未指明作用域的变量都使用它声明的作用域
    with(document){
    var bd=body; // 相当于使用document.body
    }
    带来的性能问题是,with会使作用域链变长,降低内部本地变量的查找(会先从with指定的作用域查找,然后再查找本地作用域),建议不要使用。

    》减少DOM操作
    浏览器执行分两个引擎:ECMAScript引擎和DOM引擎,前者执行js代码,非常快,后者操作UI,无论是读还是写代价都很高。例如Chrome使用V8引擎来驱动ECMAScript,使用Webkit中的WebCore来驱动DOM。
    显著提高性能的策略就是增加JS操作,减少DOM操作。

    》DOM操作优化
    >注意获取DOM元素返回是类数组,其length是动态计算的
    var alldivs = document.getElementsByTagName("div");
    for (var i = 0; i < alldivs.length; i++){
    document.body.appendChild(document.createElement("div"));
    }
    这会进入死循环,因为alldivs.length是动态的,建议的用本地变量存起来,包括查找出的结果,减少不避要的重复查找(例如若多次访问alldivs[i],可用变量存起来)
    for (var i=0, len=divs.length; i<len; i++)
    即:越怕麻烦越麻烦

    》getElementById getElementsByTagName querySelectorAll
    现代浏览器对querySelectorAll() 都做了优化,复杂语句的查询效率得到提升,最重要的,它能直接接受多条CSS语句。对于相对复杂的查找,getElementById getElementsByTagName的效率并没有querySelectorAll() 高。
    var elements = document.querySelectorAll("#menu a");
    var elements = document.getElementById("menu").getElementsByTagName("a");

    》页面的重排与重绘
    >DOM树和渲染树:
    DOM树是整个页面的骨架,每个节点在渲染树上都至少有一个对应节点。隐藏的DOM树节点是没有对应的渲染树节点。
    渲染树上的每一个节点都可称之为frame或者box,对应CSS的盒子模型,margin、 padding、border等。
    当DOM树和渲染树都创建完成,页面便开始绘制(paint)。

    >重排与重绘
    如果DOM树的某些变化,导致某个元素的拓扑发生改变,例如边框变粗、加入文字等,那么浏览器就会重新计算该元素的拓扑结构,以及其它被影响到的元素的拓扑结构。同时浏览器会将这部分的拓扑改变,映射到渲染树上,对渲染树进行重新的构建,称之为重排(reflow)。
    当重排完成之后,浏览器就会对这部分进行重新绘制,称之为重绘(repaint)。
    并不是所有的DOM树改变都会影响拓扑结构。例如改变某个元素的背景色,就只会导致重绘,而不会导致重排。但重排一定会导致重绘。重排或者重绘都会大大消耗系统资源。

    >导致重排的因素有:
    一个可见的DOM元素被添加或者删除
    元素改变的位置
    元素的尺寸发生了变化(margin、padding、border、width、height等等)
    内容发生了改变(例如文字、图片的增删改)
    页面初始化加载
    浏览器窗口发生了尺寸的改变

    >导致马上重绘的因素有:
    现代浏览器对重排与重绘操作,都做了很好的优化。即使频繁操作,浏览器也会进行相应的管理,构建队列等,最后再一次性进行重排或重绘。
    但如果用户读取下面这些属性,浏览器为了获得正确的值,就会马上重绘页面,消耗大量资源:
    offsetTop、offsetLeft、offsetWidth、offsetHeight
    scrollTop、scrollLeft、scrollWidth、scrollHeight
    clientTop、clientLeft、clientWidth、clientHeight
    getComputedStyle() (currentStyle in IE)

    》减少重排与重绘
    >避免DOM属性的读和写操作交叉
    var computed, tmp = [], bodystyle = document.body.style;
    if (document.body.currentStyle) { // IE, Opera
    computed = document.body.currentStyle;
    } else { // W3C
    computed = document.defaultView.getComputedStyle(document.body, "");
    }
    bodystyle.color = "red"; // 写
    tmp[0] = computed.backgroundColor; // 读
    bodystyle.color = "white"; // 写
    tmp[1] = computed.backgroundImage; // 读
    bodystyle.color = "green";
    tmp[2] = computed.backgroundAttachment;
    上面的操作会导致三次马上重绘

    bodystyle.color = "red";
    bodystyle.color = "white";
    bodystyle.color = "green";
    tmp[0] = computed.backgroundColor;
    tmp[1] = computed.backgroundImage;
    tmp[2] = computed.backgroundAttachment;
    这样优化后只重绘一次

    var el = document.getElementById("mydiv");
    el.style.borderLeft = "1px";
    el.style.borderRight = "2px";
    el.style.padding = "5px";
    尽管浏览器会对这三次操作进行排队、合并,但还是应该采用合理的方式(始终记住js操作效率远高于dom操作):
    el.style.cssText += "border-left:1px; border-right:2px; padding:5px;";
    或者写成一个外部css样式

    >充分利用DocumentFragment
    在创建多个相同或者相似元素时,应该合理利用文档碎片DocumentFragment来处理,浏览器有专门的优化。例如给页面一次性添加一百万个<div>元素:
    function createDivs(){
    var tFrag = document.createDocumentFragment();
    for (var i=0; i<1000000; i++){
    tFrag.appendChild(document.createElement("div"));
    }
    document.body.appendChild(tFrag);
    }

    >让元素脱离文档流
    显示或者隐藏页面的某个部分,是经常遇到的需求。尤其在制作动画的时候。重排或者重绘有时只会影响页面的某个区域,有时会影响整棵树。
    当一个元素从页面顶部动画到底部时,可能会影响整个页面的DOM树,因此可以考虑将其移出文档流。
    ->将元素设置为绝对定位,让其脱离文档流。
    ->进行动画操作,只影响页面局部。
    ->再根据情况,重新修改position属性,让其回归文档流。

    》使用事件委托
    利用事件冒泡机制,将事件绑定到父元素,子元素的事件委托给父元素处理,避免子元素频繁的变化导致重复绑定。event delegation
    var myul = sina.$("newslist");
    var lis = myul.getElementsByTagName("li");
    for(var i=0,l=lis.length;i<l;i++){
    sina.addEvent(lis[i],"click",function(e){
    e = e || window.event;
    var target = e.target || e.srcElement;
    alert(target.innerHTML);
    });
    }
    可优化为:
    var myul = sina.$("newslist");
    sina.addEvent(myul,"click",function(e){
    e = e || window.event;
    var target = e.target || e.srcElement;
    if(target.tagName.toLowerCase()=="li"){
    alert(target.innerHTML);
    }
    });
    典型应用:地图、Gmail、新闻列表翻页等等。

    》一些编程技巧
    for-in是四种循环里性能明显低于其它三种的,应尽量避免使用。只有在不知道对象属性数量、名称时才考虑;
    减少对length属性的查询能够提高性能;
    if-else与switch性能差不多,而lookup tables查表法对于键值对key-value类型的分支判断的有效;
    空间换时间,例如把递归结果缓存起来直接使用

    》字符串操作
    + += join可大胆使用,减少concat使用
    充分利用数组的方法处理字符口串,例如反转字符串 str.split("").reverse().join("")足够高效和简洁

    》正则表达式优化
    所有浏览器执行正则表达式都很快,稍微复杂的字符串匹配,用正则校验会比自己写校验逻辑更高效。

    一个正则表达式在匹配字符串的时候,经历了四个步骤:
    编译、设置开始点、匹配每一个正则标识、成功或者失败,步骤3可能有回溯,步骤2-4可能重复执行

    >编译
    创建一个正则表达式时,浏览器会检查是否符合规则。然后将该正则编译到底层,用于之后的对比。因此正则的速度通常都较快,是在浏览器更底层来完成对比,能应对复杂查询。
    var myReg = "/h(ello|appy) hippo/g"; /g表示全局匹配,或尽可能长匹配,/i表示不区分大小写

    正则表达式的优化主要是在减少回溯次数
    var myReg = "/h(ello|appy) hippo/g";
    var tStr = "hello there, happy hippo";
    ->从字符串的第一个位置开始匹配,h匹配成功,字符串这个位置被记录。
    ->正则逐一往后走,ello 都匹配成功,但h与t匹配失败。此时正则回溯(backtrack),回溯到之前成功的位置h,试图匹配appy,匹配失败。
    ->字符串返回到之前记录的位置h后面的一个位置e,重新匹配整个正则。无法匹配h,下一个位置l,也失败。直到第14个字符,又是h,匹配成功。
    ->重复步骤2,ello 匹配失败,此时正则回溯(backtrack)到之前成功的位置h,试图匹配appy,成功匹配。继续往下 hippo匹配也成功。
    整个匹配过程正则表达式有两次回溯,一次在字符串的第一个h,另一次在字符串的第二个h。

    var str = "<p>Para 1.</p><img src='s.jpg'><p>Para 2.</p><div>Div.</div>";
    var reg1 = /<p>.*</p>/; //16次匹配出结果
    var reg2 = /<p>.*?</p>/; //22次匹配出结果
    对比贪婪模式与懒惰模式:
    第一个正则表达式是贪婪匹配,在匹配<p>成功后,.* 会直接匹配到字符串的末端,然后往前逐一寻找<。
    第二个正则表达式是懒惰匹配,在匹配<p>成功后,.*? 会从P字符开始,逐一往后寻找<。
    每一次匹配不成功,正则表达式都会回溯到 .* 或 .*?,寻找下一个分支。.* 回溯后是减少一个字符,.*? 回溯后是增加一个字符。
    每回退一个字符,整体匹配一下模式串看是否成功

    由于 | 很容易导致回溯,应当尽可能的采用别的方式。
    不要使用 使用
    cat|bat [cb]at
    red|read rea?d
    red|raw r(?:ed|aw)
    (.| | ) [sS]

    其他分析参见PPT详解

    》Web Workers多线程
    javascript是一门单线程的语言,在遇到复杂计算时,可能会阻断整个页面,HTML5通过workers的模式,在一定程度上给js开启了多线程的大门。

    外部的Worker是不能操作主线程DOM树的,这样也能保证在执行子线程的时候,主线程的UI不会被打断。在子线程中,通过 self 对象来指向自己。与主线程的通讯方式也是通过 message,接收、计算完成后 post 回去。

    》其他
    js变量提升:只是声明提升,赋值动作不会,所以刚开始会是undefined
    js原型链:每一个对象都有成员属性_prop_指向类,类本身也有属性_prop_指向父类(如object),对象通过this访问成员属性或方法且本身没有命中时,会自动从_porp_中搜索原型链。假设有类(或函数)Book和对象book1、book2,当给类添加方法,即Book.protype.sayHi = function (){},则对象book1、book2能访问到sayHi方法。

  • 相关阅读:
    python学习笔记(十五)-异常处理
    python学习笔记(十四)python实现发邮件
    python学习笔记(十三)-python对Excel进行读写修改操作
    python学习笔记(十二)-网络编程
    python学习笔记(十一)-python程序目录工程化
    python学习笔记(九)-函数2
    python学习笔记(八)-模块
    勿忘初心,勇往前行
    【tp6】解决Driver [Think] not supported.
    【Linux】LNMP1.6 环境报500错误解决方法
  • 原文地址:https://www.cnblogs.com/ccdat/p/11788106.html
Copyright © 2011-2022 走看看