欢迎扫码,加作者微信

常见面试题

2022-05-26 01:31:20
2025-06-20 19:22:06

主要纪录面试过程中的面试题,对自己知识的查漏补缺

说一下深拷贝与浅拷贝以及他们的区别

浅拷贝

概念

概念: 对于字符串类型,浅拷贝是对值的复制,对于对象来说,浅拷贝是对对象地址的复制, 也就是拷贝的结果是两个对象指向同一个地址

方法

Object.assign或者(...)展开运算符

深拷贝

概念

概念: 深拷贝开辟一个新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

JSON.parse(JSON.stringify(object))或者递归

js 复制代码
let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

该方法也是有局限性:(1)会忽略 undefined(2)不能序列化函数(3)不能解决循环引用的对象

闭包

  • 闭包就是能够读取其他函数内部变量的函数

  • 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域

闭包的特性:

  • 函数内再嵌套函数
  • 内部函数可以引用外层的参数和变量
  • 参数和变量不会被垃圾回收机制回收

说说你对闭包的理解

使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念

  • 闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中

  • 闭包的另一个用处,是封装对象的私有属性和私有方法

  • 好处:能够实现封装和缓存等;

  • 坏处:就是消耗内存、不正当使用会造成内存溢出的问题

使用闭包的注意点

  • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露
  • 解决方法是,在退出函数之前,将不使用的局部变量全部删除

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

  • cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)

  • cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递

  • sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存

存储大小:

  • cookie数据大小不能超过4k
  • sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大

有期时间:

  • localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据
  • sessionStorage 数据在当前浏览器窗口关闭后自动删除
  • cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭

JS的基本数据类型和引用数据类型

  • 基本数据类型(6种):undefined、null、boolean、number、string、symbol
  • 引用数据类型(3种):object、array、function

说一下浏览器的缓存机制

浏览器缓存机制有两种,一种为强缓存,一种为协商缓存

  • 对于强缓存,浏览器在第一次请求的时候,会直接下载资源,然后缓存在本地,第二次请求的时候,直接使用缓存。

  • 对于协商缓存,第一次请求缓存且保存缓存标识与时间,重复请求向服务器发送缓存标识和最后缓存时间,服务端进行校验,如果失效则使用缓存
    协商缓存相关设置

Exprires:服务端的响应头,第一次请求的时候,告诉客户端,该资源什么时候会过期。Exprires的缺陷是必须保证服务端时间和客户端时间严格同步。
Cache-control:max-age:表示该资源多少时间后过期,解决了客户端和服务端时间必须同步的问题,
If-None-Match/ETag:缓存标识,对比缓存时使用它来标识一个缓存,第一次请求的时候,服务端会返回该标识给客户端,客户端在第二次请求的时候会带上该标识与服务端进行对比并返回If-None-Match标识是否表示匹配。
Last-modified/If-Modified-Since:第一次请求的时候服务端返回Last-modified表明请求的资源上次的修改时间,第二次请求的时候客户端带上请求头If-Modified-Since,表示资源上次的修改时间,服务端拿到这两个字段进行对比

Vue面试题

请详细说下你对vue生命周期的理解

总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后,Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是Vue的生命周期

各个生命周期的作用

生命周期 描述
beforeCreate 组件实例被创建之初,组件的属性生效之前
created 组件实例已经完全创建,属性也绑定,但真实dom还没有生成,$el还不可用
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用
mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子
beforeUpdate 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前
update 组件数据更新之后
activated keep-alive专属,组件被激活时调用
deactivated keep-alive专属,组件被销毁时调用
beforeDestroy 组件销毁前调用
destroyed 组件销毁后调用

Vue实现数据双向绑定的原理:Object.defineProperty()

  • vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue追踪依赖,在属性被访问和修改时通知变化。

  • vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。

Proxy 相比于 defineProperty 的优势

Object.defineProperty() 的问题主要有三个:

  • 不能监听数组的变化
  • 必须遍历对象的每个属性
  • 必须深层遍历嵌套的对象

