js进阶学习笔记(一) -- js部分原理

之前看了一些关于作用域的文章和书,可是都渐渐淡忘了,这里我在重新复习作用域之前,先去了解一下js引擎编译的大致过程,来帮助我加深对js的理解.

渲染引擎

浏览器的核心是两部分:渲染引擎和javascript解释器(引擎);不同的浏览器有不同的渲染引擎,他的主要作用是生成网页,通常分成四个阶段,因为图片看起来更加直观,所以把内容放在图片里.


js进阶学习笔记(一) -- js部分原理_第1张图片
渲染引擎工作.jpg

javascript虚拟机(引擎)

  • js是解释型语言,有一定的优缺点:
js进阶学习笔记(一) -- js部分原理_第2张图片
js引擎.jpg
  • 早期,浏览器内部对js的处理过程:
js进阶学习笔记(一) -- js部分原理_第3张图片
浏览器对JS的处理过程.png
  • 即时编译

为了提高运行速度,现代浏览器改为采用'即时编译'(just in time compiler,缩写JIT),即字节码只在运行时编译,用到哪行,就编译哪一行,并且把编译结果缓存,通常,一个程序被经常用到的,只是其中一小部分的代码,有了缓存的编译结果,整个程序的运行速度就会显著提升.

有的浏览器索性省略了字节码的翻译步骤,直接编译成机器码,比如CHROME浏览器的v8引擎.
  • 字节码不能直接运行,而是运行在一个虚拟机之上,一般也把虚拟机称为'javascript引擎'.因为js运行时未必有字节码,所以js虚拟机并不完全基于字节码,而是部分基于源码,即只要有可能,就通过JIT编译器直接把源码编译成机器码运行,省略字节码步骤,这一点与其他采用虚拟机的语言不尽相同,这样做的目的,是为了尽可能地优化代码,提高性能.

script标签的工作原理

正常的网页加载流程:

script.jpg
  • 为了避免发生阻塞效应,较好的做法是将script标签都放在底部,而不是头部.这样即使遇到脚本失去响应,网页主体的渲染也已经完成了,用户至少可以看到内容,而不是面对一张空白的页面.

  • 如果某些脚本代码非常重要,一定放在页面头部的话,最好将代码嵌入页面,而不是链接外部脚本文件,这样能缩短加载时间

  • 将脚本放在网页尾部加载还有一个好处,在DOM结构生成之前就调用DOM,js会报错,如果脚本都在网页尾部加载,就不存在这个问题,因为这时DOM肯定已经生成了.

defer属性

为了解决脚本文件下载阻塞网页渲染的问题,一个方法是加入defer属性

  • defer属性的作用是,告诉浏览器,等到DOM加载完成后,再执行指定脚本
defer.jpg
  • 有了defer属性,浏览器下载脚本文件的时候,不会阻塞页面渲染.下载的脚本文件在DOMContentLoaded事件触发前执行(即刚刚读取完标签),而且可以保证执行顺序就是他们在页面上出现的顺序
  • 对于内置而不是链接外部脚本的script标签,以及动态生成的script标签,defer属性不起作用

async

  • 解决'阻塞效应'的另一个方法是加入async属性
saync.jpg
  • async属性可以保证脚本下载的同时,浏览器继续渲染,
  • 需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序,哪个脚本先下载结束,就先执行那个.使用async属性的脚本文件中,不应该使用document.write方法

