前端面试百分之九十九过的技巧(二)

针对面试中出镜率比较高的重难点知识梳理。

本篇为系列文章:

上篇:前端面试百分之九十九过的技巧(一)

四、模块化开发

4.1 什么是模块

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起
  • 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信

4.2 模块化的好处

  • 避免命名冲突(减少命名空间污染)
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

4.3 模块化规范

1、CommonJS

基本语法:

  • 暴露模块:module.exports = valueexports.xxx = value
  • 引入模块:require(xxx),如果是第三方模块,xxx为模块名;如果是自定义模块,xxx为模块文件路径

加载某个模块,其实是加载该模块的module.exports属性。 require命令用于加载模块文件。require命令的基本功能是,读入并执行一个JavaScript文件,然后返回该模块的exports对象。如果没有发现指定模块,会报错。 CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。这点与ES6模块化有重大差异。

2、ES6 模块化

export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };

/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

也可以用 export default

// export-default.js
export default function () {
  console.log('foo');
}

// import-default.js
import customName from './export-default';
customName(); // 'foo'

3、ES6 模块与 CommonJS 模块的差异

它们有两个重大差异:

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

五、封装请求,添加公共 header

添加 header

//创建 axios 实例
let instance = axios.create({timeout: 1000 * 12});

//设置post请求头
instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
instance.defaults.withCredentials=true; //让ajax携带cookie

在 api 方法中添加 header

postJson(url, params) {
  return axios.post(`${base}${url}`, params, {
    headers: {'content-type': 'application/json;charset=UTF-8'}
  })
},

//基本的get方法
get(url, params) {
  return axios.get(`${base}${url}`, {params: params})
},

//基本的post方法
post(url, params) {
  return axios.post(`${base}${url}`, JSON.stringify(params))
},

六、页面性能优化

性能优化几乎是必问的,最好把下面五种方式都说出来。

第二、三点会延伸了问,小括号里是一般延伸的方向。详细的解释参见 十二、页面性能

目标:加快界面展示速度,减少数据请求次数。

提升页面性能的方法有哪些?

  1. 资源压缩合并,减少 HTTP 请求
  2. 非核心代码异步加载(异步加载的方式,异步加载的区别)
  3. 利用浏览器缓存(缓存的分类,缓存原理)
  4. 使用 CDN
  5. 预解析 DNS
//强制打开 <a> 标签的 dns 解析
<meta http-equiv="x-dns-prefetch-controller" content="on">
//DNS预解析
<link rel="dns-prefetch" href="//host_name_to_prefetch.com">

七、浏览器缓存

缓存策略的分类:

  • 强缓存
  • 协商缓存

缓存策略都是通过设置 HTTP Header 来实现的。 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识。 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。

7.1 强缓存

强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

1. Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。 Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效Expires: Wed, 22 Oct 2018 08:41:00 GMT表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。

2. Cache-Control

在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。 Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:

前端面试百分之九十九过的技巧(二)

3. Expires和Cache-Control两者对比

其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。 强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。

7.2 协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

  • 协商缓存生效,返回304和Not Modified
  • 协商缓存失效,返回200和请求结果

协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。

1. Last-Modified 和 If-Modified-Since

浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified 的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和 header;

Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT

浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200。 但是 Last-Modified 存在一些弊端:

  • 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
  • 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源

既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETagIf-None-Match

2. ETag 和 If-None-Match

Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

3. 两者之间对比

  • 首先在精确度上,Etag要优于Last-Modified。

Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体       现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-                 Modified也有可能不一致。

  • 第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
  • 第三在优先级上,服务器校验优先考虑Etag

7.3 缓存机制

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。

强缓存与协商缓存的区别可以用下表来表示:

缓存类型 获取资源形式 状态码 发送请求到服务器
强缓存 从缓存取 200(from cache) 否,直接从缓存取
协商缓存 从缓存取 304(Not Modified) 是,通过服务器来告知缓存是否可用

用户行为对缓存的影响

用户操作 Expires/Cache-Control Last-Modied/Etag
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进回退 有效 有效
F5刷新 无效 有效
Ctrl+F5强制刷新 无效 无效

7.4 from memory cache 与 from disk cache对比

在chrome浏览器中的控制台Network中size栏通常会有三种状态

  1. from memory cache
  2. from disk cache
  3. 资源本身的大小(如:1.5k)

三种的区别:

  • from memory cache:字面理解是从内存中,其实也是字面的含义,这个资源是直接从内存中拿到的,不会请求服务器一般已经加载过该资源且缓存在了内存当中,当关闭该页面时,此资源就被内存释放掉了,再次重新打开相同页面时不会出现from memory cache的情况。
  • from disk cache:同上类似,此资源是从磁盘当中取出的,也是在已经在之前的某个时间加载过该资源,不会请求服务器但是此资源不会随着该页面的关闭而释放掉,因为是存在硬盘当中的,下次打开仍会from disk cache
  • 资源本身大小数值:当http状态为200是实实在在从浏览器获取的资源,当http状态为304时该数字是与服务端通信报文的大小,并不是该资源本身的大小,该资源是从本地获取的
