每日一道面试题-叠加

173 阅读26分钟

CSS

1、布局都有什么方式,float和position有什么区别?

布局方式

1.静态块级 2.弹性布局(flex) 3.网络布局(grid) 4.自适应布局(根据当前访问设备进行多套样式来适配) 5.响应式布局(通过媒体查询进行适配,rem/em) 6.浮动布局(float) 7.定位布局(position)

float和position有什么区别?

  • float:none left right inherit

特性:

  • 浮动会脱离文档流,并且会随着分辨率和窗口尺寸的变化而变化
  • 浮动后面的元素如果是块级元素,会占据块级元素的文本位置,但会与块级元素背景和边框重叠
  • 多个浮动不会产生重叠现象
  • 会将块级元素和行内元素变为行内块元素
  • position:relative absolute fixed static 特性

  • relative和static不会脱离文档流
  • absolute和fixed会脱离文档流
  • absolute根据relative定位。fixed根据body定位
  • absolute和fixed会触发BFC
  • 定位的优先级高于浮动

HTML + JS + 浏览器

1、简单说下你理解的语义化,怎样来保证你写的符合语义化?HTML5语义化标签了解下?

语义化:

     每个html标签都有自己特定的含义(语义),语义化指的是使用语义恰当的标签,使页面有良好的结构,页面元素有含义,能够让人和搜索引擎都容易理解

如何写符合语义化:

     1)尽可能少用无语义的div、span标签等;使用HTML5中新增的较多强语义化标签,不要使用样式化标签,如font、b...等等,完全可以用css实现样式

     2)强调文本,尽量使用strong标签加强强调,em标签设置斜体

     3)表格书写规范:标题要用caption,表头用head,主题部分用tbody包围,尾部用tfoot包围。包头和一版单元格要区分开,表头用th,单元格用td

     4)表单域要用fieldset标签包起来,并用legend标签说明表单的用途;

     5)每个input标签对应的说明文本都要使用label标签,并且通过id属性和相对应的input关联起来

     6)去掉或者丢失样式的时候

优势:---(结构清晰、提升用户体验、利于SEO、方便其他设备解析、便于团队开发维护

     1)去掉或者丢失样式的时候能够让页面呈现出清晰的结构

     2)用户体验:例如title、alt用于解释名词或者解释图片信息、label标签的作用

     3)有利于SEO:和搜索引擎简历良好沟通,有助于爬虫抓取更多的有效信息;爬虫依赖于标签来确定上下文和各个关键字的权重;

     4)方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备)有意义的方式来渲染网页;

     5)便于团队开发和维护,语义化更具可读性,遵循W3C的标准的团队都遵循这个标准,可以减少差异化

2、谈谈你对重绘和回流的理解?

渲染流水线的流程

1)回流

回流也叫重排。

     触发条件:

            当我们对DOM结构的修改引发DOM几何尺寸变法的时候,会发生回流的过程

            1)一个DOM元素的几何属性变化,常见的几何属性有width、height、padding、margin、left、top、border等等

            2)使DOM节点发生增减或者移动

            3)读写offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作

            4)调用window.getComputedStyle方法

      回流过程

             依照上面的渲染流水线,触发回流的时候,如果 DOM 结构发生改变,则重新渲染 DOM 树,然后将后面的流程(包括主线程之外的任务)全部走一遍。

相当于将解析和合成的过程重新又走了一篇,开销是非常大的。

2)重绘

触发条件:当DOM的修改导致了样式的变化,并且没有影响几何属性的时候,会导致重绘(repaint)

    重绘过程:

            由于没有导致DOM几何属性的变化,因此元素的位置信息不需要更新,从而省去布局的过程

跳过了 生成布局树 和 建树图层 的阶段,直接生成绘制列表,然后继续进行分块、生成位图等后面一系列操作

可以看到,重绘不一定导致回流,但回流一定发生了重绘。

合成

还有一种情况,是直接合成。比如利用 CSS3 的transformopacityfilter这些属性就可以实现合成的效果,也就是大家常说的GPU加速

#GPU加速的原因

