【千人虐】面试总结---1

251 阅读10分钟

今天去公司另外一个部门内部面试,发现写业务代码太久,很多东西再次提起就感觉很陌生,抽点时间做下总结

几个回答的不太好的问题

  • 1.http缓存机制
  • 2.webpack打包优化
  • 3.浏览器渲染机制
  • 4.AMD、CMD、及commonJs

1.http的缓存机制

浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识;浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。
上述就是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存 。

  • 1.1 强制缓存
    强制缓存的规则:当浏览器向服务器发送请求的时候,服务器会将缓存规则放入HTTP响应的报文的HTTP头中和请求结果一起返回给浏览器,控制强制缓存的字段分别是Expires和Cache-Control,其中Cache-Conctrol的优先级比Expires高。
    • 1.1.1 Expires
      Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求的结果缓存的到期时间,即再次发送请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。Expires是HTTP/1.0的字段,但是现在浏览器的默认使用的是HTTP/1.1,到了HTTP/1.1,Expires已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,如果客户端与服务端的时间由于某些原因(时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存直接失效,那么强制缓存存在的意义就毫无意义。
      1.1.2 Cache-Control
      在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:
      (1)public:所有内容都将被缓存(客户端和代理服务器都可缓存)
      (2)private:所有内容只有客户端可以缓存,Cache-Control的默认取值
      (3)no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定
      (4)no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
      (5)max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效
      HTTP响应报文中expires的时间值,是一个绝对值;HTTP响应报文中Cache-Control为max-age=xxx,是相对值。由于Cache-Control的优先级比expires,那 么直接根据Cache-Control的值进行缓存,意思就是说在xxx秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于expires是更好的选择,所以同时存在时,只有Cache-Control生效。
  • 1.2 协商缓存
    控制协商缓存的字段分别有:Last-Modified / If-Modified-Since和Etag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。
    • 1.2.1 Last-Modified / If-Modified-Since
      (1)Last-Modified是服务器响应请求时,返回该资源文件在服务器最后被修改的时间;
      (2)If-Modified-Since则是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件。
    • 1.2.2 Etag / If-None-Match
      (1)Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成);
      (2)If-None-Match是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200;
      另外Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。

2.webpack打包优化

这个暂时没有想好具体的总结

3.浏览器渲染机制

  • 1.关键渲染路径
    关键渲染路径是指浏览器从最初接收请求来的HTML、CSS、javascript等资源,然后解析、构建树、渲染布局、绘制,最后呈现给客户能看到的界面这整个过程 浏览器请求、加载、渲染一个页面的耗时任务主要有:
    ①DNS 解析查询
    ②TCP/IP 连接
    ③HTTP 请求和响应
    ④服务器响应
    ⑤客户端渲染
    浏览器渲染,主要包括以下几步: ①解析HTML生成DOM树
    ②解析CSS生成CSSOM规则树
    ③将DOM树与CSSOM规则树合并在一起生成渲染树
    ④遍历渲染树开始布局,计算每个节点的位置大小信息
    ⑤将渲染树每个节点绘制到屏幕
    注:上面步骤不一定是一次性顺序完成。如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。 在文字写书写不同数量的#可以完成不同的标题,如下:
    • 1.1 构建DOM树
      当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点,HTML Parser将HTML标记解析成DOM Tree 但DOM树的生成过程中可能会被CSS和JS的加载执行阻塞
    • 1.2 构建CSSOM规则树
      CSS Parse将每个CSS文件都被解析成一个StyleSheet对象,每个对象都包含Style Rules,Style Rules也叫CSSOM(CSS Object Model)。
    • 1.3 阻塞渲染
      当HTML解析器(HTML Parser)遇到script标记阻塞时,解析器虽然会停止构建 DOM,但仍会执行JavaScript脚本的资源直至脚本执行完再开始构建DOM;如果JavaScript脚本还操作了CSS,而正好这个CSSOM还没有下载和构建,这时就存在阻塞的资源,浏览器会延迟JavaScript脚本执行,直至完成其CSSOM的下载和构建再执行。遵循下面两个原则:
      (1)CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源
      (2)JavaScript 置后:我们通常把JS代码放到页面底部,且JavaScript 应尽量少影响 DOM 的构建
      (3)使用defer或者async(这2个属性只对设置了scr属性的外部脚本有效,对内联脚本(没有设置src属性的script)无效)
         <script src="vue.js" defer></script> 
         //defer的script将会并发异步的去下载对应的外部脚本文件(可能从本地缓存中获取)
         //vue.js下载完成后不会马上执行,要等到HTML解析完成后,按脚本出现的顺序依次执行,这个过程中多个脚本之间是顺序执行
         //(即延迟执行引入的js脚本)
      
         <script src="vue.js" async></script> 
         //async的script将会并发异步的去下载对应的外部脚本文件(可能从本地缓存中获取),
         //vue.js下载完成后,渲染引擎中断渲染,执行这个脚本以后,再继续渲染,这个过程中多个脚本按下载完成的时间的先后顺序依次执行
      

