2020年6月前端面试题合集

286 阅读18分钟

JS篇

1.原型和原型链

[原型] 每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。

使用原型对象的好处就是:可以让所有对象实例共享它所包含的属性和方法。

  1. prototype是函数的原型对象,它会被对应的__proto__引用
  2. 要知道自己的__proto__引用了哪个ptototype,只需要看看哪个构造函数构造了你,那你的__proto__就是那个构造函数的prototype
  3. 所有构造函数的原型链最后都会引用object构造函数的原型,即object构造函数的原型是所有原型链的最底层,即 Object.prototype.__proto__===null
//一般情况下,object都是由构造函数构造出来的
Object.__proto__===Function.prototype

Function.__proto__===Function.prototype
Function.prototype.__proto__ ===Object.prototype

true.__proto__ === Boolean.prototype
Boolean.prototype.__proto__ === Object.prototype

Object.prototype.__proto__===null

// 原型链的最底层是Object.prototype,而Object.prototype的原型是null

2.new操作符

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象
var o = new A();
===
var o = new Object();//创建一个空对象
o.__proto__ = A.prototype; //将空对象的原型赋值为构造器函数的原型
A.call(o);//更改构造器函数内部this,将其指向新创建的空对象

3.this

this 永远指向最后调用它的那个对象

this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window 当函数作为某个对象的方法调用时,this等于那个对象。 当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

call/apply/bind存在的意义:改变函数执行时的上下文,即改变函数运行时的this指向。
区别:bind 方法的返回值是函数,并且需要稍后调用,才会执行。而 apply 和 call 则是立即调用。

//call
Function.call(obj,[param1[,param2[,…[,paramN]]]])
//apply
Function.apply(obj[,argArray])
//bind
Function.bind(thisArg[, arg1[, arg2[, ...]]])
//e.g.
function add (a, b) {
    return a + b;
}
function sub (a, b) {
    return a - b;
}
add.bind(sub, 5, 3); // 这时,并不会返回 8
add.bind(sub, 5, 3)(); // 调用后,返回 8
封装一个bind的方法
function bind(f,o){
  if(f.bind) return f.bind(o); 
  else return function(){
    return f.apply(o,arguments)
  }
}

4.简述http

