阅读 23296

前端面试必备技巧(二)重难点梳理

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

相比于第一篇 前端面试必备技巧,本篇文章更贴合今年的面试实际。第一篇比较全面,也比较基础,建议先看一遍上一篇再看本篇会更容易理解。

第一篇无法访问问题已解决,我也不知道咋回事,又重新发布了一遍

一、ES6常见用法

关于 ES6(泛指 ECMAScript 2015 及以后的版本)几乎是面试必问的,一般的问法是:“平常会使用 ES6 吗?列举几个 ES6 的用法”。

回答出来三四个就差不多了,但回答的每一个都要弄清楚,有的面试官会延伸着追问。

如果时间充足,还是建议看看 阮一峰的 ES6 入门教程

1.1 let 和 const

  • let 在块级作用域内有效,不会污染全局变量
  • const 一经声明不能改变。注意保证的是它指向的内存地址不能改变,如果是对象或者数组里面的属性或元素可以改变的。
  • 存在暂时性死区,不能变量提升。
  • 只能先声明再使用,且不能重复声明

1.2 字符模板

用作字符串拼接:你好,${name}

1.3 变量的解构赋值

  • let [a,b,c] = [1,2,3]
  • 交换变量的值:[a,b] = [b,a]
  • 提取 JSON 数据,获取服务器返回数据时有用:let {data, code} = res
  • 输入模块的指定方法: 
const { SourceMapConsumer, SourceNode } = require("source-map");
复制代码
  • 从函数返回多个值: 返回个数组或对象

1.4 扩展运算符

扩展运算符(spread)是三个点(...)。

  • 复制数组:let arr2 = [...arr1]
  • 合并数组:[...arr1, ...arr2, ...arr3]

1.5 Promise

  • 成功调用 resolve,失败调用 reject
  • .then 获取结果,.catch 捕获异常。捕获异常还可通过 .then 的第二个参数
  • .finally 无论成功失败都一定会调用
  • 多个并发的请求,用 Promise.all()
    • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
    • 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