Proxy 在 ES2015 规范中被正式加入,它有以下几个特点

针对对象:针对整个对象,而不是对象的某个属性,所以也就不需要对 keys 进行遍历。这解决了上述 Object.defineProperty() 第二个问题
支持数组:Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的。
除了上述两点之外,Proxy 还拥有以下优势:

Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富
Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法。

v-for和v-if为什么不建议同时用

vue2中会优先执行 v-for, 当 v-for 把所有内容全部遍历之后 , v-if 再对已经遍历的元素进行删除 , 造成了加载的浪费 , 所以应该尽量在执行 v-for 之前优先执行 v-if , 可以减少加载的压力。(在vue3中v-if的优先级高于v-for)
解决方案:
(1)、外部条件放到遍历的父级元素上,没有父级可以使用。注意 key 不能放 template 标签上。
(2)、在计算属性中先用内/外部条件处理数据,再遍历处理后的数据

说说Vue2.0和Vue3.0有什么区别

  • 重构响应式系统,使用Proxy替换Object.defineProperty,使用Proxy优势:

可直接监听数组类型的数据变化
监听的目标为对象本身,不需要像Object.defineProperty一样遍历每个属性,有一定的性能提升
可拦截apply、ownKeys、has等13种方法,而Object.defineProperty不行
直接实现对象属性的新增/删除

  • 新增Composition API,更好的逻辑复用和代码组织
  • 重构 Virtual DOM

模板编译时的优化,将一些静态节点编译成常量
slot优化,将slot编译为lazy函数,将slot的渲染的决定权交给子组件
模板中内联事件的提取并重用(原本每次渲染都重新生成内联函数)

  • 代码结构调整,更便于Tree shaking,使得体积更小
  • 使用Typescript替换Flow

介绍一下Vue中的Diff算法

在新老虚拟DOM对比时

  • 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换
  • 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
  • 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。 匹配时,找到相同的子节点,递归比较子节点
    在diff中,只对同层的子节点进行比较,放弃跨级的节点比较,使得时间复杂从O(n^3)降低值O(n),也就是说,只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。

15 说一说keep-alive实现原理

keep-alive组件接受三个属性参数:include、exclude、max

  • include 指定需要缓存的组件name集合,参数格式支持String, RegExp, Array。当为字符串的时候,多个组件名称以逗号隔开。

  • exclude 指定不需要缓存的组件name集合,参数格式和include一样。

  • max 指定最多可缓存组件的数量,超过数量删除第一个。参数格式支持String、Number。
    原理

  • keep-alive实例会缓存对应组件的VNode,如果命中缓存,直接从缓存对象返回对应VNode

LRU(Least recently used) 算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。(墨菲定律:越担心的事情越会发生)

关于宏任务/微任务,同步/异步的执行顺序的面试题

language 复制代码
async function promise1() {
    console.log("promise1  start")
    await promise2()
    console.log("promise1  end")
}
function promise2() {
    console.log("promise2")
}
setTimeout(function () {
    console.log("setTimeout")
}, 0)
console.log("script start")
promise1()
new Promise((resolve, reject) => {
    console.log("Promise")
    resolve()
}).then(function () {
    console.log("Promise then")
})
console.log("script end")

宏任务

language 复制代码
script(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)

微任务

language 复制代码
Promise.then
Object.observe(将要废弃)
MutaionObserver(新特性)
process.nextTick(Node.js 环境)

事件循环

  • js是单线程,一个线程拥有唯一一个时间循环,但任务队列可以有多个。
  • 任务队列又分为宏任务和微任务。
  • 来自不同任务源的任务会进入到不同的任务队列。(setTimeout与setInterval是同源的)
  • 事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始进入第一次循环,代码一行一行执行,执行过程中遇到宏任务,把宏任务加到宏任务队列中, 遇到微任务放到微任务队列中,当宏任务的函数调用栈执全部执行后,去看有没有微任务, 如果有,去执行微任务, 微任务全部执行完成后,循环再次从宏任务开始,这样循环。
  • 浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染,流程:宏任务->微任务->渲染->宏任务->微任务->渲染->...

