《高性能JavaScript》笔记总结

一、加载和执行
1、脚本是如何下载以及最佳位置?
脚本是串行加载的,一个脚本下载完成执行完之后另外一个脚本才会再进行下载执行。有些高版本的浏览器已经支持脚本的并行下载,但浏览器还是会等待脚本都下载完成执行之后再继续。假设脚本提前放在头部,就会导致脚本阻塞其他资源的下载,所以脚本的最佳位置应该要放在标签的底部,以尽量减少对整个页面下载的影响。
2、如何优化下载脚本时阻塞页面渲染?
(1)脚本合并;减少页面中外链脚本文件的数量将会改善性能。
(2)无阻塞脚本;页面在下载js脚本的时候,就不会去下载其他的资源,这样会导致浏览器锁死一段时间,页面就会出现空白,所以推出无阻塞脚本。无阻塞脚本就是等页面加载完成之后才下载js文件;即window对象的load事件被触发的时候才去下载脚本。
3、无阻塞脚本的方式?
(1)使用script标签的defer属性;Defer属性指明该脚本不会修改DOM,因此代码能安全延迟执行。
优点:添加defer属性的script标签可以放在文档的任何位置,都会等待window的onload事件触发之前被执行。
缺点:不是理想型的跨浏览器解决方案,低版本的浏览器不兼容。
(2)动态脚本元素;无论何时启动下载,文件的下载和执行都不会阻塞其他进程。
优点:跨浏览器兼容。
缺点:如果下载的js文件过多且彼此之间有序加载,会使得代码产生回调地狱。不是那么美观。
实现封装代码:

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.url=url;
             document.getElementsByTagName('head')[0].appendChild(script);
   }

(3)XMLHttpRequest脚本注入,运用XHR对象获取脚本并注入页面中。
优点:跨浏览器兼容;下载js文件但不执行即可实现按需执行。
缺点:js文件必须和所请求的页面处于相同的域即js不能从CDN的方式下载。
实现封装代码:

function xhrLoadScript(){
          var xhr=new XMLHttpRequest();
          xhr.open("get",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.body.appendChild(script);
                  }
              }
          }
          xhr.send(null);
   }

二、数据存取
1、如何理解作用域链;函数执行环境;标识符解析最优化?
(1)作用域链是一个集合包含一个函数被创建的作用域中对象的集合;每个函数对象在创建的时候有一个内部属性[[Scope]],它相当于一个指针,指向了函数作用域链。
(2)函数每次调用时会创建一个独一无二的执行环境的内部对象(执行上下文)。每个执行环境的内部对象拥有自己的作用域(用于解析标识符),包含函数创建时[[Scope]]所指向的作用域链(复制过来的全局对象)和函数运行时的变量对象(生成的活动对象);活动对象是局部变量在作用域链的最顶端,函数执行完执行上下文和活动对象(闭包结构的函数的活动对象除外)都随之消失。
(3)一个标识符所在的位置越深,它的读写速度就越慢。全局变量总是存在于执行环境作用域的最末端,所以读写速度最慢。如函数引用多次同一全局变量,先将这个全局变量的引用存储在一个局部变量中,然后使用这个局部变量代替全局变量。
2、在函数中,多次查找同一对象属性如何实现最优化?
最佳做法是将属性保存到局部变量中。
三、DOM编程
1、DOM是什么,为什么慢?
DOM是Document Object Modal(文档对象模型)的缩写,是一个独立于语言的,用于操作XML文档,HTML文档的程序接口(API)。浏览器中的DOM和JavaScript是独立实现的两个功能,那么相互独立的功能只要通过接口彼此连接,就会产生消耗。
2、访问和修改DOM如何实现最优化?
(1)最小化DOM访问次数,尽可能在JavaScript端做处理。
(2)如果需要多次访问某个DOM节点,使用局部变量存储它的引用;当遍历一个HTML集合时。第一优化原则是把集合存在某个局部变量中,并把length缓存在循环的外部,然后使用局部变量替代这些需要多次读取的元素。
(3)HTML集合(类数组对象)实时联系着底层文档(假定实时态),那么每操作一次HTML集合就会重新操作着底层文档。迭代过程中,将集合的长度缓存在局部变量并且使用它,如果要经常操作集合,建议把它拷贝到一个数组中。
(4)如果可能的话,使用速度更快的API;比如querySelectAll()和fristElementChild。
3、什么是重排和重绘?
浏览器下载页面中的所有组件之后会解析并生成DOM树(页面结构)和渲染树(DOM节点如何显示)。DOM树上的每一个节点在渲染树上都有与之对应的节点(不包括隐藏的节点),被称为“帧”或者“盒”。当DOM的几何属性发生变化了,渲染树要重新构造这个过程叫做重排;重排之后,浏览器会重新绘制,这个过程叫做重绘。
4、如何实现最小化重排和重绘?
(1)动态改变元素几何属性时,一次性处理DOM元素所需的样式;使用cssText属性可以实现或者修改元素的className。
(2)往页面修改或者新增DOM元素时,让DOM元素脱离文档;最常用的方式是使用文档片段,在当前DOM之外构建一个子树,再把它拷贝到文档。
(3)缓存布局信息;尽量减少布局信息的获取次数,获取后把它赋值给局部变量,然后再操作局部变量。
(4)做动画展开折叠的元素,利用绝对定位让其元素脱离文档流。
5、什么是事件委托?
每个事件都会经历事件捕获,到达目标,事件冒泡的三个经历。事件捕获就是事件到达目标之后,沿着捕获阶段的路线原路返回,逐层向上冒泡。使用事件代理,只需给外层元素绑定一个处理器,就可以处理在其子元素上触发的所有事件。
优点:减少内存的占用,提高运行处理时间。
四、算法和流程控制
1、如何提高循环的性能?
(1)减少循环的工作量;倒序循环数组是通用的性能优化方法。

