Fork me on GitHub

前端高频面试题收录

JavaScript

1. 深拷贝和浅拷贝(递归调用死循环问题如何解决) :star:

基本类型–名值都存储在栈内存中。
引用数据类型–存储的是地址(指针),数据存储在堆上(栈内存会提供一个引用的地址指向堆内存中的值)

基本类型赋值时,赋的是值,所以不存在深浅拷贝问题。

引用类型赋值时,复制的是原本变量的引用地址(指针),浅拷贝就是新值和旧值变量指向同一个内存地址,当地址中的值改变时,他们都会同时变化。

深拷贝就是实现新旧值互不影响,拷贝的过程中,独立地开辟了一个空间,这个对象指向这个地址,与原来的对象互不干扰。深拷贝也被称为值拷贝。

对象实现深拷贝有JSON.parse(JSON.stringify())Object.assign()。数组实现深拷贝有sliceconcat

用扩展运算符对数组或者对象进行拷贝时,只能扩展和深拷贝第一层的值,对于第二层及其以后的值,扩展运算符将不能对其进行打散扩展,也不能对其进行深拷贝,即拷贝后和拷贝前第二层中的对象或者数组仍然引用的是同一个地址,其中一方改变,另一方也跟着改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 实现深拷贝
export function deepClone(obj, hash = new WeakMap()) {
// 处理null或者undefined
if (obj === null) return obj;
// 处理日期类型
if (obj instanceof Date) return new Date(obj);
// 处理正则类型
if (obj instanceof RegExp) return new RegExp(obj);
// 普通值或函数不需要深拷贝
if (typeof obj !== "object") return obj;
// 对象进行深拷贝(解决循环引用)
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj);
for (let key in obj) {
// 检测属性是否为对象的自有属性
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}

JS 从零手写一个深拷贝

JSON.parse(JSON.stringify(obj)) 实现深拷贝存在的缺陷
  • 如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
  • 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
  • 如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
  • 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
  • JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的,则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
  • 如果对象中存在循环引用的情况也无法正确实现深拷贝。
JSON.stringify的参数

JSON.stringify(value[, replacer [, space]])

  • replacer:处理序列化中的每个属性的函数方法
  • space:指定缩进用的字符串,用于美化输出
手写一个JSON.stringify
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function myStringify(obj){
//忽略undefind和function 先置为undefined 组合的时候忽略
if(obj === undefined || typeof(obj) === 'function'){
return undefined;
}

//null或者NaN Infinity置为null
if(obj === null){
return null;
}

// 时间对象转为字符串
if(obj instanceof Date){
return `"${obj.toJSON()}"`
}

//regExp输出{}
if(obj instanceof RegExp){
return `{}`
}

//字符串 置为“spring” 布尔 数字原样输出
if(typeof(obj)!== 'object'){
return typeof(obj) === 'string' ? `"${obj}"`:obj
}

//数组
if(Array.isArray(obj)){
let arrStr = obj.map(item => `${myStringify(item)}` )
return `[${arrStr.join(',')}]`
}

//采用Object.getOwnPropertyNames直接过滤掉symbol
let keyNames = Object.getOwnPropertyNames(obj);
const arrObj = keyNames.map((item) => {
return `${myStringify(obj[item]) !== undefined && `"${item}":${myStringify(obj[item])}`}`
})
return `{${arrObj.join(',')}}`
}

2. 防抖和节流 :star:

防抖(debounce)是当事件被触发后,延迟n秒后再执行回调,如果在这n秒内事件又被触发,则重新计时(多次触发,只执行最后一次)

作用: 高频率触发的事件,在指定的单位时间内,只响应最后一次,如果在指定的时间内再次触发,则重新计算时间。

应用场景:按钮多次点击,resize 多次触发、搜索框输入查询等

1
2
3
4
5
6
7
8
9
10
11
12
13
// 防抖函数
function debounce (f, wait) {
let timer; // 创建一个标记用来存放定时器的返回值

return (...args) => {
// 每当用户输入的时候把前一个 setTimeout clear 掉
clearTimeout(timer)
// 然后又创建一个新的 setTimeout, 这样就能保证interval 间隔内如果时间持续触发,就不会执行 fn 函数
timer = setTimeout(() => {
f(...args)
}, wait)
}
}

节流(throttle),控制事件发生的频率,如控制为1s发生一次,甚至1分钟发生一次。类似(规定时间内,只触发一次)

作用: 高频率触发的事件,在指定的单位时间内,只响应第一次。

应用场景:监听滚动事件,播放事件等

1
2
3
4
5
6
7
8
9
10
11
// 节流函数
function throttle (f, wait) {
let timer
return (...args) => {
if (timer) { return }
timer = setTimeout(() => {
f(...args)
timer = null
}, wait)
}
}

3. async/await 和 Promise、JS事件循环机制 :star:

async/await 是参照 Generator 封装的一套异步处理方案,可以理解为 Generator 的语法糖。