let p = Promise.all([p1,p2,p3])
p.then(([res1, res2,res3]) => {};
复制代码

Promise 示例:

new Promise(){
	if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
}.then().catch().finally()
复制代码

1.6 async await

async 函数是什么?一句话,它就是 Generator 函数的语法糖。 async 函数对 Generator 函数的改进:

  • 内置执行器,直接调用即可。Generator 还需要调用 next() 才能执行
  • 更好的语义。asyncawait*yield 更好理解
  • 返回值是 Promise

1.7 箭头函数

箭头函数 () => {}this 是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this 就继承了定义函数的对象。this 一旦确定以后不会改变。

普通函数的 this 指向的是调用它的对象。

二、移动端的 H5 兼容性和适配

常用的适配方案是 rem + flex 布局。

2.1 rem 适配

先在应用的入口设置基准字体大小,越早执行越好。如果设计图尺寸宽度是 750,就除以 7.5。具体的值根据实际情况来算。

document.documentElement.style.fontSize = document.documentElement.clientWidth / 7.5 + 'px';
复制代码

设置完基准字体大小后,通过 SASS 的函数封装一个方法方便使用

@function px2rem($value){
  @return $value * 0.02rem
}
复制代码

使用时直接调用封装好的函数即可,value 传 px 的值,方法会自动转换成 rem。

width: px2rem(100)
复制代码

2.2 采用 flex 布局

flex 布局推荐 阮一峰的 Flex 布局教程:语法篇

三、Vue 相关问题

一般来说中小公司问框架会多一些,具体是 Vue 还是 React 根据公司所用技术栈而定。因为中小公司技术栈比较统一,很少出现同时用两种甚至三种框架的,且更注重干活的能力,所以框架的使用基本会占面试的一半时间。

大公司反而框架问的少,比较注重基础。他们的逻辑是考察你有没有这个能力,如果能力强用啥框架都能快速上手。

因为我对 React 不太熟,所以就只准备了 Vue 的一些问题。下面几个问题都是面试频率非常高的,一定要弄懂。

有一篇文章对 Vue 总结的比较全,这里也是摘录了几个答案。完整的内容看这里:30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度

3.1 Vue双向绑定原理

如果问框架的话 Vue 肯定会问这个问题,把下面这一段话背下来就好。

Vue 主要通过以下 4 个步骤来实现数据双向绑定的:

  • 实现一个监听器 Observer:对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
  • 实现一个解析器 Compile:解析 Vue 模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
  • 实现一个订阅者 Watcher:Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
  • 实现一个订阅器 Dep:订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。

回答完这个问题有的面试官还会延伸问:Vue3 将会用 Proxy 替换 Object.defineProperty() proxy 有什么优点?

建议把 proxy 相关介绍 看一下,说出几个优点。

3.2 虚拟 Dom 实现原理

这个题的重要性仅次于 Vue 双向绑定原理,建议掌握

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

优点:

  • 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
  • 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
  • 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

  • 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

深入剖析:Vue核心之虚拟DOM juejin.cn/post/684490…

3.3 路由缓存

使用 <keep-alive> 可缓存路由。

它有 includeexclude 两个属性,分别表示包含或排除某些路由,值可以是字符串、数组或者正则表达式。

独有的生命周期方法:activiteddeactivited

3.4 路由跳转,name 和 path 跳转的区别

路由跳转的几种方式

// 字符串
this.$router.push('home')

// 命名的路由
this.$router.push({
  name: 'user',
  params: {userId: '123'}
})
//接收参数
this.userId = this.$route.params.userId

// 带查询参数,变成 /user?userId=123
this.$router.push({
  path: '/user',
  query: {userId: '123'}
})
//接收
this.userId = this.$route.query.userId;
复制代码

name 和 path 跳转的区别在于

  • name 传参用 params,path 传参用 query。
  • 用 name 跳转后参数不会携带到 url 上,用 query 传参参数会携带到 url 上。

3.5 路由守卫

全局前置守卫

注意一定要调用  next(); ,否则钩子就不会被 resolved。

const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
  // ...
  next();
})
复制代码

全局解析守卫

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
  // ...
})
复制代码

路由独享的守卫

你可以在路由配置上直接定义 beforeEnter 守卫:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})
复制代码

这些守卫与全局前置守卫的方法参数是一样的。

组件内的守卫

最后,你可以在路由组件内直接定义以下路由导航守卫:

  • beforeRouteEnter
  • beforeRouteUpdate (2.2 新增)
  • beforeRouteLeave

注意要调用 next() 

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}
复制代码

这块内容比较多,更详细的介绍看 vue-router 官方文档

3.6 Vue 和 React 之间的区别

Vue 的表单可以使用 v-model 支持双向绑定,相比于 React 来说开发上更加方便,当然了 v-model 其实就是个语法糖,本质上和 React 写表单的方式没什么区别。

改变数据方式不同,Vue 修改状态相比来说要简单许多,React 需要使用 setState 来改变状态,并且使用这个 API 也有一些坑点。并且 Vue 的底层使用了依赖追踪,页面更新渲染已经是最优的了,但是 React 还是需要用户手动去优化这方面的问题。

React 16以后,有些钩子函数会执行多次,这是因为引入 Fiber 的原因,这在后续的章节中会讲到。

React 需要使用 JSX,有一定的上手成本,并且需要一整套的工具链支持,但是完全可以通过 JS 来控制页面,更加的灵活。Vue 使用了模板语法,相比于 JSX 来说没有那么灵活,但是完全可以脱离工具链,通过直接编写 render 函数就能在浏览器中运行。

在生态上来说,两者其实没多大的差距,当然 React 的用户是远远高于 Vue 的。

在上手成本上来说,Vue 一开始的定位就是尽可能的降低前端开发的门槛,然而 React 更多的是去改变用户去接受它的概念和思想,相较于 Vue 来说上手成本略高。

3.7 组件间传值

组件间传值又分为父子组件传值和非父子组件传值

