前端面经汇总

本篇面经按照对应的考察点进行分类

1. CSS

1.1 实现响应式布局的几种方式

  • 百分比布局
  • 媒体查询布局
  • rem响应式布局
  • vw响应式布局
  • flex弹性布局

1.2 flex布局的实现

flex主要包含两部分的内容:轴和项目。

  • 轴:分为主轴(main axis)和交叉轴(cross axis)
  • 容器:父容器和子容器,子容器又可以被叫做项目;当其父元素为display:flex时,子元素就可以称之为项目

注意:容器具有这样的特点:父容器可以统一设置子容器的排列方式,子容器也可以单独设置自身的排列方式,如果两者同时设置,以子容器的设置为准。

容器,容器中常用属性主要为4个,父容器和子容器各两个。 父容器中可以使用的属性为:

  • justify-content:设置主轴上子元素的对齐方式
    • flex-start:起始端对齐,类似于word中的左对齐
    • flex-end:末尾端对齐,类似于word中的右对齐
    • center:居中对齐,类似于word中的居中对齐
    • space-around:子容器沿主轴均匀分布,位于首尾两端的子容器到父容器的距离是子容器间距的一半。
    • space-between:子容器沿主轴均匀分布,位于首尾两端的子容器与父容器相切,类似于word中的两端对齐
  • align-items:设置交叉轴上子元素的对齐方式
    • flex-start:起始端对齐
    • flex-end:末尾端对齐
    • center:居中对齐
    • baseline:baseline是基线对齐,是指首行文字
    • stretch:子容器沿交叉轴方向的尺寸拉伸至与父容器一致,当子元素不设置高度的时候产生

子容器中可以使用的属性为:

  • flex flex是一个复合属性,包含flex-grow flex-shink flex-basis这三个属性。
  1. flex-grow:flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大
  2. flex-shink:flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  3. flex-bisis:浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
  • align-self 单独设置子容器如何沿交叉轴排列:align-self,同父级元素的align-items属性 关于轴的介绍,可以看MDN文档的介绍,以上只介绍了部分

1.3 CSS中实现定位的方式

1.4 为什么有时候用translate来改变位置而不是定位

改变transformopacity不会触发浏览器重新布局(reflow)或重绘(repaint),只会触发合成(compositions)。而改变绝对定位会触发重新布局,进而触发重绘和复合。transform使浏览器为元素创建一个 GPU 图层,但改变绝对定位会使用到 CPU。 因此translate()更高效,可以缩短平滑动画的绘制时间。

能够触发合成的情况:

  1. 3D transforms: translate3d, translateZ 等;
  2. video, canvas, iframe 等元素;
  3. 通过 Element.animate() 实现的 opacity 动画转换;
  4. 通过 СSS 动画实现的 opacity 动画转换;
  5. position: fixed; 。。。

1.5 重绘和重排的理解

2. JS

2.1 Promise 和async/await有什么区别

定义在原型上面的方法是给实例对象使用的

  • Promise的出现解决了传统callback函数导致的“地域回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。

    而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。await又是相当于新建一个Promise,然后await后面的代码,相当于Promise中的.then调用链。

  • async await与Promise一样,是非阻塞的。

  • async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。

3. 前端性能优化

3.1 怎么做前端性能优化

4. 其他

4.1 实现即时通讯(Instant Messaging)有哪几种方式

即时通讯是指客户端无需向服务器端主动发起请求,就可以拿到服务器端的数据。

场景:比如QQ邮箱,在我们没有刷新页面,没有主动请求的情况下,当有新的消息发送过来的时候,邮箱的右下角就会出现对应的提示。即时通信业经常用来实现通知功能。

(1)实现方式
  1. 轮询(短轮询)
  2. 长轮询(comet)
  3. 长连接(SSE)
  4. WebSocket

image.png

(2)具体实现
1. 短轮询