async/await原理:将 Generator 函数和自动执行器,包装在一个函数里,不用手动调用Generator.next。一般返回一个promise对象。

写给自己的前端面试指南

4. for in 和 for of

  • for in:主要用来遍历对象(for keys in obj)

    遍历自身和继承的可枚举属性(延续原型链遍历出对象的原型属性)

    有什么问题:要使用 hasOwnProperty 判断,只处理自身的,不处理继承的

  • for of: ES6 新增的,遍历所有数据结构的统一的方法

    只要部署了 Symbol.iterator 属性,就被视为具有 iterator 接口,就可以用 for…of 循环遍历它的成员

    包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments 对象、DOM NodeList 对象)、Generator 对象,以及字符串

5. 函数式编程:柯里化(curry);组合函数(compose);管道函数(pipe)

高阶函数:一个函数接收的参数是一个函数,或者调用的返回值是一个函数。

  • 函数柯里化(curry):把接收多个参数的函数,转成接收单一参数的函数,它返回一个新的函数, 这个新函数去处理剩余的参数;
  • 函数组合(pipe&compose):函数组合可以让我们把多个函数组合成一个新的函数,然后在执行的过程中,我们可以把参数输入给第一个函数,当它执行完成以后会返回一个中间结果。并且把这个中间结果交接下一个函数去处理,当最后一个函数执行完毕之后,我们会把最终结果返回

6. slice和splice的区别

两者都是数组删除的方法

  • splice改变原数组,slice不改变原数组;
  • slice会返回一个新的数组,可用于截取数组;
  • splice返回被删除项组成的的新数组,除了可以删除之外,还可以替换,添加数组;
  • 传参:splice(index, number, …newItems),slice(start, end)

7. substr和substring的区别

两者的作用都是截取字符串的。

  • substr(start, length)是从起始索引开始提取指定长度的字符串
  • substring(start, stop)是提取字符串中两个指定索引之间的字符

8. let const var区别 :star:

和var的区别:

  • 重复声明:let和const不允许在相同作用域内,重复声明同一个变量
  • 变量提升:let不像var那样,会发生“变量提升”现象。(var会提升变量的声明到作用域的顶部)
  • 暂时性死区:只要作用域内存在let、const,它们所声明的变量或常量就会自动“绑定”这个区域,不再受外部作用域的影响
  • 块级作用域:var没有块级作用域(var 声明的变量的作用域只能是全局或者整个函数块的,作用域是它当前的执行上下文),let和const有块级作用域
  • 全局作用域中:let和const声明的变量内存空间不挂在于window上(let、const声明的全局变量在window对象上看不到,在script中形成了一个块级作用域,这样在全局就可以访问到),而var声明的变量是挂载到window上

const声明常量,声明之后常量的值不会改变。

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

  • 箭头函数是匿名函数,而普通函数可以匿名也可以不匿名
  • 本身没有 this,内部this是上层作用域决定
  • 没有原型,没有自己的arguments
  • 不能作为构造函数

10. Map和WeakMap的区别, Set

  • Map可以接受任何类型作为key,WeakMap只接受Object作为key;Map 通过两个数组分别存放键和值,容易导致内存泄漏。
  • WeakMap 对键是弱引用,有垃圾回收机制,不可枚举。
  • Set: Set 对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。(has、add、delete、size)

11. requestAnimationFrame

requestAnimationFrame 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题。

12. class继承的优点

简化了原型链继承的写法。

13. js实现私有属性和方法

在一个构造函数里面定义的function,只有父类可以访问的方法和属性,就是一个私有方法。

  • 基于编码规范约定实现方式: this._name

  • 基于闭包的实现方式:

    1
    2
    3
    4
    5
    6
    function Person(name){
    var _name = name;
    this.getName = function(){
    return _name;
    }
    }
  • 使用Proxy 拦截访问私有变量_prop

  • Es6:通过 # 的方式来标识私有属性和方法 #prop

14. js为什么是单线程

js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom。如果多线程同时操作dom会混乱(比如删除和监听点击事件同时被触发)。

js的异步:js主线程虽然是单线程,但是底层还是有其他线程的,比如事件执行线程等,异步任务就会放到事件队列中执行,执行完返回给主线程。

16. dom事件模型

事件冒泡和事件捕获。事件捕获 ——> 目标 ——> 冒泡

IE和DOM事件流的区别

事件冒泡(IE事件流):从目标元素冒泡到body(绑定事件attachEvent)

事件捕获: 从body到目标元素

DOM事件模型: 先捕获后冒泡(绑定事件addEventListener)

17. null,undefined的区别

null 表示空对象,转换数值会变成0;undefined表示“无”的原始值,转换数值是NaN

null表示一个对象被定义了,但存放了空指针,转换为数值时为0。
undefined表示声明的变量未初始化,转换为数值时为NAN。

18. 栈和堆的区别?