父子组件间传值

  • 父组件给子组件传值,直接通过props传值
<custom content="hello world"></custom>
复制代码
复制代码
  • 子组件给父组件传值,通过 emit发送事件
this.$emit('chooseType', type)
复制代码
复制代码

父组件接收事件:

<custom content="hello world" @chooseType="handleType"></custom>
复制代码
复制代码

非父子组件传值

主要通过事件总线传值

在根节点给 Vue 挂载一个空的 Vue 对象

Vue.prototype.bus = new Vue();
复制代码
复制代码

需要发送事件的组件里

this.bus.$emit("change", params)
复制代码
复制代码

接收事件的组件

this.bus.$on("change", (msg) => {
    //do yourself work
})
复制代码

除了以上这几个问题,还有 Vue 生命周期,v-show 和 v-if,vuex 等知识点经常会问。

更多 Vue 开发技巧可以看我的另一篇文章:Vue 开发经验小记

四、模块化开发

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-ControlLast-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时该数字是与服务端通信报文的大小,并不是该资源本身的大小,该资源是从本地获取的
状态类型说明
200form memory cache不请求网络资源,资源在内存当中,一般脚本、字体、图片会存在内存当中。
200form 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 也可显著压缩大小。

九、安全

前端安全分两类:CSRF、XSS

常考点:基本概念和缩写、攻击原理、防御措施

9.1 CSRF

CSRF(Cross-site request forgery)跨站请求伪造。

攻击原理

  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

防御措施

  1. Token验证
  2. Referer 验证(简单易行,但 referer 可能被改变)
  3. 隐藏令牌(跟 Token验证差不多,把令牌存到 header 中)

9.2 XSS

XSS(cross-site scripting)跨域脚本攻击

攻击原理

往 Web 页面里插入恶意Script代码

防御措施

  1. HTML:对以下这些字符进行转义:
&:&amp;
<:&alt;
>:&gt;
':&#x27;
":&quot;
/:&#x2F;
复制代码
  1. Javascript:把所有非字母、数字的字符都转义成小于256的ASCII字符;

  2. URL:使用Javascript的encodeURIComponent()方法对用户的输入进行编码,该方法会编码如下字符:

    ,      /      ?     :     @     &     =     +     $     #
    复制代码

十、深拷贝浅拷贝

问 js 基础的时候一般会问到深浅拷贝的问题,出镜率也比较高

浅拷贝

  1. Object.assign 

  2. 展开运算符 ... 

深拷贝

  1. 可以通过 JSON.parse(JSON.stringify(object)) 来解决。

    但有几个需要注意的地方

    • 会忽略 undefined
    • 会忽略 symbol
    • 不能序列化函数
    • 不能解决循环引用的对象
  2. 如果你所需拷贝的对象含有内置类型并且不包含函数,可以使用 MessageChannel

  3. 推荐使用 lodash 的深拷贝函数

十一、HTTP,HTTPS,HTTP2.0

http 相关的也比较重要,经常被问。

11.1 基本概念

