常见面试题

2022-05-25 17:31:20
2024-04-14 17:27:47

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

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

浅拷贝

概念

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

方法

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

深拷贝

概念

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

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

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)、外部条件放到遍历的父级元素上,没有父级可以使用<template></template>。注意 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) 算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。(墨菲定律:越担心的事情越会发生)

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

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(整体代码)
setTimeout
setInterval
I/O
UI交互事件
postMessage
MessageChannel
setImmediate(Node.js 环境)

微任务

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也会放到微任务队列中。

揭晓答案

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
目录
暂无评论,欢迎留下你的评论

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

昵称
留言
赞赏金额