在合成的情况下,会直接跳过布局和绘制流程,直接进入非主线程处理的部分,即直接交给合成线程处理。交给它处理有两大好处:

  1. 能够充分发挥GPU的优势。合成线程生成位图的过程中会调用线程池,并在其中使用GPU进行加速生成,而GPU 是擅长处理位图数据的。

  2. 没有占用主线程的资源,即使主线程卡住了,效果依然能够流畅地展示。

节选自“今天大梦要早睡”  链接:www.jianshu.com/p/e8519f9d5…

3、如何加快页面渲染速度,都有哪些方式?

    1)静态资源的优化

         主要是减少静态资源的加载时间,只要包括html、js、css和图片

  • a.减少http请求数:合并js、css、制作雪碧图以及使用http缓存

  • b.减少资源的大小:压缩图片、压缩文件、小兔使用base64编码

  • c. 异步组件和图片懒加载

  • d. CDN加速和缓存(bootCDN):客户端可通过最佳的网络链路加载静态资源,提高访问的速度和成功了。(CMD:通过在网络各处防止节点服务器构成的一层之恶能虚拟网络,可将用户的请求重新导向离用户最近的服务节点上)

     2)接口访问的优化

  • a.http持久连接(Conention:keep-alive)

  • b.后端优化和并请求(比如在进入一个商品详情页的时候后端会提供一个接口获取商品的基本信息,然后当用户点击加入购物车时)

  • c.数据接口缓存到localStorage,减少请求

     3)页面渲染速度的优化

  • a.由于浏览器的js引擎线程和GUI渲染线程是互斥的,所以在执行js的时候会阻塞它的渲染,所以一般会将css放到顶部,优先渲染,js在底部

  • b.减少dom操作

  • c.使用虚拟dom渲染方案,做到最小化操纵真实的dom

  • d.事件代理:利用事件冒泡原理,把函数注册到父级元素上

  • e.减少页面的重绘和回流

4、浏览器缓存机制,协商缓存的内容能了解多少呢?

      协商缓存

       强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag来决定是否使用缓存,这就是协商缓存。 具体来说,这样的缓存tag分为两种:Last-Modified和ETag。这两者各有优劣之分,并不存在谁对谁错有绝对的优势。

       Last-Modified

       最后的修改时间。在浏览器第一次给服务器发送请求时,服务器会在响应头中加上这个字段 浏览器接收到后,如果再次请求,会在请求头重携带If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间 服务器拿到请求头重的If-Modified-Since的字段后,其实会和这个服务器中该资源的最后修改时间对比:如果请求头重的这个值小于最后修改时间,说明是时候更新了,返回新的资源,跟常规http请求响应的流程一样 否则返回304,告诉浏览器直接使用缓存

       ETag

       ETag是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过响应头把这个值给浏览器。浏览器接收到ETag的值,会在下次请求时,将这个值昨晚If-None-Match这个字段的内容,并放到请求头中,然后发送给吴福气 服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行对比;如果两者不一样,说明要更新了,返回新的资源,跟常规的http请求响应的流程一样,否则返回304,告诉浏览器直接用缓存

       两者对比:

           1)在精准度上,ETag由于Last-Modified。由于ETag是按照内容给资源上标识,一次能准确感知资源的变化。而Last-Modified就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况

            编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效

            LastModified能够感知的单位时间是秒,如果文件在1秒内改变了多次,那么这时候的Last-Modified并没有体现出修改了

           2)在性能上,Last-Modified由于ETag,也很简单李杰,Last-Modified仅仅只是一个记录一个时间点,而ETag需要根据文件的具体内容生成哈希值

           另外,如果两种方式都支持的话,服务器优先考虑ETag。