栈(stack):由程序自动分配释放,存放函数的参数值局部变量等;
堆(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由操作系统释放。

19. JS延迟加载的方式

  • defer:延迟脚本。立即下载(与html解析并行),但延迟执行(等整个页面都解析完再运行),按照脚本出现的先后顺序执行;
  • async: 异步脚本。下载(与html解析并行)完立即执行(可能会阻塞html解析),但不保证按照脚本出现的先后顺序执行;
  • 动态创建dom方式、
  • 使用jquery的getScript方法、
  • 使用setTimeout延迟方法、让js最后加载

PS:同步和异步:同步强调的是顺序性,按顺序执行

20. Symbol和bigint

symbol作为对象的属性是不能用for in 和Object.keys()来枚举的。JSON.stringify()将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外。
使用场景:

  • 使用Symbol来作为对象属性名,利用该特性,把一些不需要对外操作和访问的属性使用Symbol来定义
  • 使用Symbol定义类的私有属性和方法
  • 由于在 Number 与 BigInt 之间进行转换会损失精度,因而建议仅在值可能大于253 时使用 BigInt 类型,并且不在两种类型之间进行相互转换。

21. prefetch和preload

preload与prefetch同属于浏览器的Resource-Hints,用于辅助浏览器进行资源优化。prefetch通常翻译为预提取,preload则翻译为预加载。

  • preload和prefetch的本质都是预加载,即先加载、后执行,加载与执行解耦。
  • preload和prefetch不会阻塞页面的onload。
  • preload用来声明当前页面的关键资源,强制浏览器尽快加载;而prefetch用来声明将来可能用到的资源,在浏览器空闲时进行加载。
  • 关于preload和prefetch资源的缓存,它被存储在HTTP缓存(也就是disk cache)中,可以被现在或将来的任务使用;如果资源不能被缓存在HTTP缓存中,作为代替,它被放在内存缓存中直到被使用。

22. BOM(浏览器对象模型)和DOM(文档对象模型)

23. Ajax、Axios、Fetch有啥区别?

Ajax:是对XMLHttpRequest(XHR)的封装
Axios:是基于Promise对XHR对象的封装
Fetch:是window的一个方法,基于Promise,与XHR无关,不兼容IE

24. load、$(document).ready、DOMContentLoaded的区别?

$(document).ready、DOMContentLoaded:DOM树构建完毕,但还没有请求静态资源
load:静态资源请求完毕, 页面加载完成后

25. includes 比 indexOf好在哪

includes可以检测NaN,indexOf不能检测NaN,includes内部使用了Number.isNaN对NaN进行了匹配

Vue

1. 响应式原理 :star:

  • Vue2:通过Object.defineProperty()(可以控制一个对象属性的一些特有操作)来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
  • Vue3: 基于 Proxy 实现的,解决了 Vue2 响应式的缺陷。通过 new Proxy 代理了 obj 对象,然后通过 get、set 方法代理了对象的读取、修改和删除操作,从而实现了响应式的功能。

Vue2和Vue3原理区别

2. 虚拟 dom 原理 和 diff 算法 :star:

  • 虚拟DOM:它通过js对象模拟DOM中的节点,是对真实DOM的抽象,状态变更时,记录新树和旧树的差异,最后把差异更新到真正的dom中。(通过特定的render方法将其渲染成真实的DOM节点)
  • diff算法:一种通过同层的树节点进行比较的高效算法。通过JS层面的计算,返回一个patch对象,即补丁对象,在通过特定的操作解析patch对象,完成页面的重新渲染。其有两个特点:
    1. 只会在同层级节点进行比较,不会跨级。
    2. diff比较过程是从两侧向中间进行比较。

patch作用:更新 DOM 树,将两次渲染之间的 DOM 操作更新到树中。

虚拟dom原理:

  • 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
  • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  • 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

虚拟dom缺点:
⾸次渲染⼤量 DOM 时,由于多了⼀层虚拟 DOM 的计算,会⽐ innerHTML 插⼊慢

Vue2和Vue3原理区别

3. vue 组件传值 :star:

props/$emit、ref、$parent/$children、$attrs/$listeners、provide/inject、eventBus、vuex

  • 父子:props/$emit、ref、$attrs、$parent
  • 兄弟:$parent、$root、eventBus、vuex
  • 跨级:eventBus、vuex、provide/inject

几大框架知识点梳理

4. vue生命周期钩子函数为什么不能使用箭头函数

所有生命周期钩子的 this 上下文将自动绑定至实例中,因此你可以访问 data、computed 和 methods。这意味着你不应该使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。因为箭头函数绑定了父系上下文,所以 this 不会指向预期的组件实例,并且this.fetchTodos 将会是 undefined。

箭头函数自己没有定义 this 上下文,而是绑定到其父函数的上下文中。当你在 Vue 程序中使用箭头函数时,this 关键字并不会绑定到 Vue 实例,因此会引发错误。

5. nextTick 实现原理 :star:

在下次DOM更新循环结束之后执行的延迟回调。应用于需要在视图更新之后,基于新的视图进行操作。

Vue 在更新 DOM 时是异步执行的。Vue的异步更新策略:如果数据变化,视图不会立即更新,而是把组件更新函数保存在一个队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。

实现原理:nextTick方法主要是使用了事件循环机制,定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空队列:

  • Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,找到存在的就调用他childrenRef;如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。(优先微任务)
  • 先把传入的回调函数 cb 推入 回调队列 callbacks 数组,同时在接收第一个回调函数时,执行能力检测中对应的异步方法 timerFunc,在该方法中会使用当前浏览器支持的异步方法去异步执行 flushCallbacks,而 flushCallbacks 函数中会对 callbacks 进行遍历,并执行每一个回调函数。

nextTick的实现原理

6.如何衡量组件封装的好与不好

扩展性;如何设置扩展性比较好(预留插槽)

对于组件的设计,一般是一个组件能够完整的完成一件事情,或者一个完整的逻辑;另外组件的设计一般是讲究高内聚,低耦合等原则进行设计。一个组件的设计应该具备以下的内容:

  • 功能独立和唯一
  • 可复⽤的模块,完成既定功能
  • 有明确的接⼝规定
  • 有上下⽂依赖、外部依赖资源的定义
  • 可以独⽴发布

7. vue style scoped 属性作用是啥,如何是样式仅在当前模块生效

作用:实现组件的私有化,不对全局造成样式污染,表示当前 style 属性只属于当前模块
原理:打包之后,编译成特定样式,data-v-[hash],即 CSS 带属性选择器

8. Vue3 和 React的区别

Vue的优势:

  • 模板和渲染函数的弹性选择
  • 简单的语法及项目创建
  • 更快的渲染速度和更小的体积

React的优势:

  • 适用于大型应用和更好的可测试性
  • 同时适用于Web端和原生App

Vue与React的区别和优势对比

9. vue key 的作用

高效的更新虚拟 dom。

如果循环dom时没有key,在diff算法比较时(key相同且tag相同认为是相同元素)就可能判断出错,可能永远认为是相同节点,执行更新操作,导致DOM操作频繁,影响性能。

10. vue-router 实现原理 :star:

  • hash模式(浏览器的hashchange事件)
  • history模式(HTML5 History API)

Vue路由

11. v-model原理

v-model一般配合input框使用,实现双向数据绑定的效果,它是v-bind和v-on的语法糖,原理是通过v-bind将数据绑定给input框,再通过v-on:input,在input中的值改变时,通过$event可以获取到事件源对象 再通过target.value获取到input中更新后的值 将这个值再赋值给绑定的数据即可

12. 父子组件的周期执行顺序 :star:

  1. 初始化阶段时,先执行父组件的beforeCreate、created、beforeMount三个钩子函数,然后执行子组件的beforeCreate、created、beforeMount、mounted四个钩子函数,最后执行父组件的mounted钩子函数
  2. 更新阶段,先执行父组件的beforeUpdate,然后执行子组件的beforeUpdate,updated,最后执行父组件的updated
  3. 销毁阶段,先执行父组件的beforeDestroy,然后执行子组件的eforeDestroy,destroyed,最后执行父组件的destroyed

13. vite 和 webpack 区别

  1. 工具本身定位不同:webpack是底层的东西,vite则是更上层的工具。webpack是配置化,灵活度极高的工具,vite是开箱即用,使用更简单的工具
  2. 原理不同:webpack是bundle,自己实现了一套模块导入导出机制。vite是利用浏览器的esm能力,是unbundle

优缺点:
vite开箱即用,基于浏览器esm,使得热更新更加优秀。(vite主要使用插件进行扩展功能)

webpack更加灵活,api以及插件生态更加丰富,高可定制,兼容更多浏览器,例如ie11。

vite介绍

vite: 基于esbuild与Rollup,依靠浏览器自身ESM编译功能,实现的构建工具

vite的特点:

  • 冷启动快,不需要等待打包操作;
  • 热更新快(采用 unbundle 机制,所以 dev server在监听到文件发生变化以后,只需要通过ws通知浏览器去重新加载变化的文件,剩下的工作就交给浏览器做);
  • 按需编译,不再等待整个应用编译完成(Vite 只需要在浏览器请求源码时进行转换并按需提供源码)
  • 缺点:首屏和懒加载性能的下降

vite常用配置:plugins,server, resolve/alias, build(outDir, lib, rollupOptions)

vite的打包原理

Vite 原理是利用现代浏览器支持原生的 ESM 规范,配合 server 做拦截,把代码编译成浏览器支持的。通过劫持浏览器的请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再返回给浏览器。

1.将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。

  • 依赖 :一般是第三方依赖,比如组件库,这一部分使用 esbuild 来进行 依赖预构建, esbuild 使用的是 Go 进行编写,比 JavaScript 编写的打包器预构建依赖快很多
  • 源码 :浏览器不能直接执行的非js代码(.jsx、.css、.vue等),vite只在浏览器请求相关源码的时候进行转换,以提供ESM源码。

2.拦截浏览器对模块的请求并返回处理后的结果。(浏览器执行ESM的import时,会向dev server发起该模块的ajax请求,服务器对源码做简单处理后返回给浏览器。)

过程:
依赖预构建 -> 启动dev server -> 浏览器解析模块并根据顺序请求 -> 拦截浏览器请求并进行转换 -> 源码返回给浏览器

vite首屏加载优化:在启动时对某些资源进行预打包,尽量避免后续的动态打包(vite-plugin-optimize-persist)

vite的打包原理

14. vite为什么启动那么快(快速的冷启动和快速的热更新)

Vite 借助了浏览器对 ESM 规范的支持,采取了与 Webpack 完全不同的 unbundle 机制。

unbundle 机制:不需要做 bundle 操作,即不需要构建、分解 module graph,源文件之间的依赖关系完全通过浏览器对 ESM 规范的支持来解析。这就使得 dev server 在启动过程中只需做一些初始化的工作,剩下的完全由浏览器支持。

  • 模块之间的依赖关系的解析由浏览器实现
  • 文件的转换由 dev server 的 middlewares 实现并做缓存
  • 不对源文件做合并捆绑操作

unbundle 机制缺点:首屏慢,vite需要动态的解析依赖,并打包,引入。

Vite为什么快

15. vue keep-alive 缓存组件的策略

keep-alive是Vue提供的一个内置的抽象组件,被keep-alive组件包裹的内部组件,其状态将被缓存(缓存不活跃的组件实例而不是销毁他们,在组件切换过程中状态保存在内存中,防止重复渲染),该组件会新增两个生命周期函数activated和deactivated,每次进入都会被执行。

如何实现:主要是根据LRU策略缓存组件 VNode,最后在 render 时返回子组件的 VNode。

如何更新:缓存渲染过程会更新 keep-alive 插槽,重新再 render 一次,从缓存中读取之前的组件 VNode 实现状态缓存。(vnode中的componentInstance保存了组件实例,在patch过程中直接insert实例,避免重新渲染)。

在使用 keep-alive 时,可以添加 prop 属性 include、exclude、max 允许组件有条件的缓存。
$route.meta.keepAlive可以缓存页面。

原理:LRU(最近最少使用)策略,根据组件访问的时间,将很久未访问的组件从缓存中删除。组件首次渲染时,keep-alive 会将组件缓存起来。等到缓存渲染时,keep-alive 会更新插槽内容,之后 $forceUpdate 重新渲染。这样在 render 时就获取到最新的组件,如果命中缓存则从缓存中返回 VNode。

16. 组件渲染更新的过程

初次渲染:

  • 解析模板为render函数(在开发环境下用vue-loader完成)
  • 触发响应式,监听data属性(Object.freeze可以不做响应式监听)
  • 执行render函数(会触发用到的getter),生成vnode,并且进行patch(ele, vnode)

更新:

  • 修改data触发setter
  • 重新执行render函数,生成newVnode
  • 执行patch(vnode, newVnode),diff算法更新视图

异步渲染:汇总data修改,一次性更新视图,减少DOM操作优化性能

17. computed 和 watch 区别

computed一般是根据data数据返回计算之后的结果。
computed与methods区别:computed是惰性的,有缓存性。如果依赖值不变就不重新计算。
watch没有返回值,一般用于监听数据变化执行一些异步操作或DOM操作。

18. vuex的理解

vue的状态管理库。可以统一管理所有组件的状态。vue中尽量使用单向数据流,所以在多个组件共享一个状态,就很难保证单向。所以我们需要把共享状态抽离出来,以全局单例模式管理,使得状态管理更加结构化和可维护。比较适合用于中大型项目。

原理:vuex利用了vue的mixin机制,混合 beforeCreate 钩子 将store注入至vue组件实例上,并注册了 vuex store的引用属性 $store。
vuex的state是借助vue的响应式data实现的。getter是借助vue的计算属性computed特性实现的。

19. vue挂载过程发生了啥

  • 初始化:创建组件实例,初始化组件状态,创建响应式数据
  • 建立更新机制:执行组件渲染函数(patch将vnode转成DOM);同时渲染时会创建内部响应式数据和依赖关系

20. vue可以做哪些性能优化

路由懒加载、keep-alive缓存、对很大的组件可以用v-show(防止重复创建)、v-once(只变化一次就不改变)、长列表采用虚拟滚动、组件销毁时清空事件监听、图片懒加载、第三方组件按需引用、无状态的静态组件尽量不拆分,需要更新的大组件尽量拆分

21. Vue2为什么只能一个根元素

vdom是单根的树形结构,patch算法会从根元素开始遍历, 然后渲染组件。Vue3中增加了Fragment这个抽象节点,如果是多根元素,就会创建一个Fragment节点作为根节点包裹。

22. router-link和router-view

vue-routers会监听popstate事件。点击后页面不会刷新,用当前path和routers中的path匹配找到对应路由组件然后在router-view(组件内容渲染)渲染。
router-link(路由导航)默认生成a标签,点击后取消默认跳转,执行pushState重新匹配路由。

23. vue的data为什么是函数

为了保证组件的独立性和可复用性。

组件可能在多次使用时多次实例化,如果写成对象形式,所有组件会复用一个data(引用类型)。函数可以保证每个组件实例化都有独立的作用域。

new Vue生成的是根元素实例,所以不会重复使用,可以用对象data。

24. vue-loader用法

用来解析和转换vue文件,把script template style标签中的内容提取出来交给对应的loader处理。(调用预处理,允许热重载)

25. 如何解析template, 如何解析指令?

解析模板字符串,先通过正则等方式将其转换为AST语法树,然后经过一步优化,将其中的静态节点打上标签,最后将AST转换为render函数。模板 ——> 解析器(parser) ——> 代码生成器 ——> 渲染函数

26. vue本地代理怎么解决跨域

实际上就是把实际请求转到了本地开启的server里面去请求,就不存在什么浏览器同源安全问题了。

Vue模板解析原理

26. 导航守卫

全局:

  • beforeEach((to, from, next)=>{})(全局前置守卫,跳转前触发)
  • beforeResolve (路由解析之前 这个几乎不用)
  • afterEach((to, from)=>{})(全局后置守卫)
  • beforeEnter(写在路由里面的,进入这个路由时才会调用)

组件内:

  • beforeRouteEnter(不能访问 this,此时组件实例还未创建)
  • beforeRouteUpdate
  • beforeRouteLeave

27. mixins

生命周期顺序:mixin的beforeCreate > 父beforeCreate > mixin的created > 父created > mixin的beforeMount > 父beforeMount > 子beforeCreate > 子created > 子beforeMount > 子mounted > mixin的mounted >父mounted

  • 对于data定义属性,组件中定义属性覆盖mixins中同名字段
  • 对于created、mounted等生命周期函数,mixins中生命周期函数优先执行(执行顺序按mixins中顺序),再执行组件中生命周期函数
  • 对于methods中的同名方法,组件内的方法覆盖mixins中的方法

Css

1. z-index 无效问题

  • 父级元素溢出隐藏或者不显示:父元素设置了 overflow:hidden /display:none/ 等,那么子元素如果在父元素外部绝对定位,那么调节子元素 z-index 可能不会显示
  • 父级元素层级低,z-index 被覆盖
  • 没有设置定位(position)属性
  • 父级元素 position 属性为 relative
  • 含有浮动(float)属性
  • IE 不兼容

2. 水平垂直居中实现 :star:

  • 相对定位和绝对定位:absolute + margin | absolute + transform | absolute + top + left
  • flex布局(移动端推荐):flex + justify-content + align-items | flex + margin auto
  • table布局:table-cell + vertical-align:middle
  • grid布局:grid + justify-self + align-self;| place-items:center

link属于HTML标签,而@import完全是CSS提供的一种方式。
link引用的CSS会同时被加载,而@import引用的CSS会等到页面全部被下载完再被加载。

4. scss的混合和函数

混合:@mixin 和@include ,可以传参

函数:@function pxToRem($num) {}, 函数可以计算

5. rem和em基于什么取值

em 单位基于使用他们的元素的字体大小,可能受父元素字体大小影响。
rem 单位基于 html 元素的字体大小。(1rem等于html根元素设定的font-size的px值)

6. 三栏布局

  • flex布局
  • grid布局
  • float + width

7. line-height 1.5

设置数字时,继承的是数字的值。line-height的值会是本身字体大小乘以数字。
设置em时,继承的时父元素的字体大小乘以em之后的值,所以可能会带来不确定的结果。

8. display:none和visibility:hidden的区别

none: 让这个元素也是直接消失,会影响到布局问题。
hidden: 可以让元素消失,属于css样式,它只是让元素看不见,但本身的位置还在,如果对div进行hidden,那么div除了看不见,其他所有的样式都在(比如块元素样式)。

9. 优先级

内联 > id > class > 标签

浏览器与网络安全

从输入 url 到浏览器渲染经过了哪些流程 :star:

URL 解析 -> DNS 查询 -> TCP 连接 -> HTTP 请求 -> 服务器响应请求 -> 页面渲染

  • 首先浏览器在输入URL之后,会先解析URL,判断是否合法;
  • 合法的话会查看浏览器缓存,判断是否有缓存,如果有缓存,则显示;
  • 如果没有缓存,浏览器会向服务器发送HTTP协议,会进行DNS解析,获取IP地址;(通过ip寻址和arp,找到目标(服务器)地址)
  • 浏览器和服务器进行TCP连接,进行三次握手,建立tcp连接;
  • 握手成功之后,浏览器会向服务器发送http请求,请求数据包;
  • 服务器处理请求,并对请求做出响应;
  • 浏览器收到服务器响应,得到html代码;
  • 渲染页面:获取到服务器相应之后,浏览器会根据相应的content-type字段对响应字符串进行解析。能够解析并成功解析就显示,能够解析但解析错误就报错,不能解析就下载。

从服务器返回数据会经过哪些阶段

浏览器首先去找本地hosts文件,检查在该文件中是否有相应的域名、IP对应关系,如果有,则向其IP地址发送请求,如果没有就发送给DNS(域名服务器)进行解析,解析成对应的服务器IP地址 ——> 网络通信 ——> 浏览器拿到数据 ——> 四次挥手断开连接

css是否会阻塞加载html

css的加载是不会阻塞DOM的解析,但是会阻塞DOM的渲染, 会阻塞link后面js语句的执行。(当cssdom还没构建完成时,页面是不会渲染到浏览器界面的)。

为了防止html页面的重复渲染而降低性能,所以浏览器只会在加载的时候去解析dom树,然后等在css加载完成之后才进行dom的渲染以及执行后面的js语句。

http 和 网络攻击 :star:

HTTP和HTTPS协议

get和post请求方式的区别

  1. get重点在从服务器上获取资源;post重点在向服务器发送数据;
  2. get传输数据是通过URL请求,参数加在url上;post的参数放在请求实体中,这个过程对用户是不可见的;
  3. 受URL长度限制,get传输的数据是有限的;post可以传输大量数据,所以上传文件时只能用post方式;
  4. post安全性高(因为get参数放在url上可见);
  5. get方式只能支持ASCII字符,向服务器传的中文字符可能会乱码;post支持标准字符集,可以正确传递中文字符。

post一般没有大小限制,但是实际上post所能传递的数据量大小取决于服务器的设置和内存大小。

ajax发起请求的流程

创建请求对象new XMLHttpRequest()  ——> 创建HTTP请求 ——> 设置响应HTTP请求状态变化的函数 ——> 发送HTTP请求 ——> 监听 onreadystatechange 事件,获取请求状态码和响应码状态

前端性能优化 :star::star::star:

1. 首页白屏解决

缓存;骨架屏(用css占位置,当资源加载完成即可填充,减少页面的回流与重绘);减少包大小;解决阻塞;SSR;预渲染

2. 大数据量的问题处理

分页;分片;虚拟滚动;关键字远程搜索;异步加载

3. 跨域问题解决 :star:

跨域:浏览器同源策略的限制(协议,域名和端口),不允许非同源的 URL 之间进行资源的交互。

  • JSONP:通过script标签(没有同源策略限制)向服务器端发送一个get请求(src=url)(ajax是通过XmlHttpRequest动态获取数据,jsonp是通过添加script标签动态获取数据)

    优缺点:兼容性好,缺点是只支持 GET 请求,不支持 POST 请求。

  • iframe;

  • postMessage(允许来自一个文档的脚本可以传递文本消息到另一个文档里的脚本);

  • CORS:跨域资源共享, 设定http的 Access-Control-Allow-Origin 允许的值(分为简单请求和非简单请求)

  • Proxy;

  • WebSocket

4. 项目优化

  • 减少HTTP请求数量:减少图片使用,使用icon代替、合并静态资源,比如雪碧图、善用缓存;缓存接口请求;
  • 压缩图片:背景图使用webp格式
  • 源码优化:代码模块化、路由懒加载、使用keep-alive对组件缓存、图片懒加载(默认data-src渲染时src)、长列表优化(无限滚动、虚拟滚动)
  • 项目打包优化:开启Gzip压缩(借助 Compression-webpack-plugin来生成 .gz文件,nginx也需要同时配置)、修改配置文件去掉打包的sourcemap、组件的按需引入
  • 使用cdn的方式外部加载一些资源
  • 减少页面回流和重绘:预加载(preload)、预渲染(prerender)、或者增加骨架屏和loading
  • 使用浏览器控制台的performance查看渲染时间,使用lighthouse查看可优化项

cdn原理:内容分发网络,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。

实践

1. 主题色

css颜色变量、scss变量 + @mixin

使用scss处理多主题切换功能

2. 权限管理(菜单权限,角色权限,路由权限,页面内部权限) :star:

路由守卫、动态路由加载、自定义指令按钮

3. token 失效刷新如何实现的

现token无感刷新对于前端来说是一项常用的技术,其本质是为了优化用户体验,当token过期时不需要用户跳回登录页重新登录,而是当token失效时,进行拦截,发送刷新token的ajax,获取最新的token进行覆盖,让用户感受不到token已经过期。

4. webpack :star:

个性化配置:拆包;less,sass 处理;打包加速优化 ;图片资源压缩(image-webpack-loader)

性能优化:

  • 并行压缩,开启多进程打包(happyPack、pipeline);
  • dllplugin提升构建速度(可以将特定的类库提前打包然后引入。生成xx.dll.js文件,通过manifest.json引用);
  • splitchunk 拆包;
  • 混淆压缩;
  • cache,缓存中间产物,对性能优化作用最大,改善构建速度

深入探究Webpack原理

5. 脚手架,一般会改哪些配置

压缩的处理:插件的压缩处理,gzip 压缩,代码压缩

6. 浏览器兼容性问题 :star:

  1. 添加reset.scss(初始化全局样式,如*{margin:0px; padding:0px}),解决不同浏览器标签默认的内外间距不同
  2. 图片默认有间距,使用float:left
  3. 安装babel兼容ES6
  4. js事件获取target兼容ie, 获取getSelection兼容ie(IE下,even对象有srcElement属性,但是没有target属性;Firefox下,even对象有target属性,但是没有srcElement属性。
    1
    2
    3
    var ev = ev || window.event
    var w = document.documentElement.clientWidth || document.body.clientWidth
    var target = ev.srcElement || ev.target

解决方法:使用srcObj = event.srcElement ?event.srcElement : event.target;)
5. css不支持,css hack

浏览器兼容性问题

7. 图片上传预览

图片存在本地,通过 base64 转化

8. 单点登录实现

单点登录(SSO),指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统。多个系统,统一登陆。

实现原理:sso需要一个独立的认证中心,所有子系统都通过认证中心的登录入口进行登录,登录时带上自己的地址,子系统只接受认证中心的授权,授权通过令牌(token)实现,sso认证中心验证用户的用户名密码正确,创建全局会话和token,token作为参数发送给各个子系统,子系统拿到token,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。

单点登录的实现方案,一般就包含:Cookies,Session同步,分布式Session,目前的大型网站都是采用分布式Session的方式。

9. 第三方登录对接

前端对接第三方登录

10. 微信公众号,小程序的开发的区别,登陆的过程

公众号授权登录:先在公众号管理页设置网页授权,然后通过authorize(调用https://open.weixin.qq.com/connect/oauth2/authorize)获取的code

小程序登录:小程序端检测登录信息(wx.checkSession),然后调用wx.login,获取登录凭证(code),并调用接口,将code发送到第三方客户端,第三方服务器端调用接口,用code换取session_key和openid

绑定同一个微信开发平台账号下, 则同一用户,对同一个微信开放平台下的不同应用,unionid是相同的

11. PC端扫码实现

扫码登录是如何实现的

12. axios返回值

axios返回的是一个promise对象。

ajax是通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据。

13. 常见命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// vue-cli创建项目
vue create projectName

// vite创建vue-ts项目
yarn create vite projectName --template vue-ts

// vite创建项目
yarn create vite projectName --template vue

/* vite集成electron */
// 安装依赖和打包
npm install electron electron-builder -D
// 安装调试
npm install electron-devtools-installer -D
npm install vite-plugin-electron -D

14. 大文件上传分块和断点续传

前端负责分块,服务端负责整合。首先是选择上传的文件资源,通过FileList对象获取到相应的文件,而 File.prototype.slice 方法可以实现资源的分块。

前端可以基于 Promise.all 将这多个接口整合,上传完成在发送一个合并的请求,通知服务端进行合并。

断点续传:下载文件时,不必重头开始下载,而是从指定的位置继续下载。可以利用请求头:Range:bytes=5001-10000或者size传参。

15. chrome调试

  • Performance 查看性能: 火焰图中每个方块的宽度代表执行耗时
  • Memory 查看内存:
    • Snapshot:某个时间点的 堆内存快照
    • TimeLine:实时的按照 时间线 显示的内存分配情况
    • Sampling:采样 的方式收集内存分配情况

16. axios特性

Axios实现是:基于nodejs的http或者https模块来发起请求的。

  • 从浏览器创建XMLHttpRequests,从node.js创建http请求
  • 支持promise API
  • 拦截请求和相应,转换请求和相应数据,取消请求,自动转换JSON数据
  • 客户端支持防御XSRF

取消请求:AbortController, cancelToken
axios要点

17. 组件库的按需加载

  • elementUI : 使用babel插件是目前大多数组件库实现按需引入的方式,安装babel-plugin-component(可以实现样式的按需加载)

  • Vant: 使用tree-shaking: 输入格式commonjs规范是最常见的使用方式,umd一般用于cdn方式直接在页面引入,而esmodule就是用来实现Tree Shaking。(esmodule是静态编译的,也就是在编译阶段就能确定某个模块导出了什么,引入了什么,代码执行阶段不会改变,所以打包工具在打包的时候就能分析出哪个方法被使用了)(rollup也是)

    修改package.json,main是commonjs模块入口,module是esmodule模块,Vue CLI使用的是webpack,对应的我们需要在package.json文件里新增一个sideEffects字段(去掉副作用函数)。

18. echarts和antv区别

echarts是以图表为主, 而antv则是以图形出发。echarts相对于antv来说是比较成熟的一套图表库, 使用方便、图表种类多, 也是比较容易上手的, 遇到问题网上的解决方式也有很多,而antv是以数据可视化底层引擎,以数据驱动, 相对于echart更具有拓展性和灵活性。

-------------完结撒花 -------------

本文标题:前端高频面试题收录

文章作者:咕噜咕噜

发布时间:2022年08月16日

最后更新:2022年09月26日

原始链接:https://aartemida.github.io/2022/08/16/前端高频面试题收录/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。