-------------------------------------------------------------------------------------------------------

  又有好长时间没有写博客了,今天想起来之前的那篇博客还没有写完,然后就开始接着写,本来想把《高性能JavaScript》这本书的知识都罗列进来的,但是......太多了,哎,还是慢慢来,于是就打算分开来写。

  本人JavaScript水平并不是特别高,也只是把自己阅读《高性能JavaScript》的部分想法记录一下,难免会有错误哈。如果对JavaScript的性能优化有兴趣的话,不妨亲自读一读。这本书虽然已经出版很多年了,不过里面的部分思想能够有助于我们理解JavaScript的运行机制以及瓶颈所在。

-------------------------------------------------------------------------------------------------------

加载和执行

  1. 脚本阻塞

    由于javascript语言是单线程的,决定了javascript语言是单线程的,因此在浏览器上UI的刷新与javascript代码的逻辑执行就无法同步执行。

    浏览器中遇到界面上的<script> 标签,浏览器无法判断script标签中的代码是否会更行dom节点(若需要更新dom则会引起界面的二次重绘)。因此在浏览器中,遇script标签的时候,将会暂停dom的绘制和渲染以及界面的交互响应,直到javascript被加载(包括外链下载的过程),执行。

    脚本的合并能够减少系统发送http请求的次数,能够减少建立http连接的次数,从而减少阻塞的时间。

  2. 实现无阻塞加载的方法(nonblocking script)

    • 使用script标签的defer和async属性,显式通知浏览器引入的script标签不会改变dom结构,浏览器在加载的时候将会异步加载

      • defer与async加载的比较:两者都能够实现脚本的异步加载,即下载的过程中不会阻塞页面的渲染。区别在于执行的时机,带有async属性的script标签在下载完成后将会自动执行,而然带有defer属性的script标签则要等到页面加载完成后才执行。

      • defer属性只有在script标签存在src属性时才会生效,即仅对外联脚本有效

    • 动态脚本加载,通过JavaScript代码生成一个页面script标签,并为其指定src,文件的下载将不会阻塞页面

       let script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = `${src}`;
      document.getElementsByTagName('head')[0].appendChild(script);
      • 使用动态方式加载脚本时,当js文件下载完成后将会自动执行,但如果有多个脚本文件注入时,由于js文件的下载速度不同,可能导致文件执行的顺序错乱,尤其当后一个脚本文件依赖于上一个脚本文件的执行时将会出错。

      // 解决方法:通过监听script标签的加载状态并触发回调,在回调函数中加载依赖的javascript文件

      // 加载脚本的方法
      var loadScript(url, callback) {
      var script = document.createElement('script');
      script.src = url;
      // 在非IE浏览器中(主流浏览器),script标签加载完成时会触发onload事件,而IE浏览器中script的加载状态通过readyState实现,readyState有以下 值:'uninitialized'(初始状态),'loading'(下载中),'loaded'(下载完成),'interactive'(数据下载完成但尚不可用),'complete'(所有数据准备就绪),在IE中需要监听readyState的状态改变,以便在合适的时机触发回调
      if(script.readyState) { // IE旧版本兼容
      script.onreadyStatechange = function(){
      if(script.readyState === 'loaded' || readyState === 'complete') {
      script.onreadyState = null;
      callback();
      }
      }
      } else {
      script.onload = function () {
      callback()
      }
      }
      }
      // callback中可以加载下一个顺序执行的javascript文件

      注意:当有多个文件需要顺序执行时,将会有多层回调函数的嵌套,容易形成回调地狱

      loadScript('file1.js', function(){
      loadScript('file2.js', function(){
      loadScript('file3.js', function () {
      loadScript('files.js');
      })
      })
      })
      // 加载顺序: file1.js --> file2/js --> file3.js --> file4.js
      // 当多文件嵌套时,尽可能将多个文件按照加载顺序合并成一个文件
    • XMLHttpRequest脚本注入

      • 通过XHR对象异步下载javascript文件

         var xhr = new XMLHttpRequest();
        xhr.open('get', `${url}`);
        shr.onreadystatechange = function () {
        if(xhr.readyState===4) {
        if(xhr.state>=200 && xhr.state < 300 || xhr.state == 304) {
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.text = xhr.responseText;
        document.getElementsByTagName('head')[0].appendChild(script);
        }
        }
        }
        xhr.send(null); 

        注意:该方法不能够进行跨域操作