5、说一下时间循环机制(node浏览器)

      1、为什么会有Event Loop

            JS的任务分为两种:  同步和异步,他们的处理方式也各自不同,同步任务是直接放在主线程上排队依次执行,异步任务会放在任务队列中,若有多个异步任务则需要在任务队列中排队等待,任务队列类似于缓存区,任务下一步会被移到调用栈然后主线程执行调用栈的任务

            调用栈: 调用栈是一个栈结构,函数嗲用会形成一个栈帧,帧中包含了当前执行函数的参数和局部变量等上下文信息,函数执行完后,它的执行上下文会从栈中弹出

       JS是单线程的,单线程是指js引擎中解析和执行js代码的现成只有一个(主进程),每次只能做一件事情,而ajax请求中,主线程在等待响应的过程中会去做其他事情,浏览器先在时间注册ajax的回调函数,响应回来后回调函数被添加到任务队列中等待执行,不会造成线程阻塞,所以说js处理ajax请求方式是异步的

       综上所述,检查调用栈是否为空以及将某个任务添加到调用栈中的过程就是event loop,这就是JS实现异步的核心

      2、浏览器中的Event Loop

Micro-Task 与 Macro-Task 浏览器端事件循环中的异步队列有两种:macro(宏任务)队列和micro(微任务)队列 常见的macro-task:setTimeout、setInterval、script(整体代码)、I/O操作、UI渲染等 常见的micro-task:new Promise().then(回调)、MutationObserve等

requestAnimationFrame requestAnimationFrame也属于异步执行的方法,但是该方法既不属于宏任务也不属于微任务,按照MDN定义:

window.requestAnimationFrame()告诉浏览器---希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行 requestAnimationFrame是GUI渲染之前执行,但是在Micro-Task之后,不过requestAnimationFrame不一定会在当前帧必须执行,由浏览器根据当前的策略自行决定在哪一帧执行

  • Event Loop过程

  • 1.检查macro-task是否为空,非空到达2,为空到达3
  • 2.执行macro-task中的一个任务
  • 3.继续检查micro-task队列是否为空,若是空到达4,否则是到达5
  • 4.取出micro-task中的任务执行,执行完成返回到达3
  • 5.执行试图更新

当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。

  • 3. node中的Event Loop

    node中的Event Loop和浏览器中的是完全不相同的东西。node.js采用v8作为js的解析引擎,而I/O处理方面使用了自己设计的libuv,libuv是一件基于事件驱动的跨平台抽象层,封装了不同操作系统一些底层特性,对外提供统一的API,事件循环机制也是这里面的实现

  • 1.v8引擎解析JS脚本

  • 2.解析后的代码,调用node API

  • 3.libuv库负责node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎

  • 4.v8引擎在将结果返回给用户

  六大阶段

其中libuv引擎中的事件循环分为六个阶段,它们会按照顺序反复运行。每当进入一个阶段的时候,都会从对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量达到系统设定的阈值,就会进入下一个阶段

1.timer阶段:这个阶段执行timer(setTimeout、setInterval)的回调,并且是由poll阶段控制的
2.I/O callbacks阶段:处理一些上一轮循环中的少数未执行的I/O回调
3.idle,prepare阶段:仅node内部使用
4.poll阶段:获取新的I/O事件,适当的条件下node将阻塞在这里
5.check阶段:执行setImmediate()的回调
6.close callbacks阶段:执行socket的close事件回调

   poll阶段

poll是一个至关重要的阶段,这一阶段中,系统会做两件事情 1.回到timmer阶段执行回调 2.执行I/O回调,并且在进入该阶段时如果没有设定timer的话,会发生以下两件事情

  • 如果poll队列不为空,会遍历回调队列并同步执行,直到队列为空或者到达系统限制

  • 如果 poll 队列为空时,会有两件事发生

    • 如果有 setImmediate 回调需要执行,poll 阶段会停止并且进入到 check 阶段执行回调
    • 如果没有 setImmediate 回调需要执行,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去 当然设定了 timer 的话且 poll 队列为空,则会判断是否有 timer 超时,如果有的话会回到 timer 阶段执行回调。

   Micro-Task 与 Macro-Task

Node端事件循环中的异步队列也是这两种:macro(宏任务)队列和 micro(微任务)队列。

  • 常见的 macro-task 比如:setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作等
  • 常见的 micro-task 比如: process.nextTick、new Promise().then(回调)等

   setTimeout 和 setImmediate

