2021前端面试必备题+答案

vue视频教程系列:

Vue3.0新个性教程

视频教程:点击观看

残缺教程目录:点击查看

Vue源码解析系列

视频教程:点击观看

残缺教程目录:点击查看

闲云游览我的项目(vue+element-ui)

视频教程:点击观看

残缺教程目录:点击查看

前端Vue3.0从0到1手把手撸码搭建治理后盾零碎

视频教程:点击观看

残缺教程目录:点击查看

一个 tcp 连贯能发几个 http 申请?

如果是 HTTP 1.0 版本协定,个别状况下,不反对长连贯,因而在每次申请发送结束之后,TCP 连贯即会断开,因而一个 TCP 发送一个 HTTP 申请,然而有一种状况能够将一条 TCP 连贯放弃在沉闷状态,那就是通过 Connection 和 Keep-Alive 首部,在申请头带上 Connection: Keep-Alive,并且能够通过 Keep-Alive 通用首部中指定的,用逗号分隔的选项调节 keep-alive 的行为,如果客户端和服务端都反对,那么其实也能够发送多条,不过此形式也有限度,能够关注《HTTP 权威指南》4.5.5 节对于 Keep-Alive 连贯的限度和规定。

而如果是 HTTP 1.1 版本协定,反对了长连贯,因而只有 TCP 连接不断开,便能够始终发送 HTTP 申请,继续一直,没有下限; 同样,如果是 HTTP 2.0 版本协定,反对多用复用,一个 TCP 连贯是能够并发多个 HTTP 申请的,同样也是反对长连贯,因而只有一直开 TCP 的连贯,HTTP 申请数也是能够没有下限地继续发送

Virtual Dom 的劣势在哪里?

Virtual Dom 的劣势」其实这道题目面试官更想听到的答案不是上来就说「间接操作/频繁操作 DOM 的性能差」,如果 DOM 操作的性能如此不堪,那么 jQuery 也不至于活到明天。所以面试官更想听到 VDOM 想解决的问题以及为什么频繁的 DOM 操作会性能差。

首先咱们须要晓得:

DOM 引擎、JS 引擎 互相独立,但又工作在同一线程(主线程) JS 代码调用 DOM API 必须 挂起 JS 引擎、转换传入参数数据、激活 DOM 引擎,DOM 重绘后再转换可能有的返回值,最初激活 JS 引擎并继续执行若有频繁的 DOM API 调用,且浏览器厂商不做“批量解决”优化, 引擎间切换的单位代价将迅速积攒若其中有强制重绘的 DOM API 调用,从新计算布局、从新绘制图像会引起更大的性能耗费。

其次是 VDOM 和实在 DOM 的区别和优化:

  1. 虚构 DOM 不会立马进行排版与重绘操作
  2. 虚构 DOM 进行频繁批改,而后一次性比拟并批改实在 DOM 中须要改的局部,最初在实在 DOM 中进行排版与重绘,缩小过多DOM节点排版与重绘损耗
  3. 虚构 DOM 无效升高大面积实在 DOM 的重绘与排版,因为最终与实在 DOM 比拟差别,能够只渲染部分

首屏和白屏工夫如何计算

首屏工夫的计算,能够由 Native WebView 提供的相似 onload 的办法实现,在 ios 下对应的是 webViewDidFinishLoad,在 android 下对应的是onPageFinished事件。

白屏的定义有多种。能够认为“没有任何内容”是白屏,能够认为“网络或服务异样”是白屏,能够认为“数据加载中”是白屏,能够认为“图片加载不进去”是白屏。场景不同,白屏的计算形式就不雷同。

办法1:当页面的元素数小于x时,则认为页面白屏。比方“没有任何内容”,能够获取页面的DOM节点数,判断DOM节点数少于某个阈值X,则认为白屏。 办法2:当页面呈现业务定义的错误码时,则认为是白屏。比方“网络或服务异样”。 办法3:当页面呈现业务定义的特征值时,则认为是白屏。比方“数据加载中”。