数据存取

  1. 明确javascript中有四种数据存取位置

    • 字面量 --> 相当于常量吧,例如 var a = 100,其中100就是字面量,javascript中的字面量包括:字符串,数字,布尔值,对象字面量,数组字面量,函数,正则字面量以及null,undefined,他们只代表字面量本身的值而不具备特定的存储位置

    • 本地变量,使用var,let等关键字声明的变量,将字面量存储在特定的变量上,如var a = 100,其中a就是本地变量

    • 数组元素,存储在javascript数组中的成员,通过下标获取。注意:此处的数组元素与数组字面量是不同的概念,如[1, 2, 3, 4],整体表示的是是数组字面量,而不涉及到数组内部元素的存取;[1,2,3,4] [0]则涉及到数组内部元素的访问

    • 对象成员,对象与对象字面量的区别与数组类似


    通常情况下,访问字面量或者局部变量的性能消耗相对较小,而对于数组元素和对象成员的访问代价相对较大,如果对于运行速度有较高的要求,那就尽可能使用字面量和局部变量,减少使用数组和对象,当然当下主流浏览器对于变量的访问进行了优化,对数组和对象的访问速度的差异已经没有那么明显了。

  2. 函数作用域链对变量访问的影响

    • javascript中是通过链的方式来保存作用域的,当一个函数被创建时,该函数的执行期上下文将会被添加到作用域链的顶端。

     const name = 'zhang';
    let test = function () {
    let a = 100;
    console.log(name);
    }
    test ();
    // test执行时需要访问变量name,而在自身的函数作用域中不存在变量name,因此会向上层作用域进行回溯。当然作用域链的层数不一定只有两层,当多层作用域连接时,访问变量会一次沿着作用域链向上搜索。
    // 当然也有可能访问完所有的作用域,依然找不到变量,此时就会抛出一个ReferenceError,也就是说,当你在函数中访问一个不存在的变量时,此时将会沿着作用域链向上搜索,直至顶层作用域,效率很低。
    • 由于全局变量时存放在全局作用域中的,位于作用域链的最底部,因此对于全局变量的访问将会很大程度上影响性能 ,在javascript中考虑性能优化时需要尽量避免访问全局变量,当需要多次访问全局变量时,可以将其保存到局部变量之中,以加快访问的速度

  3.  let visitDom () {
    const dom1 = document.getElementById('test1');
    const dom2 = document.getElementById('test2');
    const dom3 = document.getElementById('test3');
    const dom4 = document.getElementById('test4');
    .......
    }
    // 其中,document就是一个全局变量,当访问document时,函数将需要沿着作用域链层层回溯,如果函数所在的层次更深一些,那么访问的效率就更低
    let visitDom2 () {
    const doc = docuemnt;
    const dom1 = doc.getElementById('test1');
    const dom2 = doc.getElementById('test2');
    const dom3 = doc.getElementById('test3');
    const dom4 = doc.getElementById('test4');
    }
    // 在上述方法中,使用一个局部变量将需要频繁访问的变量document保存起来,此时只需要沿着作用域链搜索一次,其余的操作只要访问当前作用域即可得到
    // 通过以局部变量保存全局变量的方式,减小搜索的深度。
    改变作用域链 with()语句 const visitDom () {
    const a = 100,
    b = 200,
    c = 300;
    with (document) {
    // with语句强制性将document对象添加到函数的作用域链的顶部,即在当前with函数域中document是顶级作用域
    const dom1 = getElementById('test1');
    const dom2 = getElementById('test2');
    const dom3 = getElementById('test3');
    const dom4 = getElementById('test4');
    console.log(a);
    console.log(b);
    console.log(c);
    }
    // with()语句执行结束时,document对象将会从作用域链顶部移除
    }
    // 看似访问效率提高了,可是在[[with()函数作用域]]中,对于局部变量的访问效率将会降低
    // 此处个人的理解可能存在偏差,特此更新,为记录自己的错误理解,因此采用更新的方式纠正
    // 在with语句中访问a,b,c时,若没有with语句,就是访问局部变量;可是有了with语句,由于当前的作用域被document代替,因此,需要向上层作用域进行搜索,此时是的对于局部变量的访问成本增加。
    // 当然,在上面这个案例中,完全可以避开这种缺陷,即将console.log语句移动到with之外,就可以避免局部变量的访问,但有些时候是无法将其分离的,比如说在getelementById()函数中使用局部变量,此时就无法分离开来。

      

    • with()语句可能会增加对局部变量的访问成本,因此需要谨慎使用,而且在ES5严格模式中已不推荐使用with()语句改变作用域链了。

      ****************************************************************************************

      ①今天看了《JavaScript高级程序设计》,里面有一节延长作用域链,看了里面的代码,运行了一下,结果是能跑通的,于是乎,就感觉自己之前的理解有误

      with()语句看起来像一个函数,但其本质上与函数作用域是存在区别的,其本质上是一条语句

      function buildUrl () {
      var qs = '?name=zhang';
      with (location) {
      var url = href + qs;
      }
      return url; // 如果with()是一个函数作用域的话,此处访问url将会报错,但实际上并不会,因此,with()语句是不会识别为作用域的
      }
      document.write(buildUrl());

      更新时间:2019-04-15  10:52:52

  1.   ****************************************************************************************

    1. catch()语句

      catch语句中需要接受一个异常参数,并对异常进行处理,但cath语句会将异常对象推入到作用域链顶端,就如同with一样,会造成局部变量访问的性能开销

     try {
    do something ...
    } catch (ex) {
    // 此时ex对象作为catch函数作用域链中的顶层作用域
    handle exception ...
    }
    // 不同于with()语句,有些时候catch语句的作用是无法被替代的,因而很难避免使用catch语句,我们需要获取ex异常对象,进行一些异常处理,那么方法来了,可以声明一个专门的异常处理函数,将ex对象作为参数传入
    try {
    do something ...
    } catch (ex) {
    handerExp(ex);
    } const handlerExp = function (ex) {
    const a = 100,
    b = 200,
    c = 300;
    handle exception ...
    }
    // 此时访问局部变量就不会存在回溯作用域链的情况了
    • 动态作用域

      with(),catch()以及eval()语句都属于动态作用域,动态作用域只存在于代码执行的过程中,因此无法通过静态分析检测

       function excute (code) {
      eval (code);
      function subroutine () {
      return window;
      }
      var w = subroutine;
      console.log(w);
      }
      // 此时w的值其实是无法静态分析的,只有在code值确定时方能确定
      excute('var window = 100');
      // w --> 100
      excute('var a = 100');
      // w --> window(全局对象)

      案例引自《高性能JavaScript》 作者:[美]Nicholas C.Zakas; 丁琛译

      使用eval()会使得浏览器自身的静态分析优化失效

      (部分浏览器自身实现了通过代码分析确定哪些变量会被访问,从而避开搜索作用域链,进而达到优化变量搜索的功能)

      因而,一般尽量避免使用动态作用域

  2. 对象成员的访问

    • 对象原型

      javascript是通过原型的方式实现继承

      hasOwnProperty()与in操作符的比较:

      两者都是用来判断某一个key是否存在于对象内部

      不同点在于:hasOwnProperty()方法只会遍历对象本身的属性,而in操作符会遍历对象原型上的属性

      因此,频繁的使用in操作符会降低搜索的效率

    • 原型链

       function Book (title, publisher) {
      this.title = title;
      this.publisher = publisher;
      }

      Book.prototype.sayTitle () {
      alert(this.title);
      }

      const book1 = new Book('High Performance JavaScript', 'Yahoo! Press');
      const book2 = new Book('JavaScript:The Good Parts', 'Yahoo! Press');
      alert(book1 instanceof Book); // true
      alert(book2 instanceof Object); // true

      book1.sayTitle(); // High Performance JavaScript
      alert(book1.toString()); // [object Object]

      当需要访问原型链上某个对象上的变量时,同样需要类似于作用域链那样一层层向上搜索。对于原型链的搜索会带来类似于函数作用域链那样的开销。

    • 对象的嵌套访问

      a.b.c.d --> 对象成员嵌套的层数越多,访问的开销越大

    • 对于一个多次访问但是不发生修改更新操作的对象,可以将其缓存为局部变量,以减小性能的开销

       const a = {
      b: {
      c: {
      d: {
      method () {
      do something ...
      }
      }
      }
      }
      } // 当我们需要频繁使用method方法时,就需要重复沿着嵌套对象一层一层往下找

      const d = a.b.c.d
      d.method();
      d.method();
      // 这是一种折中的方案,此时method方法中的this指向d,同时也避免了不必要的嵌套搜索