二者非常相似,区别主要在于调用时机不同。

  • setImmediate 设计在poll阶段完成时执行,即check阶段

  • setTimeout 设计在poll阶段为空闲时,且设定时间到达后执行,但它在timer阶段执行

    setTimeout(function timeout () { console.log('timeout'); },0); setImmediate(function immediate () { console.log('immediate'); });

1.对于以上代码来说,setTimeout 可能执行在前,也可能执行在后。 2.首先 setTimeout(fn, 0) === setTimeout(fn, 1),这是由源码决定的 进入事件循环也是需要成本的,如果在准备时候花费了大于 1ms 的时间,那么在 timer 阶段就会直接执行 setTimeout 回调 3.如果准备时间花费小于 1ms,那么就是 setImmediate 回调先执行了

   process.nextTick

这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行

  • 4. node与浏览器的Event Loop差异

  • Node端,microtask 在事件循环的各个阶段之间执行

  • 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

6、浏览器缓存机制(3)对于开发很重要,缓存位置的内容能了解多少呢?

当强缓存命中或者协商缓存中服务器返回304的时候,我们直接从缓存中获取资源。那么这些资源究竟缓存在什么位置呢?

  • 浏览器中的缓存位置一共有四种:按优先级从高到第排列分别是:
    • Service Worker
    • MEmory Cache
    • Disk Cache
    • Push Cache

Service Worker

Service Worker借鉴了Web Worker的思路,既让js运行在主线程之外,由于它脱离了浏览器的窗体,因此无法直接访问DOM。虽是如此,但它仍然能帮助我们完成很多有用的功能,比如离线缓存、消息推送和网络代理等功能。其中的离线缓存就是Service Worker Cache

Memory Cache和Disk Cache

Memory Cache指的是内存缓存,从效率上讲它是最快的。但是从存活时间来讲又是最短的,当渲染进程结束后,内存缓存也就不存在了。 Disk Cache就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是它的优势在于存储容量和存储时长。

那浏览器如何决定将资源放进内存还是硬盘呢?
比较大的js、css文件会直接被丢进磁盘,反之丢进内存
内存使用率比较高的时候,文件优先进入磁盘

Push Cache

推送缓存,是一种低级的网络功能-使用网络堆栈的任何东西都可以使用它,有用的关键是一致性和可预测性,这是浏览器缓存的最后一道防线,它是http/2中的内容,虽然现在应用的并不广泛,但随着http/2的推广,它的应用越来越广泛。

  • 对浏览器的缓存机制来做个总结
  • 首先强缓存可用,直接使用
  • 否则进入协商缓存,即发送HTTP请求,服务器通过请求头中的If-Modified-Since或者If-None-Match字段检查资源是否更新
  • 若资源更新,返回资源和200状态码
  • 否则返回304,告诉浏览器直接从缓存获取资源

7、说一下栈和堆的区别,垃圾回收时栈和堆的区别?

一、栈和堆的区别

栈:其操作系统自动分配释放,存放函数的参数值和局部变量的值等。其操作方式类似于数据结构中的栈。简单的理解就是当定义一个变量的时候,计算机会在内存中开辟一块存储空间来存放这个变量的值,这块空间叫做栈,然而栈中一般存放的是基本数据类型,栈的特点就是先进后出(或者后进先出)

堆:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。其实在堆中一般存放变量的是一些对象类型

  • 1.存储大小

栈内存的存储大小是固定的,申请时由系统自动分配内存空间,运行的效率比较快,但是因为存储的大小固定,所以容易存储的大小超过存储的大小,导致溢栈。

堆内存的存储的值是大小不定,是由程序员自己申请并指明大小。因为堆内存是new分配的内存,所以运行的效率会比较低

  • 2.存储对象

栈内存存储的是基础数据类型,并且是按值访问的,因为栈是一块连续的内存区域,以后进先出的原则存储调用的,所以是连续存储的

堆内存是向高地址扩展的数据结构,是不连续的内存区域,系统也是用链表来存储空闲的内存地址,所以是不连续的。因为是记录的内存地址,所以获取是通过引用,存储的是对象居多

  • 3.回收

栈的回收是系统控制实现的

堆内存的回收是人为控制的,当程序结束后,系统会自动回收

二、垃圾回收栈和堆的区别