介绍下 promise 的个性、优缺点,外部是如何实现的,入手实现 Promise

1)Promise根本个性

  • 1、Promise有三种状态:pending(进行中)、fulfilled(已胜利)、rejected(已失败)
  • 2、Promise对象承受一个回调函数作为参数, 该回调函数承受两个参数,别离是胜利时的回调resolve和失败时的回调reject;另外resolve的参数除了正常值以外, 还可能是一个Promise对象的实例;reject的参数通常是一个Error对象的实例。
  • 3、then办法返回一个新的Promise实例,并接管两个参数onResolved(fulfilled状态的回调);onRejected(rejected状态的回调,该参数可选)
  • 4、catch办法返回一个新的Promise实例
  • 5、finally办法不论Promise状态如何都会执行,该办法的回调函数不承受任何参数
  • 6、Promise.all()办法将多个多个Promise实例,包装成一个新的Promise实例,该办法承受一个由Promise对象组成的数组作为参数(Promise.all()办法的参数能够不是数组,但必须具备Iterator接口,且返回的每个成员都是Promise实例),留神参数中只有有一个实例触发catch办法,都会触发Promise.all()办法返回的新的实例的catch办法,如果参数中的某个实例自身调用了catch办法,将不会触发Promise.all()办法返回的新实例的catch办法
  • 7、Promise.race()办法的参数与Promise.all办法一样,参数中的实例只有有一个率先扭转状态就会将该实例的状态传给Promise.race()办法,并将返回值作为Promise.race()办法产生的Promise实例的返回值
  • 8、Promise.resolve()将现有对象转为Promise对象,如果该办法的参数为一个Promise对象,Promise.resolve()将不做任何解决;如果参数thenable对象(即具备then办法),Promise.resolve()将该对象转为Promise对象并立刻执行then办法;如果参数是一个原始值,或者是一个不具备then办法的对象,则Promise.resolve办法返回一个新的Promise对象,状态为fulfilled,其参数将会作为then办法中onResolved回调函数的参数,如果Promise.resolve办法不带参数,会间接返回一个fulfilled状态的 Promise 对象。须要留神的是,立刻resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的完结时执行,而不是在下一轮“事件循环”的开始时。
  • 9、Promise.reject()同样返回一个新的Promise对象,状态为rejected,无论传入任何参数都将作为reject()的参数

2)Promise长处

  • ①对立异步 API

    • Promise 的一个重要长处是它将逐步被用作浏览器的异步 API ,对立当初各种各样的 API ,以及不兼容的模式和手法。
  • ②Promise 与事件比照

    • 和事件相比拟, Promise 更适宜解决一次性的后果。在后果计算出来之前或之后注册回调函数都是能够的,都能够拿到正确的值。 Promise 的这个长处很天然。然而,不能应用 Promise 解决屡次触发的事件。链式解决是 Promise 的又一长处,然而事件却不能这样链式解决。
  • ③Promise 与回调比照

    • 解决了回调天堂的问题,将异步操作以同步操作的流程表达出来。
  • ④Promise 带来的额定益处是蕴含了更好的错误处理形式(蕴含了异样解决),并且写起来很轻松(因为能够重用一些同步的工具,比方 Array.prototype.map() )。

3)Promise毛病

  • 1、无奈勾销Promise,一旦新建它就会立刻执行,无奈中途勾销。
  • 2、如果不设置回调函数,Promise外部抛出的谬误,不会反馈到内部。
  • 3、当处于Pending状态时,无奈得悉目前停顿到哪一个阶段(刚刚开始还是行将实现)。
  • 4、Promise 真正执行回调的时候,定义 Promise 那局部实际上曾经走完了,所以 Promise 的报错堆栈上下文不太敌对。

4)简略代码实现
最简略的Promise实现有7个次要属性, state(状态), value(胜利返回值), reason(错误信息), resolve办法, reject办法, then办法