HTTP(HyperText Transfer Protocol:超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。 简单来说就是一种发布和接收 HTML 页面的方法,被用于在 Web 浏览器和网站服务器之间传递信息。 HTTP 默认工作在 TCP 协议 80 端口,用户访问网站 http:// 打头的都是标准 HTTP 服务。 HTTP 协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。 HTTPS(Hypertext Transfer Protocol Secure:超文本传输安全协议)是一种透过计算机网络进行安全通信的传输协议。HTTPS 经由 HTTP 进行通信,但利用 SSL/TLS 来加密数据包。HTTPS 开发的主要目的,是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。 HTTPS 默认工作在 TCP 协议443端口,它的工作流程一般如以下方式:

  1. TCP 三次同步握手
  2. 客户端验证服务器数字证书
  3. DH 算法协商对称加密算法的密钥、hash 算法的密钥
  4. SSL 安全加密隧道协商完成
  5. 网页以加密的方式传输,用协商的对称加密算法和密钥加密,保证数据机密性;用协商的hash算法进行数据完整性保护,保证数据不被篡改。

11.2 HTTP 与 HTTPS 区别

  • HTTP 明文传输,数据都是未加密的,安全性较差,HTTPS(SSL+HTTP) 数据传输过程是加密的,安全性较好。
  • 使用 HTTPS 协议需要到 CA(Certificate Authority,数字证书认证机构) 申请证书,一般免费证书较少,因而需要一定费用。证书颁发机构如:Symantec、Comodo、GoDaddy 和 GlobalSign 等。
  • HTTP 页面响应速度比 HTTPS 快,主要是因为 HTTP 使用 TCP 三次握手建立连接,客户端和服务器需要交换 3 个包,而 HTTPS除了 TCP 的三个包,还要加上 ssl 握手需要的 9 个包,所以一共是 12 个包。
  • http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
  • HTTPS 其实就是建构在 SSL/TLS 之上的 HTTP 协议,所以,要比较 HTTPS 比 HTTP 要更耗费服务器资源。

11.3 TCP 三次握手

在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接

  • 第一次握手:客户端尝试连接服务器,向服务器发送 syn 包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入 SYN_SEND 状态等待服务器确认
  • 第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个 SYN包(syn=k),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态
  • 第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

参考文档:www.runoob.com/w3cnote/htt…

11.4 http2.0 优化

什么是 HTTP2.0

简单来说,HTTP/2(超文本传输协议第2版,最初命名为HTTP2.0),是HTTP协议的第二个主要版本。HTTP/2是HTTP协议自1999年HTTP1.1发布后的首个更新,主要基于SPDY协议。 HTTP2.0的特点是:在不改动HTTP语义、方法、状态码、URI及首部字段的情况下,大幅度提高了web性能。

什么是SPDY协议

刚刚对HTTP2.0的介绍中引出了一个名词 —— SPDY协议,这又是什么呢? SPDY是Speedy的昵音,意为“更快”。它是Google开发的基于TCP协议的应用层协议。目标是优化HTTP协议的性能,通过压缩、多路复用和优先级等技术,缩短网页的加载时间并提高安全性。SPDY协议的核心思想是尽量减少TCP连接数。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。

HTTP1.x  的缺点

  1. HTTP/1.0一次只允许在一个TCP连接上发起一个请求,HTTP/1.1使用的流水线技术也只能部分处理请求并发,仍然会存在队列头阻塞问题,因此客户端在需要发起多次请求时,通常会采用建立多连接来减少延迟。
  2. 单向请求,只能由客户端发起。
  3. 请求报文与响应报文首部信息冗余量大。
  4. 数据未压缩,导致数据的传输量大。

HTTP2.0特点

  1. 二进制传输
  2. 多路复用
  3. Header压缩
  4. 服务器push
  5. 更安全

参考文档:segmentfault.com/a/119000001…

常见的请求方式

GET、POST、PUT、DELETE、PATCH

十二、Linux 操作

Linux 相关的一般后端问的多一些,前端问的少

列出在内存中运行的  全部进程信息

ps  -aux
//列出 *** 进程的详细信息
ps -aux | grep *** 
复制代码

杀死进程

 kill -9 -pid 
复制代码

十三、算法

算法还是得在平时积累

二分法

二分法查找,也称为折半法,是一种在有序数组中查找特定元素的搜索算法。 二分法查找的思路如下:

  1. 首先,从数组的中间元素开始搜索,如果该元素正好是目标元素,则搜索过程结束,否则执行下一步。
  2. 如果目标元素大于/小于中间元素,则在数组大于/小于中间元素的那一半区域查找,然后重复步骤(1)的操作。
  3. 如果某一步数组为空,则表示找不到目标元素。

二分法查找的时间复杂度O(logn)。

动态规划

动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。 动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。


更基础的 js 基础相关问题请移步上一篇文章 前端面试必备技巧

最后祝各位大佬都能拿到满意的 offer~

文章分类
前端
文章标签