短轮询仍然是传统的客户端与服务器端的通信方式,通过"一发一收"来进行数据的访问。基本思路是浏览器每隔一段时间去向服务器请求数据。当数据请求完毕的时候,关闭这个TCP连接。当下一次请求的时候,再次建立TCP连接。缺点也很明显,就是由于需要不断的建立http连接,严重浪费了服务器端和客户端的资源。

image.png

// 请求数据方法
const request = () => {}
// 每隔5秒发送一次请求
setInterval(request(), 5000)
2. 长轮询

当服务器收到客户端发来的请求后,不会立即响应,而是先将请求挂起,然后判断服务器端数据是否有更新。 如果有更新,则进行响应,如果一直没有数据,如果到达一定的时间限制(服务器端设置)才返回。客户端在处理完服务器返回的信息后,再次发出请求,重新建立连接。

客户端发送请求后服务器端不会立即返回数据,服务器端会阻塞请求连接不会立即断开,直到服务器端有数据更新或者是连接超时才返回,客户端才再次发出请求新建连接、如此反复从而获取最新数据。

// 请求数据方法
const request = () => {}
// 当数据请求返回结果后发起下一次请求
request().then(res => {
  request();
})

以上的长短轮询都是按照“一发一收”的客户端,服务器端的通信方式进行交互的。短轮讯是每隔一段时间就有客户端主动发起请求;长轮询则是对于客户端发起的请求,只有服务器端的数据变更后,才会给予响应

3. 长连接

SSE是HTML5新增的功能,允许服务器推送数据到客户端,利用的协议仍然是HTTP,最大的特点是不需要客户端发送请求,可以实现只要服务器端数据有更新,就可以马上发送到客户端。

SSE 的客户端 API 部署在EventSource对象上。具体的实现方式为

//上面的`url`可以与当前网址同域,也可以跨域。跨域时,可以指定第二
//参数,打开`withCredentials`属性,表示是否一起发送 Cookie。
var source = new EventSource(url);
var source = new EventSource(url, { withCredentials: true });
// 连接一旦建立,就会触发onopen事件
source.onopen = function (event) {
  // ...
};
//客户端收到服务器发来的数据,就会触发`message`事件,
source.onmessage = function (event) {
  var data = event.data;
  // handle message
};
// 上面代码中,事件对象的`data`属性就是服务器端传回的数据(文本格式)。

EventSource实例的readyState属性,表明连接的当前状态。该属性只读,可以取以下值。

  • 0:相当于常量EventSource.CONNECTING,表示连接还未建立,或者断线正在重连。
  • 1:相当于常量EventSource.OPEN,表示连接已经建立,可以接受数据。
  • 2:相当于常量EventSource.CLOSED,表示连接已断,且不会重连
4. webSocket

WebSocket是Html5定义的一个新协议,与传统的http协议不同,该协议可以实现服务器与客户端之间全双工通信。简单来说,首先需要在客户端和服务器端建立起一个连接,这部分需要http。连接一旦建立,客户端和服务器端就处于平等的地位,可以相互发送数据,不存在请求和响应的区别。

5. Vue

5.1 如果直接给Vue中的数组项赋值的话,能够监听到变化吗

5.2 $set的使用方法

  • 参数

    • {Object | Array} target
    • {string | number} propertyName/index
    • {any} value
  • 返回值:设置的值。

  • 用法

    这是全局 Vue.set 的别名。向响应式对象中添加一个property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi')

注意对象不能是 Vue 实例,或者 Vue 实例的根数据对象。

参考Vue.set

5.3 vue2能监听到数组的变化吗

5.4 $nextTick的作用,以及实现方式,$nextTick是宏任务还是微任务