.

class Promise{  constructor(executor) {    this.state = 'pending';    this.value = undefined;    this.reason = undefined;    let resolve = value => {      if (this.state === 'pending') {        this.state = 'fulfilled';        this.value = value;      }    };    let reject = reason => {      if (this.state === 'pending') {        this.state = 'rejected';        this.reason = reason;      }    };    try {      // 立刻执行函数      executor(resolve, reject);    } catch (err) {      reject(err);    }  }  then(onFulfilled, onRejected) {    if (this.state === 'fulfilled') {      let x = onFulfilled(this.value);    };    if (this.state === 'rejected') {      let x = onRejected(this.reason);    };  }}

5)面试够用版

function myPromise(constructor){ let self=this;  self.status="pending" //定义状态扭转前的初始状态   self.value=undefined;//定义状态为resolved的时候的状态   self.reason=undefined;//定义状态为rejected的时候的状态   function resolve(value){    //两个==="pending",保障了了状态的扭转是不不可逆的     if(self.status==="pending"){      self.value=value;      self.status="resolved";     }  }  function reject(reason){     //两个==="pending",保障了了状态的扭转是不不可逆的     if(self.status==="pending"){        self.reason=reason;        self.status="rejected";       }  }  //捕捉结构异样   try{      constructor(resolve,reject);  }catch(e){    reject(e);    } }myPromise.prototype.then=function(onFullfilled,onRejected){   let self=this;  switch(self.status){    case "resolved": onFullfilled(self.value); break;    case "rejected": onRejected(self.reason); break;    default:   }}// 测试var p=new myPromise(function(resolve,reject){resolve(1)}); p.then(function(x){console.log(x)})//输入1

6)大厂专供版

const PENDING = "pending"; const FULFILLED = "fulfilled"; const REJECTED = "rejected";const resolvePromise = (promise, x, resolve, reject) => {  if (x === promise) {    // If promise and x refer to the same object, reject promise with a TypeError as the reason.    reject(new TypeError('循环援用'))  }  // if x is an object or function,  if (x !== null && typeof x === 'object' || typeof x === 'function') {    // If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.    let called    try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.      let then = x.then // Let then be x.then      // If then is a function, call it with x as this      if (typeof then === 'function') {        // If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)        // If/when rejectPromise is called with a reason r, reject promise with r.        then.call(x, y => {          if (called) return          called = true          resolvePromise(promise, y, resolve, reject)        }, r => {          if (called) return          called = true          reject(r)        })      } else {        // If then is not a function, fulfill promise with x.        resolve(x)      }    } catch (e) {      if (called) return      called = true      reject(e)    }  } else {    // If x is not an object or function, fulfill promise with x    resolve(x)  }}function Promise(excutor) {  let that = this; // 缓存以后promise实例例对象  that.status = PENDING; // 初始状态  that.value = undefined; // fulfilled状态时 返回的信息  that.reason = undefined; // rejected状态时 回绝的起因   that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数  that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数  function resolve(value) { // value胜利态时接管的终值    if(value instanceof Promise) {      return value.then(resolve, reject);    }    // 实际中要确保 onFulfilled 和 onRejected ⽅办法异步执⾏行行,且应该在 then ⽅办法被调⽤用的那⼀一轮事件循环之后的新执⾏行行栈中执⾏行行。    setTimeout(() => {      // 调⽤用resolve 回调对应onFulfilled函数      if (that.status === PENDING) {        // 只能由pending状态 => fulfilled状态 (防止调⽤用屡次resolve reject)        that.status = FULFILLED;        that.value = value;        that.onFulfilledCallbacks.forEach(cb => cb(that.value));      }    });  }  function reject(reason) { // reason失败态时接管的拒因    setTimeout(() => {      // 调⽤用reject 回调对应onRejected函数      if (that.status === PENDING) {        // 只能由pending状态 => rejected状态 (防止调⽤用屡次resolve reject)        that.status = REJECTED;        that.reason = reason;        that.onRejectedCallbacks.forEach(cb => cb(that.reason));      }    });  }  // 捕捉在excutor执⾏行行器器中抛出的异样  // new Promise((resolve, reject) => {  //     throw new Error('error in excutor')  // })  try {    excutor(resolve, reject);  } catch (e) {    reject(e);  }}Promise.prototype.then = function(onFulfilled, onRejected) {  const that = this;  let newPromise;  // 解决理参数默认值 保障参数后续可能持续执⾏行行  onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;  onRejected = typeof onRejected === "function" ? onRejected : reason => {    throw reason;  };  if (that.status === FULFILLED) { // 胜利态    return newPromise = new Promise((resolve, reject) => {      setTimeout(() => {        try{          let x = onFulfilled(that.value);          resolvePromise(newPromise, x, resolve, reject); //新的promise resolve 上⼀一个onFulfilled的返回值        } catch(e) {          reject(e); // 捕捉前⾯面onFulfilled中抛出的异样then(onFulfilled, onRejected);        }      });    })  }  if (that.status === REJECTED) { // 失败态    return newPromise = new Promise((resolve, reject) => {      setTimeout(() => {        try {          let x = onRejected(that.reason);          resolvePromise(newPromise, x, resolve, reject);        } catch(e) {          reject(e);        }      });    });  }  if (that.status === PENDING) { // 期待态// 当异步调⽤用resolve/rejected时 将onFulfilled/onRejected收集暂存到汇合中    return newPromise = new Promise((resolve, reject) => {      that.onFulfilledCallbacks.push((value) => {        try {          let x = onFulfilled(value);          resolvePromise(newPromise, x, resolve, reject);        } catch(e) {          reject(e);        }      });      that.onRejectedCallbacks.push((reason) => {        try {          let x = onRejected(reason);          resolvePromise(newPromise, x, resolve, reject);        } catch(e) {          reject(e);        }      });    });  }};

手写公布订阅

class EventListener {    listeners = {};    on(name, fn) {        (this.listeners[name] || (this.listeners[name] = [])).push(fn)    }    once(name, fn) {        let tem = (...args) => {            this.removeListener(name, fn)            fn(...args)        }        fn.fn = tem        this.on(name, tem)    }    removeListener(name, fn) {        if (this.listeners[name]) {            this.listeners[name] = this.listeners[name].filter(listener => (listener != fn && listener != fn.fn))        }    }    removeAllListeners(name) {        if (name && this.listeners[name]) delete this.listeners[name]        this.listeners = {}    }    emit(name, ...args) {        if (this.listeners[name]) {            this.listeners[name].forEach(fn => fn.call(this, ...args))        }    }}

Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?

1)Vue为什么要用vm.$set() 解决对象新增属性不能响应的问题

  1. Vue应用了Object.defineProperty实现双向数据绑定
  2. 在初始化实例时对属性执行 getter/setter 转化
  3. 属性必须在data对象上存在能力让Vue将它转换为响应式的(这也就造成了Vue无奈检测到对象属性的增加或删除)

所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)

2)接下来咱们看看框架自身是如何实现的呢?

Vue 源码地位:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {  // target 为数组    if (Array.isArray(target) && isValidArrayIndex(key)) {    // 批改数组的长度, 防止索引>数组长度导致splcie()执行有误    target.length = Math.max(target.length, key)    // 利用数组的splice变异办法触发响应式      target.splice(key, 1, val)    return val  }  // key 曾经存在,间接批改属性值    if (key in target && !(key in Object.prototype)) {    target[key] = val    return val  }  const ob = (target: any).__ob__  // target 自身就不是响应式数据, 间接赋值  if (!ob) {    target[key] = val    return val  }  // 对属性进行响应式解决  defineReactive(ob.value, key, val)  ob.dep.notify()  return val}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  1. 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  2. 如果指标是对象,会先判读属性是否存在、对象是否是响应式,
  3. 最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决
defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法