----------------------------------------------------------------------------

好了,今天《JS高程》又到了,感觉自己水平还是不太够,有些基础还有待加强哈!!

自勉

-----------------------------------------------------------------------------

2019-04-11    15:34:23

高性能JavaScript(1)的更多相关文章

  1. 《高性能javascript》一书要点和延伸(上)

    前些天收到了HTML5中国送来的<高性能javascript>一书,便打算将其做为假期消遣,顺便也写篇文章记录下书中一些要点. 个人觉得本书很值得中低级别的前端朋友阅读,会有很多意想不到的 ...

  2. 《高性能javascript》 领悟随笔之-------DOM编程篇(二)

    <高性能javascript> 领悟随笔之-------DOM编程篇二 序:在javaSctipt中,ECMASCRIPT规定了它的语法,BOM实现了页面与浏览器的交互,而DOM则承载着整 ...

  3. 《高性能javascript》 领悟随笔之-------DOM编程篇

    <高性能javascript> 领悟随笔之-------DOM编程篇一 序:在javaSctipt中,ECMASCRIPT规定了它的语法,BOM实现了页面与浏览器的交互,而DOM则承载着整 ...

  4. 各种JS模板引擎对比数据(高性能JavaScript模板引擎)

    最近做了JS模板引擎测试,拿各个JS模板引擎在不同浏览器上去运行同一程序,下面是模板引擎测试数据:通过测试artTemplate.juicer与doT引擎模板整体性能要有绝对优势: js模板引擎 Ja ...

  5. 高性能javascript学习笔记系列(6) -ajax

    参考 高性能javascript javascript高级程序设计 ajax基础  ajax技术的核心是XMLHttpRequest对象(XHR),通过XHR我们就可以实现无需刷新页面就能从服务器端读 ...

  6. 高性能javascript学习笔记系列(5) -快速响应的用户界面和编程实践

    参考高性能javascript 理解浏览器UI线程  用于执行javascript和更新用户界面的进程通常被称为浏览器UI线程  UI线程的工作机制可以理解为一个简单的队列系统,队列中的任务按顺序执行 ...

  7. 高性能JavaScript 编程实践

    前言 最近在翻<高性能JavaScript>这本书(2010年版 丁琛译),感觉可能是因为浏览器引擎的改进或是其他原因,书中有些原本能提高性能的代码在最新的浏览器中已经失效.但是有些章节的 ...

  8. 高性能javascript学习笔记系列(4) -算法和流程控制

    参考高性能javascript for in 循环  使用它可以遍历对象的属性名,但是每次的操作都会搜索实例或者原型的属性 导致使用for in 进行遍历会产生更多的开销 书中提到不要使用for in ...

  9. 高性能javascript学习笔记系列(3) -DOM编程

    参考 高性能javascript 文档对象模型(DOM)是独立于语言的,用于操作XML和HTML文档的程序接口API,在浏览器中主要通过DOM提供的API与HTML进行交互,浏览器通常会把DOM和ja ...

  10. 高性能javascript学习笔记系列(2)-数据存取

    参考 高性能javascript Tom大叔深入理解javascript系列 相关概念 1.执行上下文   当控制器转到ecmascript可执行代码的时候,就会进入一个执行上下文,执行上下文是以堆栈 ...