4. AMD、CMD、及commonJs 模块规范

他们都是用于在模块化定义中使用的,AMD、CMD、CommonJs是ES5中提供的模块化编程的方案,import/export是ES6中定义新增的

    1. CommonJS
      CommonJs 是服务器端模块的规范,Node.js采用了这个规范。
      根据CommonJS规范,一个单独的文件就是一个模块。加载模块使用require方法,该方法读取一个文件并执行,最后返回文件内部的exports对象。
        var math = require('math');    
        math.add(2, 3);    
    // 特别注意此时require是同步的;    
    
    另外模块的导出
      exports.a = () => {}
      exports.b = () => {}
    
      //   或者
      const a = ()=> {};
      const b => {};
      module.exports = {
        a, b
      }
      exports 是module.exports的 一个引用。
    
  • 2 AMD -异步模块定义
    它是一个在浏览器端模块化开发的规范,AMD对应的就是很有名的RequireJS, requireJS主要解决两个问题:
      1. 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器;
      1. js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长;
        // 定义模块 myModule.js
        define(['dependency'], function(){
            var name = 'Byron';
            function printName(){
                console.log(name);
            }
    
            return {
                printName: printName
            };
        });
    
        // 加载模块
        require(['myModule'], function (my){
          my.printName();
        });
    
    requireJS定义了一个函数 define,它是全局变量,用来定义模块 define(id?, dependencies?, factory);
    • id:可选参数,用来定义模块的标识,如果没有提供该参数,脚本文件名(去掉拓展名)
    • dependencies:是一个当前模块依赖的模块名称数组 factory:工厂方法,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值
      在页面上使用require函数加载模块 require([dependencies], function(){});require()函数接受两个参数*
      第一个参数是一个数组,表示所依赖的模块
      第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块 require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。
    1. CMD
      即通用模块定义,对应SeaJS,是阿里团队首先提出的概念和设计。跟requireJS解决同样问题,只是运行机制不同。
      // 定义模块  myModule.js
      define(function(require, exports, module) {
        var $ = require('jquery.js')
        $('div').addClass('active');
      });
    
      // 加载模块
      seajs.use(['myModule.js'], function(my){
      });
    
    Sea.js 推崇一个模块一个文件,遵循统一的写法。
    define(id?, deps?, factory) 因为CMD推崇一个文件一个模块,所以经常就用文件名作为模块id CMD推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写 factory有三个参数
    function(require, exports, module)
    require: require 是 factory 函数的第一个参数 require(id): require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口
    exports: exports 是一个对象,用来向外提供模块接口
    module: module 是一个对象,上面存储了与当前模块相关联的一些属性和方法
    1. AMD与CMD区别
      最明显的区别就是在模块定义时对依赖的处理不同
    • AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
    • CMD推崇就近依赖,只有在用到某个模块的时候再去require
    • AMD在加载完成定义(define)好的模块就会立即执行,所有执行完成后,遇到require才会执行主逻辑。(提前加载)
    • CMD在加载完成定义(define)好的模块,仅仅是下载不执行,在遇到require才会执行对应的模块。(按需加载)
    • AMD用户体验好,因为没有延迟,CMD性能好,因为只有用户需要的时候才执行。

本文使用 mdnice 排版