在Vue中,一个tick包括同步任务、异步队列(数据的改变会被缓冲到这个异步队列中)的更新、UI渲染。所以当我们想要在当前的tick中访问到更新后的DOM节点时,是无法完成的。因为UI的渲染是在事件循环的最后进行的。具体的步骤是:

  1. 因为Vue中首先是同步代码在执行,在同步代码阶段还没有涉及到DOM的更新
  2. 接下来, Vue会开启一个异步队列,缓冲在该tick下发生的所有数据改变。在这个过程中,如果同一个 watcher 被多次触发,只会被推入到队列中一次。(这里其实相当于js在执行Promise的时候,会将then中的回调函数添加到微任务队列中)
  3. 同步任务执行完毕,开始执行异步 watcher 队列的任务,更新 DOM 。Vue 在内部尝试对异步队列使用原生的 Promise.then 和 MessageChannel 方法,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。
  4. 在该tick中,通过 Vue.nextTick 获取到改变后的 DOM 。通过 setTimeout(fn, 0) 也可以同样获取到。因为在下一个事件循环中DOM节点已经完成了更新

DOM是在事件循环中的哪个时间段进行更新的呢?

5.5 vue的双向绑定管理

vue的双向绑定是基于MVVM模型的:

  • 数据层Modal:应用的数据以及业务逻辑
  • 视图层view:应用的展示效果,各类的UI组件
  • 业务逻辑层viewModel:负责将数据和视图连接起来 双向绑定就是当数据变化是更新视图,当视图变化时更新数据。 总体来说,主要包含两个部分:
  1. 监听器:observer:对所有的数据进行响应式监听
  2. 解析器:compiler:将每个元素节点的指令进行扫描和解析,根据指令去替换数据,绑定对应的更新函数

具体的实现原理是

  1. new Vue()执行初始化,对data中的数据通过Object.defineProperty()进行响应式处理,这个过程发生在observer中,每个属性都会有一个dep实例来存储watcher实例数组
  2. 对模板进行编译,v-开头的关键词作为指令解析,找到动态绑定的数据,从data中获取数据并初始化,这个过程发生在compiler中。如果说遇到的指令是v-model,就会监听input时间,更新data中的数据
  3. 在解析指令的过程中,会定义一个更新函数和watcher,之后对应的数据变化时,watcher会调用更新函数,new watcher的过程中会去读取data中的key,触发依赖收集,将相对应的watcher添加到dep中
  4. 将来data中的数据发生变化的情况下,会首先找到dep,通知所有的watcher执行更新函数。
function defineReactive(data, key, val)
{
  observe(val);
  var dep=new Dep();
  Object.defineProperty(data,key,{
    get(){
      if(是否需要添加订阅者)
      {
        dep.addSub(watcher);
      }
      return val;
    },
    set(newValue){
      if(val==newValue){
        return;
      }
      val=newValue;
      console.log('属性' + key + '已经被监听了,现在值为:“' + newValue.toString() + '”')
      dep.notify();//如果数据变化,通知所有的订阅者,去执行更新视图的函数
    }
  })
}
function Dep () {
    this.subs = [];
}
Dep.prototype={
  addSub:function(sub){
    this.subs.push(sub)
  },
  notify:function(){
    this.subs.forEach(function(sub){
      //所有的watcher都要调用更新函数
      sub.update();
    })
  }
}

function observe(data) {
    if (!data || typeof data !== 'object') {
        return;
    }
    Object.keys(data).forEach(function(key) {
        defineReactive(data, key, data[key]);
    });
};

5.6 Vue的响应式原理

Vue的响应式原理是使用Object.defineProporty()对new Vew()中的data的数据的getter和setter进行数据劫持,数据劫持其实也可以理解为重新定义setter和getter的过程,就是让我这个对象失去了对数据直接操作的权利。 答出上面的部分只能说明是我们有所了解,更具体来说,我们应该明白响应式是什么?

响应式其实就是我们能够监听到数据的变化,在数据变化以后,会产生相应的反映。 Vue中的响应式涉及到3个对象:Observe Watcher Dep,对这3个对象进行简单的说明:

  • Observe:Observe其实就是一个数据的响应式处理工具,当Vue实例化的时候,会对data中的所有属性使用Object.defineProporty()进行数据劫持,主要是对data中的每一个属性的getter以及setter进行重写。
  • Dep:是一个依赖收集的池子,data中的每一个属性都有对应的一个Dep,即针对性地对每一个data中的数据进行观测。
  • Watcher:watcher是在编译模板的时候创建的,每一个组件都有一个对应的watcher。 以上信息的逻辑关系图为:

image.png

讲解:其中利用发布-订阅者模式的是Dep和watcher两者。Dep可以理解为一个专门负责买杂志的一个小报亭,watcher是来这个小报亭买报纸的人。当watcher来Dep中买报纸以后,Dep为了扩大自己的生意,会记录watcher的电话。这个过程就相当于添加订阅者的过程;后续如果报亭有新的报纸时,Dep就可以通知所有的watchers,有新的报纸到了,可以来购买了。发布-订阅者模式更像是一种一对多的通信方式。

5.6.1 对象的响应式处理

对象必须在data中完成初始化工作,才能进行响应式处理。如果通过obj.proporty='XXX'当方式添加属性,那么是监听不到数据变化的,这个时候需要用到全局的$set方法来完成属性的响应式处理。

5.6.2 数组的响应式处理

在Vue中,对响应式处理利用的是Object.defineProperty对数据进行拦截,而这个方法并不能监听到数组内部变化,数组长度变化,数组的截取变化等,所以需要对这些操作进行hack,让Vue能监听到其中的变化。Vue对原生的数组方法进行了重写,代码如下:

const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
];

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function(method) {
  // 缓存原生数组方法
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    // 执行并缓存原生数组功能
    const result = original.apply(this, args);
    // 响应式处理
    const ob = this.__ob__;
    let inserted;
    switch (method) {
    // push、unshift会新增索引,所以要手动observer
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
        break;
    }
    ob.dep.notify();
    return result;
  });
});

6. React

6.1 React Hooks包括哪些,常用的钩子函数是什么

6.2 useEffect有什么了解

7. 浏览器

7.1 对于浏览器的事件循环谈一谈

7.2

7.3

7.4

8. 计算机网络

8.1 常见的HTTP状态码

  1. 2XX 2XX的状态码表示请求被服务器端成功的处理了。
  • 200 OK 表示客户端发来的请求被服务器端正常处理了
  • 204 No content 表示客户端发来的请求被服务器端正常处理了,但是没有响应的内容,一般是用在客户端向服务器端发送消息,服务器端不需要进行返回的情况下。
  • 206 Partial COntent 表示客户端进行了范围请求,服务器端执行了这部分的GET请求。响应报文中包含由 Content-Range 指定范围的实体内容。
  1. 3XX 300多用来表示重定向。
  • 301 表示永久的重定向,即访问的页面被永久性地迁移到了新的URI,我们需要通过新的URI才能请求到页面。新的URI会在HTTP响应头的LOcation首部进行指定。
  • 302 表示临时重定向,多用于页面的重定向。一般的应用场景是:未登录的用户会跳转到登录页进行登录
  • 304 表示请求的资源在服务端没有被修改,当命中协商缓存的时候,请求到的资源会返回该状态码。
  1. 4XX 通常用来表示客户端的错误。
  • 400 表示客户端的请求报文中有错误,当错误发生时,需要修改当前的请求内容。
  • 401 表示当前请求的资源需要进行认证才能访问,比如登录失败的情况下,当我们访问后台系统的时候,会以这个状态码进行标识
  • 403 表示请求的资源被客户端拒绝了,服务器端不会给予详细的理由。典型的场景是:普通用户在登录状态的时候,去访问后台管理系统中的页面时,就会出现403状态码
  • 404 请求的资源在服务器端不存在
  1. 5XX 通常用来表示服务器端发生了错误
  • 500 500表示当前发起的请求,引起了服务器端的错误
  • 502 该状态码表明扮演网关或代理角色的服务器,从上游服务器中接收到的响应是无效的。
  • 503 该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。