promise, async/await

  • promise是同步的,它里面的代码会同步执行。
  • promise.then是微任务,promise.then里面的代码放到微任务队列中,等宏任务执行完成之后执行。
  • async/await 是同步语法,解决异步回调问题,promise.then.catch 链式调用,但也是基于回调函数的。
  • await会等待一个函数的执行结果,这个函数式同步的
  • await下面的代码相当于promise.then也会放到微任务队列中。

揭晓答案

language 复制代码
async function promise1() {
    console.log("promise1  start")
    await promise2()
    console.log("promise1  end")
}
function promise2() {
    console.log("promise2")
}
setTimeout(function () {
    console.log("setTimeout")
}, 0)
console.log("script start")
promise1()
new Promise((resolve, reject) => {
    console.log("Promise")
    resolve()
}).then(function () {
    console.log("Promise then")
})
console.log("script end")

------------------------------------
script start
promise1  start
promise2
Promise
script end
promise1  end
Promise then
setTimeout

h5性能优化

1、APP内嵌页面

APP预加载网页,点击的时候直接调用页面(缺点,造成app资源浪费,占用内存)
css 使用原子css

1、文件优化: 通过压缩CSS、JavaScript和HTML文件大小,减少网络传输时间。同时,将多个CSS和JavaScript文件合并成一个可以减少请求次数,加快页面加载速度。

2、图片优化:

使用图片压缩工具(如TinyPNG)来减小图像文件的大小,采用适当的压缩格式(如JPEG、WebP),并合理使用CSS Sprite或者Base64编码来减少对图片的请求次数,查看图片是否可以服用

3、资源缓存优化:

设置合适的缓存策略,利用浏览器缓存机制,尽量减少重复请求,提高页面加载速度。可以通过设置HTTP响应头中的Cache-Control和Expires来控制静态资源的缓存时间。

4、延迟加载:

对于非关键内容,如图片、广告等,可以使用懒加载技术,延迟加载这些资源,当用户滚动到它们所在的位置时再进行加载,减少首次加载的时间。

5、预加载:

对于可能会在后续页面中使用到的资源(如下一页的CSS、JavaScript等),可以通过预加载机制提前加载这些资源,以减少后续页面的加载时间。

6、DOM操作优化:

减少不必要的DOM操作,尽量使用批处理和缓存DOM查询结果来提高性能。避免频繁的重排和重绘操作,可以使用CSS3动画代替JavaScript动画。

7、使用Web Workers和Service Worker:

将一些耗时的计算或网络请求任务放到Web Workers中进行并行处理,利用Service Worker实现离线缓存、消息推送等功能,提高应用的响应速度和离线体验。

8、减少重定向和请求次数:

避免不必要的重定向和请求,合理使用缓存、本地存储等技术来减少服务器请求次数。

9、清理无用资源:

定期清理不再使用的资源,如未使用的JavaScript库、样式表和图片等,以减少应用的体积。

10、性能监测和优化:

使用工具对H5应用进行性能监测和分析,发现性能瓶颈,并针对性地进行优化,保持应用的高性能状态。

11、使用SSR服务端渲染

文章目录

运营需要亿点资金维持,您的支持,是小白龙创作的动力!!!

生成中...

扫码赞赏

感谢您的支持与鼓励

安全支付

支付赞赏

您的支持是我们前进的动力

留言
快捷金额
自定义金额
¥

安全保障

采用银行级加密技术,保障您的支付安全

暂无评论,欢迎留下你的评论
暂无评论,期待您的精彩留言!
Copyright © 2025 粤ICP备19043740号-1
🎉 今日第 1 位访客 📊 年访问量 0 💝 累计赞赏 1000+