前端面试题 | 常考题整理

本文为面试中出现的高频次考题,具体还是要看所有题。

目录

css

[1、☆介绍下 BFC 及其应用](#1、☆介绍下 BFC 及其应用)

3、☆浮动清除

17、☆说几个未知宽高元素水平垂直居中方法

js

[9、☆箭头函数与普通函数的区别是什么?构造函数可以使用 new 生成实例,那么箭头函数可以吗?为什么?](#9、☆箭头函数与普通函数的区别是什么?构造函数可以使用 new 生成实例,那么箭头函数可以吗?为什么?)

11、☆简述懒加载

[19、☆JavaScript 有几种类型的值](#19、☆JavaScript 有几种类型的值)

36、☆什么是闭包,为什么要用它?

[52、☆js 的事件循环是什么?](#52、☆js 的事件循环是什么?)

[60、☆什么是 Promise 对象,什么是 Promises/A+ 规范?](#60、☆什么是 Promise 对象,什么是 Promises/A+ 规范?)

64、☆谈谈对es6的理解

[65、☆Let 与 var 与 const 的区别](#65、☆Let 与 var 与 const 的区别)

[66、☆Promise 的理解](#66、☆Promise 的理解)

[70、☆for in 和 for of 的区别](#70、☆for in 和 for of 的区别)

[71、☆Promise.all && Promise.any && Promise.race三者有什么区别?使用场景是什么?](#71、☆Promise.all && Promise.any && Promise.race三者有什么区别?使用场景是什么?)

72、☆哪些操作会造成内存泄漏

[73、☆var、let 和 const 的区别](#73、☆var、let 和 const 的区别)

74、☆bind、call、apply的区别

vue

[☆Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?](#☆Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?)

[2、☆写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?](#2、☆写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?)

[5、☆Vue 的父组件和子组件生命周期钩子执行顺序是什么](#5、☆Vue 的父组件和子组件生命周期钩子执行顺序是什么)

[7、☆Vue 中 computed 和 watch 的差异?](#7、☆Vue 中 computed 和 watch 的差异?)

[8、☆Vue 双向数据绑定原理](#8、☆Vue 双向数据绑定原理)

[12、☆请问 v-if 和 v-show 有什么区别?](#12、☆请问 v-if 和 v-show 有什么区别?)

[14、☆nextTick 的使用?](#14、☆nextTick 的使用?)

[15、☆vue 组件中 data 为什么必须是函数?](#15、☆vue 组件中 data 为什么必须是函数?)

[18、☆vuex 有哪几种属性?](#18、☆vuex 有哪几种属性?)

[20、☆vue 的路由实现 Hash 模式和 History 模式? ](#20、☆vue 的路由实现 Hash 模式和 History 模式? )

[23. ☆使用 Object.defineProperty() 来进行数据劫持有什么缺点?](#23. ☆使用 Object.defineProperty() 来进行数据劫持有什么缺点?)

[24、☆什么是 Virtual DOM?为什么 Virtual DOM 比原生 DOM 快?](#24、☆什么是 Virtual DOM?为什么 Virtual DOM 比原生 DOM 快?)

[25、☆ 如何比较两个 DOM 树的差异?](#25、☆ 如何比较两个 DOM 树的差异?)

[27、☆Vue 的各个生命阶段是什么?](#27、☆Vue 的各个生命阶段是什么?)

[30. ☆vue 中 mixin 和 mixins 区别?](#30. ☆vue 中 mixin 和 mixins 区别?)

[31、☆vue 双向数据绑定原理](#31、☆vue 双向数据绑定原理)

[35. ☆面试官:Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?](#35. ☆面试官:Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?)

[39、☆面试官:Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?](#39、☆面试官:Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?)

[44、☆为什么 Vuex的mutation中不能做异步操作](#44、☆为什么 Vuex的mutation中不能做异步操作)

计算机基础、浏览器

16、☆垃圾回收原理浅析

18、☆谈一谈浏览器的缓存机制?

[27、请描述一下 cookies,sessionStorage,indexDB 和 localStorage 的区别?](#27、请描述一下 cookies,sessionStorage,indexDB 和 localStorage 的区别?)

工程化

[1、(腾讯)webpack 中 loader 和 plugin 的区别是什么?](#1、(腾讯)webpack 中 loader 和 plugin 的区别是什么?)

[2、介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面的](#2、介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面的)

[5. ☆谈谈你对 webpack 的看法](#5. ☆谈谈你对 webpack 的看法)


css
1、☆介绍下 BFC 及其应用

BFC块级格式化上下文,是页面盒模型中的一种 CSS 渲染模式,相当于一个独立的容器,里面的元素和外部的元素相互不影响。

创建 BFC 的方式有:

  1. html 根元素
  2. float 浮动
  3. 绝对定位
  4. overflow 不为 visible
  5. display 为表格布局或者弹性布局

BFC 主要的作用是:

1).清除浮动

2).防止同一 BFC 容器中的相邻元素间的外边距重叠问题

3、☆浮动清除

方法一:使用带 clear 属性的空元素在浮动元素后使用一个空元素如

<div class="clear"></div>,并在 CSS 中赋予.clear{clear:both;}属性即可清理浮动。

方法二:使用 CSS 的 overflow 属性给浮动元素的容器添加 overflow:hidden;或 overflow:auto;可以清除浮动

方法三: 给浮动的元素的容器添加浮动给浮动元素的容器也添加上浮动属性即可清除内部浮动,但是这样会使其整体浮动,影响布局,不推荐使用。

方法四:使用邻接元素处理什么都不做,给浮动元素后面的元素添加 clear 属性。

方法五:使用 CSS 的:after 伪元素 结合:after 伪元素,可以完美兼容当前主流的各大浏览器。 给浮动元素的容器添加一个 clearfix 的 class,然后给这个 class 添加一个:after 伪元素实现元素末尾添加一个看不见的块元素清理浮动。

17、☆说几个未知宽高元素水平垂直居中方法

标准回答

未知宽高元素水平垂直都居中的实现方法:

  1. 设置元素相对父级定位position:absolute;left:50%;right:50%,让自身平移自身高度50% transform: translate(-50%,-50%);,这种方式兼容性好,被广泛使用的一种方式

  2. 设置元素的父级为弹性盒子display:flex,设置父级和盒子内部子元素水平垂直都居中justify-content:center; align-items:center ,这种方式代码简洁,但是兼容性ie 11以上支持,由于目前ie版本都已经很高,很多网站现在也使用这种方式实现水平垂直居中

  3. 设置元素的父级为网格元素display: grid,设置父级和盒子内部子元素水平垂直都居中justify-content:center; align-items:center ,这种方式代码简介,但是兼容性ie 10以上支持

  4. 设置元素的父级为表格元素display: table-cell,其内部元素水平垂直都居中text-align: center;vertical-align: middle; ,设置子元素为行内块display: inline-block; ,这种方式兼容性较好


js
9、☆箭头函数与普通函数的区别是什么?构造函数可以使用 new 生成实例,那么箭头函数可以吗?为什么?

箭头函数是普通函数的简写,可以更优雅的定义一个函数,和普通函数相比,有 以下几点差异:

  1. 函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象;
  2. 不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替;
  3. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数;
  4. 不可以使用 new 命令,因为: A.没有自己的 this,无法调用 call、apply; B.没有 prototype 属性 ,而 new 命令在执行时需要将钩子函数的 prototype 赋值给新的对象的 proto
11、☆简述懒加载

懒加载指的是在长网页中延迟加载图像,是一种很好优化网页性能的方式。

懒加载的优点:

  1. 提升用户体验,加快首屏渲染速度;
  2. 减少无效资源的加载;
  3. 防止并发加载的资源过多会阻塞 js 的加载;
    懒加载的原理:

首先将页面上的图片的 src 属性设为空字符串,而图片的真实路径则设置在 data-original 属性中,当页面滚动的时候需要去监听 scroll 事件,在 scroll 事件的回调中,判断我们的懒加载的图片是否进入可视区域,如果图片在可视区内则将图片的 src 属性设置为 data-original 的值,这样就可以实现延迟加载。

19、☆JavaScript 有几种类型的值

原始数据类型(Undefined、Null、Boolean、Number、String)

Symbol(es6新增,Symbol 值通过Symbol函数生成,注意 Symbol 函数前不能使用new 命令,Symbol 值不能够被强制类型转换为数字 ,但可以被强制类型转换为布尔值

Bigint (es10新增,BigInt是一个内置对象,它提供了表示大于最大安全整数之外的方法, bigint 通常用于计算最大安全整数之外的数值)

引用数据类型(对象、数组和函数)

两种类型的区别 是:存储位置不同。原始数据类型直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。引用数据类型存储在堆中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

36、☆什么是闭包,为什么要用它?

闭包 是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

闭包有两个常用的用途 。闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量 。通过使用闭包,我们可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。函数的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中 ,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收

其实闭包的本质就是作用域链的一 个特殊的应用,只要了解了作用域链的创建过程,就能够理解闭包的实现原理。

从外部读取函数内部的变量、将创建的变量的值始终保持在内存中、封装对象的私有属性和私有方法

52、☆js 的事件循环是什么?

因为 js 是单线程运行的,在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到与当前执行栈中不同的另一个任务队列中等待执行。任务队列可以分为宏任务对列和微任务对列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。

当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。微任务包括了 promise 的回调、node 中的 process.nextTick 、对 Dom 变化监听的 MutationObserver。 宏任务包括了 script 脚本的执行、setTimeout ,setInterval ,setImmediate 一类的定 时事件,还有如 I/O 操作、UI 渲染等。

60、☆什么是 Promise 对象,什么是 Promises/A+ 规范?

Promise 对象是异步编程的一种解决方案,最早由社区提出。Promises/A+ 规范是 JavaScript Promise 的标准,规定了一个 Promise 所必须具有的特性。

Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是 pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者 rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。状态的改变是通过 resolve() 和 reject() 函数来实现的, 我们可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。

64、☆谈谈对es6的理解

1.新增模板字符串(为JavaScript提供了简单的字符串插值功能)

2.箭头函数

3.for-of (用来遍历数据----例如数组中的值)

4.argument对象可被不定参数和默认参数完美代替

5.ES6将对象纳入规范,提供了原生的Promise对象

6.增加了let和const命令,用来声明变量

7.增加了块级作用域

(let命令实际就增加了块级作用域)

8.还有就是引入module模块的概念

注意:这个要结合promise、箭头函数和普通函数的区别、let,const,val的区别这几个问题来看,一般问了es6的新特性后,会追问这几个问题

65、☆Let 与 var 与 const 的区别

Var 声明的变量会挂载在 window 上,而 let 和 const 声明的变量不会 Var 声明的变量存在变量提升,let 和 const 不存在变量提升同一作用域下 var 可以声明同名变量,let和 const、不可以 Let 和 const 声明会形成块级作用域 Let 暂存死区 Const 一旦声明必须赋值,不能用 null 占位,声明后不能再修改,如果声明的是复合类型数据,可以修改属性

66、☆Promise 的理解

Promise 是一种解决异步编程的方案,相比回调函数和事件更合理和更 强大。

从语法上讲,promise 是一个对象,从它可以获取异步操作的消息;

promise 有三种状态:pending 初始状态也叫等待状态,fulfiled 成功状态,rejected 失败状态;状态一旦改变,就不会再变。创造 promise 实例后,它会立即执行。

Promise 的两个特点 1、Promise 对象的状态不受外界影响 2、Promise 的状态一旦改变,就不会再变,任何时候都可以得到这个结 果,状态不可以逆,

Promise 的三个缺点

  1. 无法取消 Promise,一旦新建它就会立即执行,无法中途取消
  2. 如果不设置回调函数,Promise 内部抛出的错误,不会反映到外部

当处于 pending(等待)状态时,无法得知目前进展到哪一个阶段, 是刚刚开始还是即将完成

70、☆for in 和 for of 的区别

1、for...in 循环:只能获得对象的键名,不能获得键值

for...of 循环:允许遍历获得键值

2、对于普通对象,没有部署原生的 iterator 接口,直接使用 for...of 会报错

3、for...in 循环不仅遍历数字键名,还会遍历手动添加的其它键,甚至包括原型链上的键。for...of 则不会这样

71、☆Promise.all && Promise.any && Promise.race三者有什么区别?使用场景是什么?

promise.all(Iterator) **:**接收一个可迭代对象,如数组,传入promise状态全为resolve状态,则返回成功状态。否则只要存在一个reject,则返回reject状态。

使用场景:

1.前端发起多个请求并根据请求顺序获取和使用数据;

2.合并多个请求结果并处理错误
**promise.any(Iterator):**接收一个可迭代对象,如数组,传入的promise状态只要有一个为resolve,整个则返回成功状态。全为reject,即返回reject状态。

使用场景:从最快的服务器检索资源,如果存在多台服务器,从最快的一台服务器获取资源。

promise.race(Iterator): 接收一个可迭代对象,如数组,运行多个promise。其中哪个最先执行完成,则返回其状态(无论成功与否)。

使用场景:请求超时提醒。

72、☆哪些操作会造成内存泄漏

1、意外的全局变量引起的内存泄漏

2、闭包引起的内存泄漏

3、没有清理的DOM元素引用

4、被遗忘的定时器或者回调

5、子元素存在引用引起的内存泄漏
解决方法

1.减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收

2.注意程序逻辑,避免"死循环"之类的

3.避免创建过多的对象

73、☆var、let 和 const 的区别

var声明的变量具有变量提升的特性,在javascript的创建阶段过程中,会对声明的变量与函数进行收集,提升到作用域的顶部。

其中有两个细节:

1.只有声明被提升(变量会初始化为undefined)

2.当变量名和函数名冲突时,函数会被优先提升

ES6 新增了 let const 关键字:

let用于声明变量,用法与var类似

const 用于声明常量:

与变量不同,常量是一个恒定的值,只读,不可修改

常量在定义时必须进行初始化赋值
相同特性:

在相同作用域内,无法对同一个变量/常量进行重复声明

存在暂时性死区

会形成块级作用域

不会在全局声明时(在最顶层作用域)创建window对象的属性

74、☆bind、call、apply的区别

call、apply和bind区别:

相同点:

作用相同,都是动态修改this指向;都不会修改原先函数的this指向。
异同点:

(1)执行方式不同:

call和apply是改变后页面加载之后就立即执行,是同步代码。

bind是异步代码,改变后不会立即执行;而是返回一个新的函数。

(2)传参方式不同:

call和bind传参是一个一个逐一传入,不能使用剩余参数的方式传参。

apply可以使用数组的方式传入的,只要是数组方式就可以使用剩余参数的方式传入

(3)修改this的性质不同:

call、apply只是临时的修改一次,也就是call和apply方法的那一次;当再次调用原函数的时候,它的指向还是原来的指向

bind是永久修改函数this指向,但是它修改的不是原来的函数;而是返回一个修改过后新的函数,此函数的this永远被改变了,绑定了就修改不了。


vue
☆Vue 的响应式原理中 Object.defineProperty 有什么缺陷?为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?

Vue2.0

基于Object.defineProperty,不具备监听数组的能力,需要重新定义数组的原型来达到响应式。

Object.defineProperty 无法检测到对象属性的添加和删除。

由于Vue会在初始化实例时对属性执行getter/setter转化,所有属性必须在data对象上存在才能让Vue将它转换为响应式。

深度监听需要一次性递归,对性能影响比较大。
Vue3.0

基于Proxy和Reflect,可以原生监听数组,可以监听对象属性的添加和删除。

不需要一次性遍历data的属性,可以显著提高性能。

因为Proxy是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11 。

2、☆写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?

vue 和 react 都是采用 diff 算法来对比新旧虚拟节点,从而更新节点。在 vue 的 diff 函数交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的 key 去对比旧节点数组中的 key,从而找到相应旧节点。如果没有找到就认为是一个新增节点。而如果没有 key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个 map 映射, 另一种是遍历查找。相比而言,map 映射的速度更快。

5、☆Vue 的父组件和子组件生命周期钩子执行顺序是什么
  1. 加载渲染过程:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted;

  2. 子组件更新过程:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated;

  3. 父组件更新过程:父 beforeUpdate -> 父 updated;

  4. 销毁过程:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed;

7、☆Vue 中 computed 和 watch 的差异?
  1. computed 是计算一个新的属性,并将该属性挂载到 Vue 实例上,而 watch 是监听已经存在且已挂载到 Vue 实例上的数据,所以用 watch 同样可以监听 computed 计算属性的变化;

  2. computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 值,才会计算新的值。而 watch 则是当数据发送变化便会调用执行函数;

  3. 从使用场景上来说,computed 适用一个数据被多个数据影响,而 watch 使 用一个数据影响多个数据。

8、☆Vue 双向数据绑定原理

vue 通过双向数据绑定,来实现了 View 和 Model 的同步更新。vue 的双向数据绑定主要是通过数据劫持和发布订阅者模式来实现的。

首先我们通过 Object.defineProperty() 方法来对 Model 数据各个属性添加访问器属性,以此来实现数据的劫持,因此当 Model 中的数据发生变化的时候,我们可以通过配置的 setter 和 getter 方法来实现对 View 层数据更新的通知。对于文本节点的更新,我们使用了发布订阅者模式,属性作为一个主题,我们为这个节点设置一个订阅者对象,将这个订阅者对象加入这个属性主题的订阅者列表中。当 Model 层数据发生改变的时候,Model 作为发布者向主题发出通知,主题收到通知再向它的所有订阅者推送,订阅者收到通知后更改自己的数据。

12、☆请问 v-if 和 v-show 有什么区别?

共同点:都是动态显示 DOM 元素

区别点:

手段

v-if 是动态的向 DOM 树内添加或者删除 DOM 元素

v-show 是通过设置 DOM 元素的 display 样式属性控制显隐

编译过程

v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

v-show 只是简单的基于 css 切换

编译条件

v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开 始局部编译态的

v-show 是在任何条件下都被编译,然后被缓存,而且 DOM 元素保留

性能消耗

v-if 有更高的切换消耗

v-show 有更高的初始渲染消耗

使用场景

v-if 适合运营条件不大可能改变

v-show 适合频繁切换

14、☆$nextTick 的使用?

this.$nextTick()将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它, 然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上

this.$nextTick()在页面交互,尤其是从后台获取数据后重新生成 DOM 对象之后的操作有很大的优势

15、☆vue 组件中 data 为什么必须是函数?

Object 是引用数据类型,如果不用 function 返回,每个组件的 data 都是内存的同一个地址,一个数据改变了其他也改变了

javascipt 只有函数构成作用域,data 是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响

在创建或注册模板的时候传入一个 data 属性作为用来绑定的数据。但是在组件中,data 必须是一个函数,因为每一个 vue 组件都是一个 vue 实例,通过 new Vue() 实例化,引用同一个对象,如果 data 直接是一个对象的话,那么一旦修改其中一个组件的数据,其他组件相同数据就会被改变,而 data 是函数的话,每个 vue 组件的 data 都因为函数有了自己的作用域,互不干扰

18、☆vuex 有哪几种属性?

分别是 State、 Getter、Mutation 、Action、 Module

state:Vuex 使用单一状态树,即每个应用将仅仅包含一个 store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据

mutations:mutations 定义的方法动态修改 Vuex 的 store 中的状态或数据

getters:类似 vue 的计算属性,主要用来过滤一些数据

action:actions 可以理解为通过将 mutations 里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action

Module:项目特别复杂的时候,可以让每一个模块拥有自己的 state、mutation、action、 getters,使得结构非常清晰,方便管理

20、☆vue 的路由实现 Hash 模式和 History 模式? 

hash 模式:在浏览器中符号"#",#以及#后面的字符称之为 hash,用

window.location.hash 读取。

特点:hash 虽然在 URL 中,但不被包括在 HTTP 请求中;用来指导浏览器动作,对服务端安全无用,hash 不会重加载页面

 history 式:history 采用 HTML5 的新特性;且提供了两个新方法:pushState(),

replaceState()可以对浏览器历史记录栈进行修改,以及 popState 事件的监听到状态变更

23. ☆使用 Object.defineProperty() 来进行数据劫持有什么缺点?

有一些对属性的操作,使用这种方法无法拦截,比如说通过下标方式修改数组数据或者给对象新增属性,vue 内部通过重写函数解决了这个问题。在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用 Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为这是 ES6 的语法。

24、☆什么是 Virtual DOM?为什么 Virtual DOM 比原生 DOM 快?

我对 Virtual DOM 的理解是,

首先对我们将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。

然后我们将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。当页面的状态发生改变,我们需要对页面的 DOM 的结构进行调整的时候,我们首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。 我认为 Virtual DOM 这种方法对于我们需要有大量的 DOM 操作的时候,能够很好的提高我们的操作效率,通过在操作前确定需要做的最小修改,尽可能的减少 DOM 操作带来的重流和重绘的影响。其实 Virtual DOM 并不一定比我们真实的操作 DOM 要快,这种方法的目的是为了提高我们开发时的可维护性,在任意的情况下,都能保证一个尽量小的性能消耗去进行操作。

25、☆ 如何比较两个 DOM 树的差异?

两个树的完全 diff 算法的时间复杂度为 O(n^3) ,但是在前端中,我们很少会跨层级的移动元素,所以我们只需要比较同一层级的元素进行比较,这样就可以将算法的时间复杂度降低为 O(n)。

算法首先会对新旧两棵树进行一个深度优先的遍历,这样每个节点都会有一个序号。在深度遍历的时候,每遍历到一个节点,我们就将这个节点和新的树中的节点进行比较,如果有差异,则将这个差异记录到一个对象中。 在对列表元素进行对比的时候,由于 TagName 是重复的,所以我们不能使用这个来对比。我们需要给每一个子节点加上一个 key,列表对比的时候使用 key 来进行比较,这样我们才能够复用老的 DOM 树上的节点。

27、☆Vue 的各个生命阶段是什么?

Vue 一共有 8 个生命阶段,分别是创建前后、加载前后、更新前后、销毁前后,每个阶段对应了一个生命周期的钩子函数。

  1. beforeCreate 钩子函数,在实例初始化之后,在数据监听和事件配置之前触发。因此在 这个事件中我们是获取不到 data 数据的。
  2. created 钩子函数,在实例创建完成后触发,此时可以访问 data、methods 等属性。但这个时候组件还没有被挂载到页面中去,所以这个时候访问不到 $el 属性。一般我们可以在这个函数中进行一些页面初始化的工作,比如通过 ajax 请求数据来对页面进行初始化。
  3. beforeMount 钩子函数,在组件被挂载到页面之前触发。在 beforeMount 之前,会找到对应的 template,并编译成 render 函数。
  4. mounted 钩子函数,在组件挂载到页面之后触发。此时可以通过 DOM API 获取到页面中的 DOM 元素。
  5. beforeUpdate 钩子函数,在响应式数据更新时触发,发生在虚拟 DOM 重新渲染和打补丁之前,这个时候我们可以对可能会被移除的元素做一些操作,比如移除事件监听器。
  6. updated 钩子函数,虚拟 DOM 重新渲染和打补丁之后调用。
  7. beforeDestroy 钩子函数,在实例销毁之前调用。一般在这一步我们可以销毁定时器、解绑全局事件等。
  8. destroyed 钩子函数,在实例销毁之后调用,调用后,Vue 实例中的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

当我们使用 keep-alive 的时候,还有两个钩子函数,分别是 activated 和 deactivated 。 用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

30. ☆vue 中 mixin 和 mixins 区别?

mixin 用于全局混入,会影响到每个组件实例。

mixins 应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉加载数据这种逻辑等等。另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并

31、☆vue 双向数据绑定原理

实现原理

vue双向数据绑定,其核心是 Object.defineProperty()方法。

通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。就必须要实现以下几点:

1、通过数据监听器Observer,来监听数据对象的所有属性,改变时可以拿到最新数据并通知订阅者

2、通过指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模版替换数据,并绑定相应的更新函数

3、通过Watcher,作为连接Observer和Compile的桥梁,能订阅并接收每个属性变动的通知,执行指令绑定的相应回调函数,来更新视图

Object.defineProperty(obj, prop, descriptor) ,这个语法内有三个参数,分别为 obj (要定义其上属性的对象) prop (要定义或修改的属性) descriptor (具体的改变方法)

35. ☆面试官:Vue3.0 所采用的 Composition Api 与 Vue2.x 使用的 Options Api 有什么不同?

小结

  • 在逻辑组织和逻辑复用方面,Composition API是优于Options API
  • 因为Composition API几乎是函数,会有更好的类型推断。
  • Composition API对 tree-shaking 友好,代码也更容易压缩
  • Composition API中见不到this的使用,减少了this指向不明的情况
  • 如果是小型组件,可以继续使用Options API,也是十分友好的

细节看全文

39、☆面试官:Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

Object.defineProperty

proxy

三、总结

44、☆为什么 Vuex的mutation中不能做异步操作

Vuex中状态更新的途径是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。


计算机基础、浏览器

9、☆从输入 URL 到页面加载的全过程

  1. 浏览器获取用户输入,等待 url 输入完毕,触发 enter 事件;

  2. 解析 URL,分析协议头,再分析主机名是域名还是 IP 地址;

  3. 如果主机名是域名的话,则发送一个 DNS 查询请求到 DNS 服务器,获得主机 IP 地址;

  4. 使用DNS获取到主机IP地址后,向目的地址发送一个请求,并且在网络套接字上自动添加端口信息;

  5. 等待服务器响应结果;

  6. 将响应结果经浏览器引擎解析后得到 Render tree,浏览器将 Render tree 进行渲染后显示在显示器中,用户此时可以看到页面被渲染。

16、☆垃圾回收原理浅析

1 、标记清除(mark and sweep

大部分浏览器以此方式进行垃圾回收 ,当变量进入执行环境 的时候,垃圾回收器将其标记为**"进入环境"** ,当变量离开环境 的时候将其标记为**"离开环境"** ,在离开环境之后还有的变量则是需要被删除的变量**。标记方式不定**,可以是某个特殊位的反转或维护一个列表等。

垃圾收集器给内存中的所有变量都加上标记 ,然后去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上的标记的变量即为需要回收的变量,因为环境中的变量已经无法访问到这些变量

2 、引用计数

另一种不太常见的垃圾回收策略是引用计数 。引用计数的含义是跟踪记录每个值被引用的次数 。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1 。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。

18、☆谈一谈浏览器的缓存机制?

强缓存:

不会向服务器发送请求,直接从缓存中读取资源,请求返回200的状态码,Expires 和 Cache-Control。

1.Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

2.Cache-Control

Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:

3.Expires 和Cache-Control 两者对比

其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control 优先级高于Expires

协商缓存

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

1.Last-Modified 和If-Modified-Since

浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200。

但是 Last-Modified 存在一些弊端:

如果本地打开缓存文件,即使没有对文件进行修改

只能以秒计时,如果在不可感知的时间内修改完成文件

2.ETag 和If-None-Match

Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

3. 两者之间对比:

首先在精确度上,Etag要优于Last-Modified。

第二在性能上,Etag要逊于Last-Modified

第三在优先级上,服务器校验优先考虑Etag

27、请描述一下 cookies,sessionStorage,indexDB 和 localStorage 的区别?

浏览器端常用的存储技术是 cookie 、localStorage 和 sessionStorage。

cookie 其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。

sessionStorage 是 html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源 页面所访问共享。

localStorage 也是 html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者 更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享。

IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。


工程化
1、(腾讯)webpack 中 loader 和 plugin 的区别是什么?

loader:loader 是一个转换器,将 A 文件进行编译成 B 文件,属于单纯的文件转换过程;

plugin:plugin 是一个扩展器,它丰富了 webpack 本身,针对是 loader 结束后,webpack 打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行广泛的任务。

2、介绍下 webpack 热更新原理,是如何做到在不刷新浏览器的前提下更新页面的
  1. 当修改了一个或多个文件;
  2. 文件系统接收更改并通知 webpack;
  3. webpack 重新编译构建一个或多个模块,并通知 HMR 服务器进行更新;
  4. HMR Server 使用 Websocket 通知 HMR runtime 需要更新,HMR runtime 通过 HTTP 请求更新 jsonp;
  5. HMR runtime 替换更新中的模块,如果确定这些模块无法更新,则触发整 个页面刷新;
5. ☆谈谈你对 webpack 的看法

我认为 webpack 的主要原理是,它将所有的资源都看成是一个模块,并且把页面逻辑当作一个整体,通过一个给定的入口文件,webpack 从这个文件开始,找到所有的依赖文件,将各个依赖文件模块通过 loader 和 plugins 处理后,然后打包在一起,最后输出一个浏览器可识别的 JS 文件。

Webpack 具有四个核心的概念:

Entry 是 webpack 的入口起点,它指示 webpack 应该从哪个模块开始着手,来作为其构建内部依赖图的开始。

Output 属性告诉 webpack 在哪里输出它所创建的打包文件,也可指定打包文件的名称,默认位置为 ./dist。

loader 可以理解为 webpack 的编译器,它使得 webpack 可以处理一些非 JavaScript 文件。在对 loader 进行配置的时候,test 属性,标志有哪些后缀的文件应该被处理,是一个正则表达式。

use 属性,指定 test 类型的文件应该使用哪个 loader 进行预处理。常用的 loader 有 css-loader、style-loader 等。插件可以用于执行范围更广的任务,包括打包、优化、压缩、搭建服务器等等,要使用一个插件,一般是先使用 npm 包管理器进行安装,然后在配置文件中引入,最后将其实例化后传递给 plugins 数组属性。

使用 webpack 的确能够提供我们对于项目的管理,但是它的缺点就是调试和配置起来太麻烦了。但现在 webpack4.0 的免配置一定程度上解决了这个问题。但是我感觉就是对我来说,就是一个黑盒,很多时候出现了问题,没有办法很好的定位。

相关推荐
web1350858863518 分钟前
前端node.js
前端·node.js·vim
m0_5127446419 分钟前
极客大挑战2024-web-wp(详细)
android·前端
若川28 分钟前
Taro 源码揭秘:10. Taro 到底是怎样转换成小程序文件的?
前端·javascript·react.js
潜意识起点43 分钟前
精通 CSS 阴影效果:从基础到高级应用
前端·css
奋斗吧程序媛1 小时前
删除VSCode上 origin/分支名,但GitLab上实际上不存在的分支
前端·vscode
IT女孩儿1 小时前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748256563 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@3 小时前
HTML5适配手机
前端·html·html5
@解忧杂货铺5 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
F-2H7 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++