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方法。

  • 相关阅读:
    MVP模式与MVVM模式
    webpack的配置处理
    leetcode 287 Find the Duplicate Number
    leetcode 152 Maximum Product Subarray
    leetcode 76 Minimum Window Substring
    感知器算法初探
    leetcode 179 Largest Number
    leetcode 33 Search in Rotated Sorted Array
    leetcode 334 Increasing Triplet Subsequence
    朴素贝叶斯分类器初探
  • 原文地址:https://www.cnblogs.com/ccdat/p/11788106.html
Copyright © 2011-2022 走看看