状态 类型 说明
200 form memory cache 不请求网络资源,资源在内存当中,一般脚本、字体、图片会存在内存当中。
200 form disk ceche 不请求网络资源,在磁盘当中,一般非脚本会存在内存当中,如css等。
200 资源大小数值 资源大小数值 从服务器下载最新资源。
304 报文大小 请求服务端发现资源没有更新,使用本地资源,即命中协商缓存。

八、webpack基本配置

面试高级前端开发的话肯定会问 webpack,稍微准备一下总比啥都不知道强

8.1 基本概念

webpack 的默认配置文件是 webpack.config.js

webpack 默认只能处理 js 文件,如果想处理图片等其他文件,则需要用到相应的 loader。比如 file-loader 、 url-loader 、 css-loader 、 style-loader ,如果用 sass 的话会用到 sass-loader 。

其他几个重要的概念是:

  • mode: 指定打包的模式,development 或 production。
  • devtool:指定生成 sourceMap 的方式。
  • entry:配置入口文件,多文件打包的话要打包几个文件,就在 entry 中写几个入口,output 的 filename 用占位符 [name] 表示。
  • output: 出口。
  • loader:辅助打包的各种工具。
  • plugins:插件,loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。如 HtmlWebpackPlugin,CleanWebpackPlugin。
  • devServer:使用 WebpackDevServer 开启热更新,提升开发效率。

8.2 HMR 热重载

配置方法

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {CleanWebpackPlugin} = require('clean-webpack-plugin');
//添加这行
const webpack = require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  entry: {
    main: './src/index.js',
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  },
  devServer: {
    contentBase: './dist',
    open: true,
    port: 9090,
    //添加下面两行
    hot: true,
    hotOnly: true
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "src/index.html"
    }),
    new CleanWebpackPlugin(),
    //添加这行
    new webpack.HotModuleReplacementPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.(png|jpg|gif|jpeg)/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name]_[hash].[ext]',
            outputPath: 'images/'
          }
        }
      }, {
        test: /\.(eot|woff|svg|ttf)/,
        use: {
          loader: 'file-loader',
        }
      }, {
        test: /\.(css|scss)/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // modules: true
            }
          },
          'sass-loader',
          'postcss-loader'
        ]
      }
    ]
  }
}

CSS热重载 style-loader 和 css-loader 已经帮我们实现了。

JS热重载

//判断是否开启了热更新
if (module.hot){
  //监听 hotTest.js 文件,当文件有变动时执行箭头函数里的方法
  module.hot.accept('./hotTest.js', () => {
    //文件变动时执行的操作
    hotTest();
  })
}

8.3 代码、图片压缩

压缩JavaScript

目前最成熟的JavaScript代码压缩工具是UglifyJS,它会分析JavaScript代码语法树,理解代码含义,从而能做到诸如去掉无效代码、去掉日志输出代码、缩短变量名等优化。

要在Webpack中接入UglifyJS需要通过插件的形式,目前有两个成熟的插件,分别是:

  • UglifyJsPlugin:通过封装 UglifyJS 实现压缩。
  • ParallelUglifyPlugin:多进程并行处理压缩。

压缩图片

压缩图片使用 image-webpack-loader

开启 Gzip 压缩

开启 gzip 也可显著压缩大小。

好了,今天先分享这么多,更多内容下期分享。

最后

进大厂没有想象的那么难,关键还是技术。

机会是留给有准备的人,了解岗位要求后,早准备,做足准备,可以少走弯路,网上各种面经笔经,学习课程应有尽有。

下面分享给大家一份我整理的《前端开发工程师必备资料包》。

269页前端大厂面试宝典

前端面试百分之九十九过的技巧(二)

前端面试百分之九十九过的技巧(二)

前端面试题汇总

前端面试百分之九十九过的技巧(二)

JavaScript

前端面试百分之九十九过的技巧(二)

性能

前端面试百分之九十九过的技巧(二)

linux

前端面试百分之九十九过的技巧(二)

jQurey

前端面试百分之九十九过的技巧(二)

小程序相关

前端面试百分之九十九过的技巧(二)

前端资料汇总

前端面试百分之九十九过的技巧(二)

由于篇幅原因列举的内容不多,需要完整版《前端开发工程师必备资料包》的小伙伴们直接点击这里免费领取,祝大家顺风顺水顺财神!

结束语

无论做什么,不止前端,都应该要有自己的想法和思考,这样子才能把事情做好,做得更深。否则这就像一场梦,醒来还是很感动。希望各位读者,看上面的题目并不是背答案,而是理解它,并能活用,以后做类似的事情,有参考的思路。如果遇到和我同一个面试官,题目当然是不完全一样的,此时需要临场发挥自己的积累和灵活运用了。

最后再补充一点,如果你见过了普遍情况,了解到了普遍现象,那要是什么都和人家一样,最后不就是也成为普遍水平了吗?如果想脱离当前现状,实现突破,那么目标应该是成为有个性、有特色的、有区分度的人,成为一名不一样的前端,不一样的人。

上一篇:LeetCode重点题系列之【42. Trapping Rain Water】


下一篇:二十四.浏览器的协商缓存与服务器的自启动