HTTP,全称为 HyperText Transfer Protocol,即为超文本传输协议。

  • http特性

    • HTTP 是无连接无状态的
    • HTTP 一般构建于 TCP/IP 协议之上,默认端口号是 80
    • HTTP 可以分为两个部分,即请求和响应。
  • http请求

    • 请求报文(请求行 + 请求头 + 数据体)
      • 请求行---请求方法、请求地址和协议及版本
      • 请求头
        User-Agent:产生请求的浏览器类型。
        Accept:客户端可识别的内容类型列表。
        Host:主机地址
        Cache-Control: no-cache
        Range: 只请求实体的一部分,指定范围
        Host: 指定请求的服务器的域名和端口号
        Pragma: 用来包含实现特定的指令
        Cookie:HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。
        ( https://segmentfault.com/a/1190000006689767 )
      • 数据体---post方法中,会把数据以key value形式发送请求
    • 响应报文
      • 状态行
      • 消息报头
      • 响应正文

5.闭包

闭包是指有权访问另一个函数作用域中的变量的函数。

【用途】:

  • 可以读取函数内部的变量
  • 让这些变量的值始终保持在内存中

【使用闭包注意事项】:

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

6.虚拟DOM

Virtual DOM本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象。 由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。

虚拟 DOM 的实现原理:

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

7.Promise

promise是一个对象,它代表了一个异步操作的最终完成或者失败。
【async + await方式】
实现机制:async/await利用协程和Promise实现了同步方式编写异步代码的效果,其中Generator是对协程的一种实现

一个promise有以下几种状态:

  • pending:初始状态,既不是成功,也不是失败状态
  • fulfilled:操作成功完成
  • rejected:操作失败

方法:

- promise.all([]) //该方法返回一个新的promise对象,
- promise.race([]) //数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行
- promise.reject()
- promise.resolve() 

[ 原理 ]:
promise本身相当于一个状态机,拥有三种状态:pending、fulfilled、rejected。一个promise对象和初始化状态是pending,调用resolve后会将promise的状态扭转为fulfilled,调用reject后会将promise的状态改变为rejected,这两种改变一旦发生便不能再改变到其他状态
promise对象原型上有一个then方法,then方法会返回一个新的promise对象,并且回调函数return的结果作为该promise resolve】的结果,then方法会在一个promise状态被改变为fulfilled或rejected时被调用。

实现一个简单的promise

function MyPromise(executor){
  let self = this
  self.value = null
  self.onResolveCallbacks = []
  self.onRejectCallbacks = []
  
  function resolve(value){
    if(self.status === 'pending'){
      self.status = 'fulfilled'
      self.value = value
      self.onResolveCallbacks.forEach(cb=>cb(self.value))
    }
  }
  function reject(reason){
    if(self.status === 'pending'){
      self.status = 'rejected'
      self.value = reason
      self.onRejectCallbacks.forEach(cb=>cb(self.value))
    }
  }
  try{
    executor(resolve,reject)
  }catch(e){
    reject(e)
  }
}

//then
MyPromise.prototype.then = function(onFulfilled,onRejected){
  let self = this
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected = typeof onRejected ==='function'?onRejected : r=> throw r

  if(self.status === 'pending'){
    self.onResolveCallbacks.push(onFulfilled)
    self.onRejectCallbacks.push(onRejected)
  }else if(self.status === 'fulfilled'){
    onFulfilled(self.value)
  }else if(self.status === 'rejected'){
    onRejected(self.value)
  }
}

8.实现add(1)(2)(3)

//方法1
var add=function(a){
  return function(b){
    return function(c){
      return a+b+c
    }
  }
}
add(1)(2)(3)//6

//方法2
function add(x){
  var sum=x;
  var tmp=function(y){
    sum=sum+y
    return tmp
  }
  tmp.toString=function(){
    return sum;
  }
  return tmp
}
add(1)(2)(3) //6

[柯里化]柯里化其实是函数式编程的一个过程,在这个过程中我们能把一个带有多个参数的函数转换成一系列的嵌套函数。它返回一个新函数,这个新函数期望传入下一个参数。

作用:参数复用、提前返回和 延迟执行

9.MVVM实现原理

[数据劫持]vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

[实现mvvm的双向绑定]

  1. 实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,如有变动可以拿到最新值并通知订阅者;
  2. 实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模版替换数据,以及绑定相应的更新函数;
  3. 实现一个Watcher,作为链接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。
var obj = {}
Object.defineProperty(obj, 'msg', {
  // 设置 obj.msg = "1" 时set方法会被系统调用 参数分别是设置后和设置前的值
  set: function (newVal, oldVal) {  },
  // 读取 obj.msg 时get方法会被系统调用
  get: function ( newVal, oldVal ) {}
})

剖析Vue原理&实现双向绑定MVVM

10.BFF

Backends For Frontends服务于前端的后端,也就是服务器设计 API 时会考虑前端的使用,并在服务端直接进行业务逻辑的处理。 juejin.cn/post/684490… https://segmentfault.com/a/1190000009558309

11.模块化规范

  • AMD ([requirejs])
  • CMD ([seajs])
  • CommonJS ([nodejs])
    • 加载模块 require()
    • 导出模块 module.exports= {} / exports= {}
  • ES6 中的 import / export
  • UMD 通用模块化规范 (可以兼容 AMD、CommonJS、浏览器中没有模块化规范 等这些语法)

12.cookie、localStorage、sessionStorage

【cookie】cookie的设计初衷是给服务端脚本用来存储少量数据的,该数据会在每次请求一个相关的url时传递到服务器中。

  • 少量数据
  • 自动在web浏览器和web服务器之间传输
  • cookie存储数据有时间限制

【localStorage】

  • 仅本地存储,不在浏览器与服务器之间传输
  • 永久存储,除非web应用可以删除,否则数据将一直保存在客户端,永久不过期
  • 支持多窗口数据共享,即使窗口关闭localStorage数据也一直存在

【sessionStorage】

  • 仅本地存储,不在浏览器与服务器之间传输
  • 永久存储
  • 窗口关闭时,同时删除sessionStorage存储的数据

13.ajax

  • 创建一个XMLHttpRequest对象,也就是创建一个异步调用对象。
  • 创建一个新的HTTP请求,并指定该HTTP请求方式,URL和验证信息。
  • 设置响应HTTP请求状态变化的函数。
  • 发送HTTP请求
  • 获取异步调用返回的数据。
  • 使用JavaScript和DOM实现局部刷新。
// 实例化一个xhr对象
let xhr = new XMLHttpRequest()
// 监听状态的变化
xhr.onreadystatechange = () =>{
    // 监听数据请求完毕  readyState 0=>初始化 1=>载入 2=>载入完成 3=>解析 4=>完成
    if(xhr.readyState === 4){
      // 判断状态码
      if( xhr.status === 200 ){
         //将数据转化成json对象
          alert(JSON.parse(xhr.responseText)) 

      }                                       
    }
}
// 设置请求  GET是请求方式(要传递的参数可以写在url后面)  url是网址  true是异步请求数据改为false就是同步
xhr.open("GET",url,true);
// 发送数据 也可以发送post请求要传递参数
xhr,send(null)

14.WebSockets

WebSockets 是一种先进的技术。它可以在用户的浏览器和服务器之间打开交互式通信会话。使用此API,您可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。

15.数据类型

基本数据类型:string、number、null、undefind、boolean、object、symbol(表示独一无二的值)
凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let a = symbol('111');
let b = symbol('111');
a==b   //false
a===b  //false

16.基本类型和引用类型的值

基本类型值是指简单的数据段
引用类型值指那些可能由多个值构成的对象,引用类型的值是存在内存中的对象。

引用类型值有:Object、Array、Regexp、Function、Date

17.js执行顺序

同步→异步→回调
异步分为微任务和宏任务,微任务优先级高于宏任务
promise是异步微任务
setTimeout是异步宏任务

18.箭头函数与普通函数的区别

  • 箭头函数不会创建自己的this
  • 箭头函数不能作为构造函数使用
  • 箭头函数没有原型prototype

19. object和array都是对象,有什么区别

  • 数组表示有序数据的集合,而对象表示无序数据的集合
  • 数组的数据没有”名称”(name),对象的数据有”名称”(name)
  • 原型链不同

VUE篇

vue的双向绑定原理

  • vue2.x的原理

    1. 当一个普通javascript对象传入vue实例作为data选项,vue将遍历此对象的所有property,并使用Object.defineProperty把这些property全部转化成getter/setter;

    2.这些getter/setter在内部让vue能够追踪依赖,在property被访问和修改时通知变更;

    1. 每个组件实例都对应一个watcher实例,它会在组件渲染过程中把“接触”过的数据property记录为依赖,之后当依赖项的setter触发时,会通知watcher,从而使他关联的组件重新渲染。

    由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。

  • vue3.x的原理
    改用Proxy替代Object.defineProperty
    因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

    判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。
    监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。

vue中key的作用

key的作用主要是为了高效的更新虚拟DOM

key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

vuex

状态管理器

  • state: vuex使用单一状态树,用一个对象就包含来全部的应用层级状态
  • mutation: 更改vuex中state的状态的唯一方法就是提交mutation
  • action: action提交的是mutation,而不是直接变更状态,action可以包含任意异步操作
  • getter: 相当于vue中的computed计算属性

vue的性能优化

尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载

预渲染
服务端渲染SSR

压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化

骨架屏
PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

vue-router

hash模式和history模式实现原理

  • hash 模式:
    #后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。

  • history 模式:
    history 模式的实现,主要是 HTML5 标准发布的两个 API,pushState 和 replaceState,这两个 API 可以在改变 url,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作

  • 区别:
    url 展示上,hash 模式有“#”,history 模式没有
    刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
    兼容性,hash 可以支持低版本浏览器和 IE。

vue与react的区别

  • Vue组件分为全局注册和局部注册,在react中都是通过import相应组件,然后模版中引用;
  • props是可以动态变化的,子组件也实时更新,在react中官方建议props要像纯函数那样,输入输出一致对应,而且不太建议通过props来更改视图;
  • 子组件一般要显示地调用props选项来声明它期待获得的数据。而在react中不必需,另两者都有props校验机制;
  • 每个Vue实例都实现了事件接口,方便父子组件通信,小型项目中不需要引入状态管理机制,而react必需自己实现;
  • 使用插槽分发内容,使得可以混合父组件的内容与子组件自己的模板;
  • 多了指令系统,让模版可以实现更丰富的功能,而React只能使用JSX语法;
  • Vue增加的语法糖computed和watch,而在React中需要自己写一套逻辑来实现;
  • react的思路是all in js,通过js来生成html,所以设计了jsx,还有通过js来操作css,社区的styled-component、jss等;而 vue是把html,css,js组合到一起,用各自的处理方式,vue有单文件组件,可以把html、css、js写到一个文件中,html提供了模板引擎来处理。
  • react做的事情很少,很多都交给社区去做,vue很多东西都是内置的,写起来确实方便一些, 比如 redux的combineReducer就对应vuex的modules, 比如reselect就对应vuex的getter和vue组件的computed, vuex的mutation是直接改变的原始数据,而redux的reducer是返回一个全新的state,所以redux结合immutable来优化性能,vue不需要。
  • react是整体的思路的就是函数式,所以推崇纯组件,数据不可变,单向数据流,当然需要双向的地方也可以做到,比如结合redux-form,组件的横向拆分一般是通过高阶组件。而vue是数据可变的,双向绑定,声明式的写法,vue组件的横向拆分很多情况下用mixin。

React

React 的工作原理

React 会创建一个虚拟 DOM(virtual DOM)。当一个组件中的状态改变时,React 首先会通过 “diffing” 算法来标记虚拟 DOM 中的改变,第二步是调节(reconciliation),会用 diff 的结果来更新 DOM。

react是单向数据流,react中属性是不允许更改的,状态是允许更改的。

react中组件不允许通过this.state这种方式直接更改组件的状态。自身设置的状态,可以通过setState来进行更改。(注意:React中setState是异步的,导致获取dom可能拿的还是之前的内容,所以我们需要在setState第二个参数(回调函数)中获取更新后的新的内容。)

React生命周期

  • 挂载:
    • 当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
      • constructor()
      • static getDerivedStateFromProps()
      • render()
      • componentDidMount()
  • 更新:
    • 当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
      • static getDerivedStateFromProps()
      • shouldComponentUpdate()
      • render()
      • getSnapshotBeforeUpdate()
      • componentDidUpdate()
  • 卸载:
    当组件从 DOM 中移除时会调用如下方法:
    • componentWillUnmount()
  • 错误处理:
    当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:
    • static getDerivedStateFromError()
    • componentDidCatch()

常用生命周期方法:

  • componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
  • componentDidUpdate() 当组件更新后,可以在此处对 DOM 进行操作。如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求。
  • componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

react事件

react的事件并没有绑定在真实的DOM节点上,而是通过事件代理,在最外层的document上对事件进行统一分发。

react的所有事件都挂载在document中
当真实dom触发后冒泡到document后才会对react事件进行处理
所以原生的事件会先执行
然后执行react合成事件
最后执行真正在document上挂载的事件

react组件通讯方式

  • 父组件向子组件:props
  • 子组件向父组件:props+回调
  • 兄弟组件:找到共同的父节点,通过上面两种方式传递数据
  • 跨层级:context
  • redux

setState是同步还是异步

在 合成事件 和 生命周期钩子(除 componentDidUpdate) 中,setState是"异步"的; 在 原生事件 和 setTimeout 中,setState是同步的

在 合成事件 和 生命周期钩子 中,setState更新队列时,存储的是 合并状态(Object.assign)。因此前面设置的 key 值会被后面所覆盖,最终只会执行一次更新;

本文使用 mdnice 排版