随机推荐

  1. Linux压缩与解压缩文件

    1 将tgz文件解压到指定目录. tar zxvf test.tgz -C 指定目录 比如:将 test.tgz 解压到 /home目录:tar zxvf test.tgz -C /home 2 将指 ...

  2. php获取id

    private static function getClientIp() { if (getenv('HTTP_X_FORWARDED_FOR')) { $tmp = explode(',', ge ...

  3. zzw原创_非root用户下安装nginx

    想自己安装nginx,又不相用到root用户. 非root用户下(本文为用户bdctool)来ngnix安装,要依赖pcre库.zlib库等, 1. 下载依赖包:下载地址 pcre(www.pcre. ...

  4. keras神经网络做简单的回归问题

    咸鱼了半个多月了,要干点正经事了. 最近在帮老师用神经网络做多变量非线性的回归问题,没有什么心得,但是也要写个博文当个日记. 该回归问题是四个输入,一个输出.自己并不清楚这几个变量有什么关系,因为是跟 ...

  5. JSP中客户端跳转与服务器端跳转的区别

    转载自:https://www.cnblogs.com/memewry/archive/2012/08/21/2649988.html 客户端跳转时用HttPservletResopse对象的send ...

  6. AI 帮助涂鸦

    这个小工具挺有意思,可以在涂鸦的同时自动猜测你要画什么,并自动完成. https://quickdraw.withgoogle.com/

  7. javascript中的add(1)(2)(3)(4)是怎么实现的

    javascript中的add(1)(2)(3)(4)是怎么实现的?实现如下: var fn = function(a){ let sum = a; let tempFn = function(b){ ...

  8. virtualenv与virtualenvwrapper的配置

    virtualenv是用来创建python虚拟环境的一个工具(python的Scripts目录下virtualev.exe文件),virtualenvwrapper是用来 便于管理virtualenv ...

  9. java项目 在 linux ubuntu 上的部署相关

    --------------------JDK在linux ubuntu上的安装------------------------------------------------------------ ...

  10. JS中的变量和数据类型

    所谓变量,就是里面存储的数据是可以改变的. 在使用变量之前,我们需要先声明变量.声明变量的关键字有var  let   const 在ES里面声明变量可以使用var,如下: //var 变量名 var ...