重流和重绘(此部分为转载)

  • 渲染树转换为网页布局,成为'布局流';布局显示到页面的这个过程,称为'绘制' 他们都具有阻塞效应,并且会耗费很多时间和计算资源

  • 页面生成后,脚本操作和样式表操作,都会触发重流和重绘,用户的互动,比如设置了鼠标悬停效果,页面滚动,在输入框中输入文本,改变窗口大小.

  • 重流和重绘并不一定一起发生,重流必然导致重绘,重绘不一定需要重流.比如改变元素的颜色,只会导致重绘,而不会导致重流.改变元素的布局,则会导致重流和重绘.

  • 大多情况下,浏览器会智能判断,将重流和重绘只限制到相关的子树上面,最小化所耗费的代价,而不会全局生成网页

  • 作为开发者,应该尽量设法降低重绘的次数和成本.比如,尽量不要变动高层的DOM元素,而以底层DOM元素的变动代替,再比如,重绘table布局和flex布局,开销都比较大.

    var foo = document.getElementById(‘foobar’);
    
    foo.style.color = ‘blue’;
    foo.style.marginTop = ‘30px’;
    

    上面的代码只会导致一次重绘,因为浏览器会累积DOM 变动,然后一次性执行.
    下面的代码则会导致两次重绘:

    var foo = document.getElementById(‘foobar’);
    
    foo.style.color = ‘blue’;
    var margin = parseInt(foo.style.marginTop);
    foo.style.marginTop = (margin + 10) + ‘px’;
    

    优化技巧:

    • 读取DOM或者写入DOM,尽量写在一起,不要混杂;
    • 缓存DOM信息
    • 不要一项一项的改变样式,而是使用CSS class一次性改变样式
    • 使用document fragment操作DOM
    • 动画时使用absolute定位或fixed定位,这样可以减少对其他元素的影响
    • 只在必要时才显示元素
    • 使用window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流
    • 使用虚拟DOM(virtual DOM)库
    // 重绘代价高
    function doubleHeight(element) {
      var currentHeight = element.clientHeight;
      element.style.height = (currentHeight * 2) + ‘px’;
    }
    
    all_my_elements.forEach(doubleHeight);
    
    // 重绘代价低
    function doubleHeight(element) {
      var currentHeight = element.clientHeight;
    
      window.requestAnimationFrame(function () {
        element.style.height = (currentHeight * 2) + ‘px’;
      });
    }
    
    all_my_elements.forEach(doubleHeight);
    

脚本的动态嵌入(此部分为转载)


['1.js', '2.js'].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  document.head.appendChild(script);
});

这种方法的好处是,动态生成script标签不会阻塞页面渲染,也就不会造成浏览器假死,但是问题在于,这种方法无法保证脚本的执行顺序,哪个脚本文件先下载完成,就先执行哪个,

如果想避免这个问题,可以设置async属性为false

['1.js', '2.js'].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

在这段代码之后加载的脚本,要等待2.js执行完成后再执行

(function() {
  var script,
  scripts = document.getElementsByTagName('script')[0];
  function load(url) {
    script = document.createElement('script');
    script.async = true;
    script.src = url;
    scripts.parentNode.insertBefore(script, scripts);
  }
  load('//apis.google.com/js/plusone.js');
  load('//platform.twitter.com/widgets.js');
  load('//s.thirdpartywidget.com/widget.js');
}());

此外,动态嵌入还有一个地方需要注意,动态嵌入必须等到CSS文件加载完成后,才会去下载外部脚本文件,静态加载就不存在这个问题,script标签指定的外部脚本文件,都是与css文件同时并发下载的.

单线程模型

单线程.jpg
  • js采用的是单线程模型
    • 一次只能运行一个任务,其他任务需要等待前一个任务完成才能工作.
    • 为什么不用多线程呢?
      • 原因是不想浏览器变得复杂,因为多线程需要共享资源,且有可能修改彼此的运行结果
  • h5允许多线程,但是子线程完全受主线程控制,且不得操作DOM,所以这个新标准并没
    • 有改变js单线程的本质.
    • 存在的问题.
      容易造成浏览器假死状态;
单线程问题.jpg
  • js设计者意识到,CPU完全可以不管IO设备,挂起处于等待中的任务,先运行排在后面的任务,等到IO设备返回了结果,再回过头,把挂起的任务继续执行下去,这种机制就是js内部采用的Event Loop

Event Loop

  • Event Loop,指的是一种内部循环,用来排列和处理事件,以及执行函数.是一种程序结果,用于等待和发送信息和事件.
  • 所有任务可以分成两种,一种是同步任务,一种是异步任务,
    • 同步任务,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,

    • 异步任务,不进入主线程,而进入任务队列的任务.只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行

  • 下图上面浅蓝色底为同步任务,图下浅红色底为异步任务。

同步异步.jpg
  • 同步模式和异步模式
模式.jpg

异步模式主线程可以运行更多的任务,提高了效率

你可能感兴趣的:(js进阶学习笔记(一) -- js部分原理)