栈内存中的数据只要结束,则直接回收

堆内存中的对象回收标准是否可达,在V8中对象先分配到新生代的From中,如果不可达直接释放,如果可达,就复制到TO中,然后将TO和From互换。当多次复制后依然没有回收,则放入老生代中,进行标记回收。之后将内存碎片进行整合放到一端。

8、HTTP请求特征是什么?

  • 支持客户-服务器模式
  • 简单快速:客户向服务器请求服务时,只需传送方法和路径。请求方法常用的有GET、POST、HEAD。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因此通信速度很快
  • 灵活:HTTP允许传输任意类型的数据对象,正在传输的类型由Content-Type加以标记
  • 无连接:限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间
  • 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要前面信息时应答会很快。

Vue

1、Vuex和localStorage的区别是什么?

1)最重要的区别

Vuex存储在内存;

localstorage以文件的方式存储再本地

localstoreage只能存储字符串类型的数据,存储对象需要JSON的stringify和parse方式进行处理。读取内存比读取硬盘速度要快

2)应用场景

vuex是一个转为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件状态。并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值

localstorage是本地存储,是将数据存储到浏览器的方法,一般是再跨域面传递数据时使用的

vuex能做到数据的响应式,localstorage不能做到

3)永久性

刷新页面时vuex存储的值会丢失。localstorage不会丢失

很多人觉得用localstorage可以代替vuex,对于不变的数据确实可以,但是当两个组件公用一个数据源(对象或数组)时,如果其中一个组件改变了该数据源,希望另一个组件响应该变化时,localstorage无法做到,原因再区别1那里

Webpack

1、webpack的构建流程是什么?

webpack的运行流程是一个串行的过程,从启动到结束会依次执行以下流程

  • 初始化参数:从配置文件和Shell语句中读取与合并参数,得出最终的参数
  • 开始编译:用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译;
  • 确定入口:根据配置中的entry找出所有的入口文件;
  • 编译模块:从入口文件出发,调用所有配置的Loader对模块进行翻译,再找出该模块依赖的模块,在递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
  • 完成模块编译:在经过第四步使用loader翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系
  • 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
  • 输出完成:再确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

2、Import和CommonJs在webpack打包过程中有什么不同

       1)es6模块调用commonjs模块

            可以直接使用commonjs模块,commonjs模块将不会被webpack的模块系统编译而是原样输出,并且commonjs模块没有default属性

       2)es6模块调用es6模块

            被调用的es6模块不会添加{esModule:true},只有调用者才会添加{esModule:true},并且可以进行tree-shaking操作,如果被调用的es6模块只是import进来,但是并没有被用到,那么被调用的es6模块将会被标记为/* unused harmony default export */,在压缩时此模块将被删除(如果被调用的es6模块里有立即执行语句,那么这些语句将会被保留)

      3)commonjs模块引用es6模块

            es6模块编译后会添加{__esModule:true}。如果被调用的es6模块中恰好有export default语句,那么编译后的es6模块将会添加default属性

      4)commonjs模块调用commonjs模块

            commonjs模块会原样输出

3、dev-server是怎么跑起来的?

  • 安装webpack-dev-server的npm包
  • 在webpack.config.js进行配置

   devServer中常用的配置对象属性如下:

  • 1.contentBase:"./" 本地服务器在哪个目录搭建页面,一般在当前目录即可
  • 2.historyApiFallback:true 搭建spa应用时会用到。它使用的时HTML5 History Api,任意的跳转或404响应可以指向index.html页面
  • 3.inline:true 用来支持dev-server自动刷新的配置,webpack有两种模式支持自动刷新,一种是iframe模式,一种是inline模式;使用iframe模式是不需要在devServer进行配置的。只需使用特定的URL格式访问即可;不过我们一般还是常用inline模式,在devServer中对inline设置为true后,当启动webpack-dev-server时仍需要配置inline才能生效
  • 4.hot:true 启动webpack热模块替换特性
  • 5.port 端口号(默认8080)

怎么跑起来的

  • 1.启动HTTP服务
  • 2.webpack构建时输出Bundle到内存,HTTP服务从内存中读取Bundle文件
  • 3.监听文件变化,重新执行第二个步骤

