博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
高性能JavaScript精简笔记
阅读量:6072 次
发布时间:2019-06-20

本文共 13796 字,大约阅读时间需要 45 分钟。

第二章 (数据存取)

一.四种基本的数据存取位置

(1)字面量

  字面量只代表自身,不存储在特定的位置。javaScript中的字面量有:String,Number,Boolean,Object,Array,Function,Regex,null,nudefined值。

(2)本地变量

  使用关键字定义变量。例:var定义的数据存储单元。

(3)数组元素

  存储在javaScript数组对象内部,以数字作为索引。

(4)对象成员

  存储在javaScript对象内部,以字符串作为索引。

二.管理作用域

  1.作用域链 ([[Scope]])

  [[Scope]]:js一切万物皆对象,Function也是对象,[[Scope]]是Function的一个内部属性,该内部属性包含了函数被创建的作用域中对象的集合。它决定了哪些数据能被函数访问。

function add(){ //创建add函数过程中,它就产生了内部作用域,然后它的作用域链中也插入了一个对象变量,这个全局对象代表着所有在全局范围内定义的变量,该全局对象包含了window,docunment,等。 }
  然后在执行add这个函数时,会产生一个称为执行上下文(执行环境)的内部对象。   每次执行add函数都会创建一个独一无二的执行环境,所以多次调用一个函数就会导致创建多个执行环境,当函数执行完毕,执行环境也随之销毁。   每个执行环境都有自己的作用域链,用于解析标识符。  2.标识符解析的性能     (1)解析标识符是有代价的,一个标识符所在的位置越深那么解析的速度则越慢。因此函数中局部变量的读写总是最快的,而读写全局变量通常是最慢的。
function add (){     var a = document.body;     var b = document.getElementId('id');     var c = document.getElementId('abc');}//该函数引用了三次document,而document是全局对象,搜索该对象的过程必须要遍历整个作用域链,直到最后在全局变量对象中找到。

    (2)优化:

function add (){    var doc = doucment;     var a = doc .body;     var b = doc .getElementId('id');     var c = doc .getElementId('abc');}//先将一个全局变量的引用储存在一个局部变量中, 然后使用整个局部变量替代全局变量doucment。所以执行时只执行一次搜索全局变量的过程。

   3.改变作用域链(with,try-catch

   (1):with语句

function initUI(){    //当使用with语句时,执行环境的作用域就会被临时改变。   with(document){ //避免多次书写document。但是却生成可以个包含了document对象所有属性的可变变量。导致这个可变变量被置于作用域链头部了。              var bd = body;  //所以访问局变量会变慢,访问doucment却很快。    }}

    (2):try-catch语句

try{      // do something}catch (ex){ // 把异常对象推入一个变量对像并且置于作用域链的首位。        alert(ex.message)//作用域在此处被改变        var a  = "123"; //局部变量都存在于第二个作用域链中了。    handleError(ex) //所以我们要委托给错误处理器函数。 由于只执行一条语句,并且没有局部变量的访问,所以作用域的临时改变就不会影响代码性能。 }

  4.闭包,作用域,内存

  (1)闭包:它允许函数访问局部作用域之外的数据。

function assing(){    var id = '123'; //执行assing函数时,就创建了包含id这个变量以及其他数据的活动对象。它成为了作用域链的第一个对象。    saveDom(id); //当闭包被创建的时候,它的[[scope]]属性被初始化为这些对象。也就是说 闭包的[[scope]]属性包含了与执行环境作用域链相同的对象的引用。因此会产生副作用。     }//通常只要执行环境执行完毕,活动对象即可销毁,但是由于闭包的引入,所以激活对象无法被销毁。所以因此会造成性能的损耗。甚至内存泄漏。//想要减轻闭包对执行速度的影响:将常用的跨作用域变量存储在局部变量中,然后直接访问局部变量。例:上面的id。

 

  5.原型

  (1)js一切皆对象,对象都是基于原型的。

  (2)_proto_:每一个对象都有一个_proto_属性,通过它去绑定对象的原型。

  (3)对象有两种成员:原型成员,实例成员。实例成员直接存在于对象实例中,原型成员则从对象原型继承而来。

  

var book =  {    title:'对象', //实例成员}alret(book.toString) // [object object]  对象继承而来的原型成员

 

  (4)使用hasOwnProperty()方法判断对象是否含有某实例成员,参数:成员名称。也可以使用 in 操作符 判断对象是否包含特性的属性。

 

var book =  {    title:'对象', //实例成员}//通过 hasOwnProperty() 方法 可以判断对象是否包含实例成员。alret(book.hasOwnProperty('title')) // true alret(book.hasOwnProperty('toString')) // false //通过in操作符 可以判断对象是否包含特定的属性,当然包括实例成员alret('title' in book) // true  alret('toString' in book) // true

 

  6.原型链

  (1)对象的原型决定了对象的类型!默认情况下,所有的对象都是对象(Object)的实例,并继承了所有的基本方法。

  (2)通过构造函数创建另外一种类型的原型。

function Book (title){    this.title = title;}Book.prototype.sayTitle =function(){        alert(this.title);}var book1 = new Book ('world'); var book2 = new Book ('hello'); alert(book1 instanceof Book) // truealert(book1 instanceof Object) // truebook1.sayTitle(); // "hello";alert(book1.toString) // [object object] //使用新构造函数 Book 来创建一个新的 Book 实例。 //实例 book1 的原型(_proto_)是Book.prototype,而 Book.prototype的原型(_proto_)是Object。这就是原型链的过程。 //book1继承了原型链中的所有成员。

 

   (3)注意:这两个 Book 实例共享着同一个原型链,它们有着自己的属性 'title' ,而其他部分都继承自原型

  (4)搜索原型链:例如 当调用 book1.toStting() 时,搜索过程会深入到原型链中直到找到这个方法为止。所以对象在原型链中存在的位置越深,则找到它越慢。

  (5)请记住:搜索实例成员比从字面量或者局部变量中读取数据代价更高,再加上遍历原型链带来的开销,性能问题会更加严重。

 

第三章 (文档对象模型DOM)

  一:浏览器世界中的DOM

    (1)dom是一个独立于语言的,使用XML,HTML文档操作的应用程序接口(API)

    (2)主要是与HTML文档打交道

    (3)尽管dom是与语言无关的API,但是在浏览器中的接口却是以javaScript实现的。

  二:天生就慢

    (1)简单来说,两个相互独立的部分以功能接口连接就会带来性能消耗。例如:把DOM和javaScript大当做两个岛屿,两者之间以一座收费桥连接,那么每次js访问dom都需要给过桥费,访问的越多次,过桥费就越高。所以建议尽量减少过桥费。

  三:DOM修改和访问

    (1)正如上方所说,访问一个DOM元素的代价是支付一次过桥费,那么修改元素的费用可能会更贵。因为它经常导致浏览器重新计算页面的几何变化。

    (2)for循环执行dom的修改和访问是最为严重的。 

function innerHTMLLoop() {    for (var count = 0; count < 15000; count++) {         document.getElementById('here').innerHTML += 'a';    }}//此代码每次循环都对dom元素访问两次,一次读取innerHTML属性内容,另一次是写入它。

      (3)解决:

function innerHTMLLoop2() {    var content = '';    for (var count = 0; count < 15000; count++) {        content += 'a';    }    document.getElementById('here').innerHTML += content;}    //将循环的内容赋值给局部变量,然后一次性写入到dom元素中,减少dom的操作。

  四:innerHTML 和 DOM方法 比较

    (1)老式浏览器innerHTML快,新式浏览器DOM方法快。

  五:节点克隆

    (1)使用DOM方法更新页面内容的另外一个方法就是克隆已有的DOM元素,效率会高一些。

  六:HTML集合

    (1)HTML集合是用于存放DOM节点应用的类数组对象,

    (2)下列函数的返回值就是一个集合

 

document.getElementsByName()document.getElementsByClassName()document.getElementsByTagName_r()//返回页面所有的img元素document.images//返回页面所有的a标签document.links//返回页面中所有的form表单document.forms//返回页面中第一个表单的所有字段document.forms[0].elements

 

       (3)以上的这些方法和属性返回的对象,是一种类数组列表,它们不是数组,但是提供length属性。

      (4)HTML集合实际上就是在查询文档,当更新信息时,每次都要重复执行这种查询操作。例如读取集合中元素的length索引。这正是低效率的来源。

  七:昂贵的集合

//意外的无限循环var alldivs = document.getElementsByTagName_r('div');for (var i = 0; i < alldivs.length; i++) {   document.body.appendChild(document.createElement('div'))}//每次迭代过程访问集合的length属性时,它导致集合器更新,在所有的浏览器中会产生明显的性能损失。//优化:将集合的长度存进一个局部变量,然后循环判断条件中使用这个变量。function loopCacheLengthCollection() {    var coll = document.getElementsByTagName_r('div'),    len = coll.length;    for (var count = 0; count < len; count++) {         //do Something    }}

  八:访问集合元素时使用局部变量

//最快的方法(将所有全局变量存储在局部变量)function collectionNodesLocal() {    var coll = document.getElementsByTagName_r('div'),     len = coll.length,    name = '',    el = null;    for (var count = 0; count < len; count++) {        el = coll[count];        name = el.nodeName;        name = el.nodeType;        name = el.tagName;    }    return name;};

  九:重绘和回流

    (1)重绘改变一个元素的背景色不影响它的高度和宽度。只要元素的布局没有改变,称之为重绘。

    (2)回流:

      1.元素的尺寸发生改变,

      2.添加和删除可见的DOM元素

      3.元素的位置改变

      4最初的页面渲染

      5.页面的尺寸改变

  十:最小化重绘和回流

   (1)每一次浏览器都要刷新渲染队列并回流。因为computed的风格被查询而引发

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 = computed.backgroundColor;bodystyle.color = 'white';tmp = computed.backgroundImage;bodystyle.color = 'green';tmp = computed.backgroundAttachment;

    (2)优化:不要在布局信息改变时查询它。等到布局信息改变完成后再查询

bodystyle.color = 'red';bodystyle.color = 'white';bodystyle.color = 'green';tmp = computed.backgroundColor;tmp = computed.backgroundImage;tmp = computed.backgroundAttachment;

    (3)改变风格:

//此代码改变了三个风格属性,每次改变都影响元素的几何属性,它导致浏览器回流了三次。var el = document.getElementById('mydiv');el.style.borderLeft = '1px';el.style.borderRight = '2px';el.style.padding = '5px';

    (4)优化:

//将所有改变风格合并在一起执行,只修改DOM一次。var el = document.getElementById('mydiv');el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';//当然也可以追加属性el.style.cssText += '; border-left: 1px;';//另外一个就是改变元素的class值。var el = document.getElementById('mydiv');el.className = 'active';

   十一:批量修改DOM

  var data = [    {      "name": "Nicholas",      "url": "http://nczonline.net"    },    {      "name": "Ross",      "url": "http://techfoolery.com"    }  ];function appendDataToElement(appendToElement, data) { var a, li; for (var i = 0, max = data.length; i < max; i++) { a = document.createElement('a'); a.href = data[i].url; a.appendChild(document.createTextNode(data[i].name)); li = document.createElement('li'); li.appendChild(a); appendToElement.appendChild(li); }};

    (1)批量操作DOM时,以下三种方法可以减少重绘和回流的次数。

      1.隐藏元素,进行修改,然后再显示它

var ul = document.getElementById('mylist');ul.style.display = 'none';  //先隐藏appendDataToElement(ul, data);ul.style.display = 'block'   //再显示

        2.使用一个文档片段在已存DOM之外创建一个子树,然后将它拷贝到文档中。

var fragment = document.createDocumentFragment(); //创建文档片段appendDataToElement(fragment, data);document.getElementById('mylist').appendChild(fragment);

        3.将原始元素拷贝到一个脱离文档的节点中,修改副本,然后覆盖原始元素。

var old = document.getElementById('mylist');var clone = old.cloneNode(true);appendDataToElement(clone,data);console.log(old.parentNode) //body//replaceChild将某个子节点替换掉old.parentNode.replaceChild(clone,old);

 

  十二:缓冲布局信息

    (1)说白了尽量减少对布局信息的查询次数,查询时将它赋值给局部变量,并且使用局部变量进行计算,例如偏移量,滚动条等。

 

//低效率myElement.style.left = 1 + myElement.offsetLeft + 'px';myElement.style.top = 1 + myElement.offsetTop + 'px';if (myElement.offsetLeft >= 500) {stopAnimation();}//高效率var current  myElement.offsetLeft;current++myElement.style.left = current + 'px';myElement.style.top = current + 'px';if (current >= 500) {stopAnimation();}

 

  十三:总结

    (1)最小化DOM访问,在javaScript端做尽可能多的事情。

    (2)在反复访问的地方使用局部变量存放DOM引用。

    (3)小心地处理HTML集合,以为他们表现出 ‘存在性’ ,总是对底层文档重新查询。将集合的length存储到一个变量中,在迭代中使用这个变量,如果经常操作这个集合,可以将它拷贝到数组中。

    (4)如果可以,使用速度更快的API。 例如:querySelectorAll()....

    (5)注意重绘和回流:批量修改风格,离线操作DOM树,缓存并减少对布局信息的访问。

    (6)动画中使用绝对坐标,使用拖放代理。

    (7)使用事件托管技术最小化事件句柄数量。

 

 第四章:算法与流程控制

  一:四种循环类型

//1.for循环for(var i=0;i<10;i++){        //do....}//2.while循环(简单的预测循环,由预测条件和循环体组成)var i=0;while(i<10){     //do...     i++;    }//3.do-while后测试的循环。var i =0; do{    // do...    }while(i++<10);//for-in对象循环。(相比以上,它是最慢的循环)for(var props in boject){    //do...}

  二:优化循环性能

 

    (1):减少迭代的工作量   

//1.将length长度存储在局部变量中for(var i=0,len

    (2):减少迭代次数

      达夫设备:限制循环迭代次数的模式.(元素总数超过1000个以上使用)
var iterations = Math.floor(items.length / 8),startAt = items.length % 8,i = 0;do {switch(startAt){case 0: process(items[i++]);case 7: process(items[i++]);case 6: process(items[i++]);case 5: process(items[i++]);case 4: process(items[i++]);case 3: process(items[i++]);case 2: process(items[i++]);case 1: process(items[i++]);}startAt = 0;} while (--iterations);//达夫设备基本原理:每次循环中最多可8次process(),循环迭代次数为元素的总数除以8,因为总数不一定是8的倍数,所以需要用startAt 变量存放余数,指出第一次循环中应当执行多少次方法; //比方现有12个元素,那么第一次循环调用process()4次,第二次循环调用process()8次。用两次循环代替了12次循环。//此算法是一个比较快的方法,取消了switch表达式,将余数处理与主循环分开。var i = items.length % 8;while(i){process(items[i--]);}i = Math.floor(items.length / 8);while(i){process(items[i--]);process(items[i--]);process(items[i--]);process(items[i--]);process(items[i--]);process(items[i--]);process(items[i--]);process(items[i--]);}

  三:基于函数的迭代

    (1)forEach() 函数在火狐,谷歌,Safari中为原生函数。

    (2)forEach()还是基于循环的迭代要慢一些。每个数组项要关联额外的函数调用是造成速度慢的原因。

   四:if-else与switch比较

    (1)基于可读性:条件数量大倾向switch,而不是if-else。

    (2)性能速度:事实证明,大多数情况下switch表达式比if-else更快。条件数量越大越明显。

   五:查表法

    (1)与if-else和switch相比,查表法不仅非常快,而且当需要测试的离散值数量非常大时,也有助于保持代码的可读性。

//switchswitch(value){case 0:return result0;case 1:return result1;case 2:return result2;}//查表法(必须消除所有判断条件)操作转换成一个数组查询或者对象查询var results = [result0, result1, result2]return results[value]

 

   六:递归

    (1)使用递归可以将复杂的算法变得简单。例如如下:阶乘

function factorial(n){   if(n==0){      return 1;    }else{     return n*factorial(n-1);    }}

    (2)递归函数存在的问题

      1.终止条件不明确或缺少终止条件会导致函数长时间运行,并使得用户界面出现假死状态。

      2.还可能出现 "调用栈大小限制" 问题.

 

      (3)调用栈限制

      1.js引擎支持的递归数量与js调用栈大小直接相关。只有IE例外:它的调用栈与系统空闲内存有关。

 

//当递归函数超过浏览器的调用栈容量时的报错信息IE:'Stack overflow at line x';Firefox:'Too much recursion'Safari :"Maximum call stack size exceeded"Opera : 'Abort (control stack overflow)'Chrome是唯一不显示调用栈溢出错误的浏览器 我们可以这样捕获它try{  recurse();}catch(ex){    alert('Too much recursion!')      }

    七.递归模式

//1.调用自身function recurse(){    recurse()} recurse()//2.两个函数相互调用(形成一个无限循环,很难定位问题)funcion first(){   second()}function second(){   first()} first();为了能在浏览器中更安全的工作建议使用:迭代,Memoization 或者结合两者一起使用

 

//并归排序法function merge(left, right) {    var tmp = [];   while(left.length && right.length) {    if(left[0] < right[0]) {        tmp.push(left.shift());    } else {        tmp.push(right.shift());    }        return tmp.concat(left, right);    }}function mergeSort(a) {    if (a.length === 1){        return a;    }    var mid = Math.floor(a.length / 2)         , left = a.slice(0, mid)         , right = a.slice(mid);    return merge(mergeSort(left), mergeSort(right));}console.log(mergeSort([5,1,4,3])) //[1,3,4,5]

 

    以上的排序法 由于频繁调用mergeSort()函数,这意味着如果一个长度超过1500的数组就会发生栈溢出错误。

    以下用迭代实现。

function merge(left, right) {  var result = [];   while (left.length && right.length) {    if (left[0] < right[0])      result.push(left.shift());    else      result.push(right.shift());  }   return result.concat(left, right);}function mergeSort(a) {  if (a.length === 1){    return a; }  var work = [];  for (var i = 0, len = a.length; i < len; i++){    work.push([a[i]]); }  work.push([]); // 如果数组长度为奇数,避免下面work[k+1]越界   for (var lim = len; lim > 1; lim = Math.floor((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];}

 

 第六章:快速响应的用户界面

  一:浏览器UI线程

  什么是浏览器UI线程:用于执行JavaScript和更新用户界面的进程通常被称为‘浏览器UI线程’。

  UI线程的工作:UI线程的工作基于一个简单的队列系统,任务会被保存到队列中直到进程空闲,一旦空闲,队列中的下一个任务就被重新提取出来并且运行,这些任务包括js,页面更新,重绘和回流等。

  二:浏览器的限制

  什么是浏览器限制:浏览器限制了JavaScript任务的运行时间,这种限制是有必要的,它确保某些恶意代码不能通过永不停止的密集操作锁住用户的浏览器或计算机。

  浏览器限制的种类:分两种,1.调用栈大小限制,2.长时间运行脚本限制。

 

  三:定时器使用

  无论发生何种情况,创建一个定时器会造成UI线程暂停,定时器代码会重置所有相关的浏览器限制,包括长时间运行脚本定时器。此外,调用栈也在定时器的代码中重置为0,这一特性使得定时器成为长时间运行脚本代码的理想跨浏览器解决方案。

 

  四:使用定时器处理数组

  1.常见的一种造成长时间运行脚本的起因就是耗时过长的循环。可以使用定时器将循环的工作分解到一系列的定时器中。

//典型的简单循环模式for(var i=0,len=items.length;i

  2.是否可以使用定时器的两个决定性因素。

  (1)处理过程是否必须同步?

  (2)数据是否必须按顺序处理?

  如果答案都是‘否’,那么代码将适用于定时器分解任务。一种基本的一部代码模式如下:

function processArray(items,process,callback){    var todo = items.concat(); //克隆原数组    setTimeout(function(){        //取得数组的下个元素并进行处理        process(todo.shit());          //如果还有需要处理的元素,创建另外一个定时器       if(todo.length>0){             setTimeout(arguments.callee,25);       }else{             callback(items);       }    },25);}var items=[123,456,789,147,258,369];function outputValue(value){   console.log(value);}processArray(items,outputValue,function(){       consloe.log('Done!');})//processArray()函数三个参数,待处理的数组,对每一项数组调用的函数,处理完成后运行的回调函数。//只要todo数组中还有条目,那么就再启动一个定时器,因为下一个定时器需要运行相同的代码,所以第一个参数是arguments.callee,该值指向当前正在运行的匿名函数。

 

  

 

 

 

 

后续持续更新!!!

 

 

 

 

 

    

      

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/MJ-MY/p/10538241.html

你可能感兴趣的文章
什么是ccflow公文流程?如何使用ccflow开发一个公文流程?
查看>>
java工作流引擎Jflow父子流程demo
查看>>
tar命令
查看>>
验证框架Validation的用法
查看>>
CentOS 7防火墙配置
查看>>
Brewer: (CAP) Theorem
查看>>
python加解密
查看>>
win8与Linux可以和平共处
查看>>
薛峰SEO培训:学习SEO的五个步骤
查看>>
如何分布式运行mapreduce程序
查看>>
Hiding the iPhone Tab Bar with TTNavigator
查看>>
史上最全蓝屏代码!电脑蓝屏了查一下什么原因吧!
查看>>
ntp对时服务配置
查看>>
JQuery - first/last child selector
查看>>
String, StringBuffer, StringBuilder
查看>>
所有的设计思想都是源于现实的实际需求
查看>>
打印机ip
查看>>
页面接口防刷 解决思路一nginx
查看>>
php接口的问题
查看>>
《MySQL入门很简单》学习笔记1——概论,数据类型,操作数据库
查看>>