for(var i=0;i<arr.length;i++){
	//代码运行逻辑
}
for(var j=0,len=arr.length;j<len;j++){
	//代码运行逻辑
}
for(var i=arr.length;i--;){
	//代码运行逻辑
}

(2)减少循环的次数
2、如何提高条件语句的性能?
(1)大多数的情况下,switch语句比if-else语句运行的要快,但只有当条件数量很大时才快的明显。
(2)if-else中的条件语句应该总是按照从大概率到小概率顺序排序。
(3)单个键和单个值之间存在逻辑映射时,查找表相对条件语句可提高性能。
3、递归模式所引发的调用栈限制如何解决?
调用栈限制就是浏览器会报出错的信息即栈溢出了。当栈溢出的时候,首先检查是否有递归模式;递归模式存在2种情况:
(1)函数调用自身

function recurse(){
	recurse()
}
recurse();

(2)隐伏模式

function frist(){
	second()
}
function second(){
	frist()
}
frist();

将递归算法转换迭代实现是避免栈溢出的方法之一。
利用缓存已经计算过的信息减少计算的工作量也可避免栈溢出。
五、快速响应用户界面
1、什么是浏览器UI线程?
执行JavaScript代码和更新用户界面的进程被称为浏览器UI线程,它相当于一个队列系统,任务会被保存在队列中,一次只能执行一个任务。这意味着在执行js代码时,用户界面不能响应输入。所以提高js执行时间对提高用户体验是很重要的。
2、如何提高js代码执行时间?
使用定时器;定时器与UI线程的交互方式有助于把运行耗时较长的脚本拆分为较短的片段。
(1)定时器分解任务;处理过程可以异步,且可不按顺序处理。

function processArray(items,process,callback){
     var todo=items.concat();
      setTimeout(function(){
          process(todo.shift());
          if(todo.length>0){
              setTimeout(arguments.callee,25);
          }else{
              callback();
          }
      },25)
  }
 processArray([123,3,4,22,44,33],function(value){
      console.log(value)
  },function(){
      console.log('打印完成')
  })
function multistep(steps,args,callback){
         var tasks=steps.concat();
         setTimeout(function(){
             var task=tasks.shift();
             task.apply(null,args||[]);
             if(task.length>0){
                 setTimeout(arguments.callee,25)
             }else{
                 callback()
             }
         },25)
     }

(2)使用Web Workers API;能使代码运行且不占用浏览器的UI线程时间。适用于处理纯数据或者与浏览器UI无关的长时间运行的脚本。
Web Workers :http://www.ruanyifeng.com/blog/2018/07/web-worker.html
六、Ajax
1、请求数据的方式?
(1)XMLHttpRequest;它允许异步发送和接收数据。
优点:跨浏览器兼容性强
缺点:不能跨域请求
(2)动态脚本注入;可使用JavaScript创建一个新的脚本标签,并设置它的src属性为不同域的URL。
优点:可以跨越
缺点:无法直接控制服务器
(3)Multipart XHR;允许客户端只用一个请求就可以从服务端向客户端传送多个资源。
优点:减少HTTP的请求,提高其性能
缺点:获得的资源不被浏览器缓存
2、Ajax的性能优化?
(1)缓存数据
(2)减少请求数量

这本书还是值得认真阅读的,受益非穷。

猜你喜欢

转载自blog.csdn.net/Miss_hhl/article/details/104381828