dev-server 实际上是一个HTTP服务器,所以还可以做静态资源的访问和API的Proxy代码

1.静态资源访问

{
    devServer:{
        contentBase:'public'
    }
}

2.Proxy代理

{
    devServer:{
        proxy:{
            '/api':{
                target:'http://api.target.com'
            }
        }
    }
}

4、如何实现 webpack 持久化缓存?

     持久化缓存

  • 服务端设置HTTP缓存头(Cache-Control等)
  • 打包依赖(dependencies)和运行时(runtime)到不同chunk(在webpack中,编译后的单独文件称为chunk),即作splitChunk,因为它们几乎是不变的
  • 延迟加载:使用import()方式,可以动态加载的文件分到独立的chunk,以得到自己的chunkhash
  • 保证hash值稳定:编译过程和文件内容的更改尽量不影响其他文件hash的计算。对于低版本webpack生成的增量数字ID不稳定问题,可用HashedModuleldsPlugin基于文件路径生成解决

5、webpack热更新原理

基础概念

1.webpack compiler:将js编译成Bundle 2.Bundle Server:提供文件在浏览器的访问,实际上就是一个服务器 3.HMR Server:将热更新的文件输出给HMR Runtime 4.HMR Runtime:会注入到bundle.js中,与HRM Server通过webSocket链接,接收文件变化,并更新对应文件 5.bundle.js:构建输出的文件

原理

1.启动阶段

  • webpack Compiler将对应文件打包成bundle.js(包含注入的HMR Server),发送给Bundler Server
  • 浏览器即可访问服务器的方式去获取bundle.js

2.更新阶段(文件发生变化)

  • webpack compiler重新编译,发送给HMR Server
  • HMR Server可以知道有哪些资源、哪些模块发生了变化,通知HRM Runtime
  • HRM Runtime更新代码

HMR原理详解

使用webpack-dev-server去启动本地服务,内部实现只要使用了webpack、express、websocket

  • 使用express启动本地服务,当浏览器访问资源时对此响应
  • 服务端和客户端使用websocket实现长连接
  • webpack监听源文件的变化,即当开发者保存文件时触发webpack的重新编译
    • 每次编译都会生成hash值,已改动模块的json文件、已改动模块代码的js文件
    • 编译完成后通过socket向客户端推送当前编译的hash戳
  • 客户端的websocket监听到有文件改动推送过来的hash戳,会和上一次对比
    • 一直就走缓存
    • 不一致就通过ajax和jsonp向服务端获取最新资源
  • 使用内存文件系统去替换有修改的内容实现局部刷新

1.server端

  • 启动webpack-dev-server服务器
  • 创建webpack实例
  • 创建server服务器
  • 添加webpack的done事件回调
  • 编译完成向客户端发送消息
  • 创建express应用app
  • 设置文件系统为内存文件系统
  • 添加webpack-dev-middleware中间件
  • 中间件负责返回生成的文件
  • 启动webpack编译
  • 创建http服务器并启动服务
  • 使用sockjs在浏览器端和服务端之间建立一个websocket长连接
  • 创建socket服务器

2.client端

  • webpack-dev-server/client端会监听到此hash消息
  • 客户端收到ok消息后会执行reloadApp方法进行更新
  • 在reloadApp中会进行判断,是否支持热更新,如果支持的话发生webpackHotUpdate事件,如果不支持就直接刷新浏览器
  • 在webpack/hot/dev-server.js会监听webpackHotUpdate事件
  • 在check方法里会调用module.hot.check方法
  • HotModuleReplacement.runtime请求Manifest
  • 通过调用JsonpMainTemplate.runtime的hotDownloadManifest方法
  • 调用JsonpMainTemplate.runtime的hotDownloadUpdateChunk方法通过JSONP请求获取最新的模块代码
  • 补丁js取回来或会调用JsonpMainTemplate.runtime.js的webpackHotUpdate方法
  • 然后会调用HotModuleReplacement.runtime.js的hotAddUpdateChunk方法动态更新模块代码
  • 然后调用hotApply方法进行热更新