面试题必背
SPA首屏加载速度慢的怎么解决?
SPA 首屏加载速度慢的问题,可以从以下几个方面入手:
- 优化代码:对于代码量过大或者一些不必要的代码可以进行清理和压缩,减少首屏加载时的请求次数和文件大小,进而提高加载速度。
- 优化图片:对于图片过大的文件可以使用相应的工具进行压缩处理,以减少图片的大小,提高加载速度。
- 按需加载:可以将页面分为多个模块,并在需要使用时再进行加载,以减少首屏加载时的请求数量和文件大小。
- CDN 加速:可以将静态资源放在 CDN 上,利用 CDN 的缓存和分发能力,提供更快的访问速度。
- 服务端渲染:使用服务端渲染技术,提前生成好 HTML 页面,可以提高首屏加载速度,减少浏览器的渲染时间。
- 骨架屏:使用骨架屏技术在加载时先显示页面框架,再逐步填充内容,提高用户体验。
- 第三方库优化:如果使用了第三方库,可以选择按需加载或者使用相应的替代方案,减少不必要的代码量和请求次数。 综上,对于 SPA 首屏加载速度慢的问题,可以从上述方面入手进行优化,提高用户体验。
Vue组件之间的通信方式都有哪些?
二、组件间通信的分类
组件间通信的分类可以分成以下
- 父子组件之间的通信
- 兄弟组件之间的通信
- 祖孙与后代组件之间的通信
- 非关系组件间之间的通信
三、组件间通信的方案
整理vue
中8种常规的通信方案
- 通过 props 传递
- 通过 $emit 触发自定义事件
- 使用 ref
- EventBus
- $parent 或$root
- attrs 与 listeners
- Provide 与 Inject
- Vuex
Vue 组件之间的通信方式主要有以下几种:
- 父子组件通信:通过 props 向子组件传递数据,子组件通过事件向父组件发送数据。这是 Vue 组件之间通信最常用的方式。
- 兄弟组件通信:可以通过一个空的 Vue 实例来充当中央事件总线,将它作为事件中心,用它来触发事件和监听事件,来实现兄弟组件之间的通信。
- 跨级组件通信:可以通过 provide/inject 属性来实现跨级组件之间的通信。通过在父组件中使用 provide 向子孙组件提供数据,然后在需要使用数据的子孙组件中使用 inject 来注入数据。
- Vuex 状态管理:Vuex 是 Vue 的一个状态管理插件,可以将组件中的共享状态抽取出来,以全局的方式进行管理,从而实现组件之间的通信。
- Event Bus:使用 Event Bus(事件总线)来进行组件之间的通信,可以在 Vue 实例中定义一个空的 Vue 实例,用它来触发事件和监听事件。 综上所述,Vue 组件之间的通信方式有多种,可以根据具体的场景和需求来选择合适的方式。
双向数据绑定是什么?*
双向数据绑定是 Vue 中的一种数据绑定方式。它可以实现数据的自动同步更新,即数据模型(Model)中的数据变化会立即反映到视图(View)中,同时视图中数据的变化也会自动同步到数据模型中。Vue 的双向数据绑定通过 v-model 指令实现,它可以绑定表单元素(如 input、select 和 textarea)的 value 或者 checked 属性,用于在视图中更新数据模型中的数据。当用户在表单元素中输入数据时,这些数据会自动同步到数据模型中。反过来,当数据模型中的数据发生变化时,视图中绑定的属性值也会自动更新。双向数据绑定可以大大简化开发人员的工作量,提高开发效率。
Vue项目中你是如何解决跨域的呢?*
一、跨域是什么
跨域本质是浏览器基于同源策略的一种安全手段
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能
所谓同源(即指在同一个域)具有以下三个相同点
- 协议相同(protocol)
- 主机相同(host)
- 端口相同(port)
反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域
一定要注意跨域是浏览器的限制,你用抓包工具抓取接口数据,是可以看到接口已经把数据返回回来了,只是浏览器的限制,你获取不到数据。用postman请求接口能够请求到数据。这些再次印证了跨域是浏览器的限制。
二、如何解决
解决跨域的方法有很多,下面列举了三种:
- JSONP
- CORS
- Proxy
而在vue
项目中,我们主要针对CORS
或Proxy
这两种方案进行展开
vue3有了解过吗?能说说跟vue2的区别吗?
我了解Vue3,以下是Vue3和Vue2的主要区别:
- 性能提升:Vue3在底层架构上进行了优化,使用Proxy代替了Object.defineProperty,提高了响应式系统的性能。在编译器方面,使用了静态提升和hoist静态节点,减少了渲染和更新的时间。
- Composition API:Vue3引入了Composition API,它是一种新的API风格,可以更好地组织和重用逻辑代码。
- Teleport组件:Vue3增加了Teleport组件,可以将组件的内容渲染到DOM树的任何位置,而不是只能放在当前组件的模板中。
- Fragment组件:Vue3引入了Fragment组件,可以让组件返回多个根元素,而不需要用一个div包裹。
- 更好的TypeScript支持:Vue3使用TypeScript重写了整个代码库,提供了更好的类型支持和代码提示。 总的来说,Vue3在性能、开发体验和组件化方面都有很大的提升。但由于Vue3对一些API进行了重构,所以如果要升级到Vue3,需要对一些代码进行修改。
说说var、let、const之间的区别
四、区别
var
、let
、const
三者区别可以围绕下面五点展开:
- 变量提升
- 暂时性死区
- 块级作用域
- 重复声明
- 修改声明的变量
- 使用
变量提升
var`声明的变量存在变量提升,即变量可以在声明之前调用,值为`undefined
let
和const
不存在变量提升,即它们所声明的变量一定要在声明后使用,否则报错
暂时性死区
var
不存在暂时性死区
let
和const
存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
块级作用域
var
不存在块级作用域
let
和const
存在块级作用域
重复声明
var
允许重复声明变量
let
和const
在同一作用域不允许重复声明变量
修改声明的变量
var
和let
可以
const
声明一个只读的常量。一旦声明,常量的值就不能改变
使用
能用const
的情况尽量使用const
,其他情况下大多数使用let
,避免使用var
你是怎么理解ES6中 Promise的?使用场景?
ES6中的Promise是一种异步编程的解决方案,用于处理异步操作并返回结果。它可以将回调函数嵌套的问题简化为链式调用,使代码更加清晰、易读和可维护。 Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。当一个Promise实例被创建后,它会立即进入pending状态。一旦异步操作成功,Promise会进入fulfilled状态,并返回异步操作的结果;如果异步操作失败,Promise会进入rejected状态,并返回失败的原因。 使用Promise的场景包括:
- 异步操作:Promise可以处理异步任务,例如从服务器获取数据或处理文件读取。
- 依赖关系:当需要依次执行多个异步操作时,可以使用Promise的链式调用来处理依赖关系,避免回调函数嵌套的问题。
- 错误处理:使用Promise可以更好地处理异步操作中的错误,避免了回调函数中的错误处理逻辑。
- 并发操作:使用Promise.all()可以并发执行多个异步操作,并在所有操作完成后返回结果。 例如,以下是一个使用Promise处理异步操作的示例:
plaintextCopy codefunction getData() {
return new Promise((resolve, reject) => {
fetch('https://example.com/data')
.then(response => {
if (response.ok) {
response.json().then(data => resolve(data))
} else {
reject('Network response was not ok.')
}
})
.catch(error => reject(error))
})
}
getData().then(data => {
console.log(data)
}).catch(error => {
console.error(error)
})
在这个示例中,我们使用Promise封装了一个异步请求,并使用then和catch方法来处理异步操作的结果和错误。
深拷贝浅拷贝的区别?如何实现一个深拷贝?
深拷贝和浅拷贝是针对对象和数组的赋值操作的两种不同方式:
- 浅拷贝:将原对象或数组的引用复制给新的变量,新变量指向原对象或数组的内存地址。当修改新变量时,原对象或数组也会被修改。
- 深拷贝:将原对象或数组复制一份,新的变量与原对象或数组没有任何关系。当修改新变量时,原对象或数组不会被修改。 实现深拷贝的方式有很多,以下是一种简单的递归实现方式:
plaintextCopy codefunction deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj
}
let clone = Array.isArray(obj) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key])
}
}
return clone
}
在这个实现中,我们首先判断传入的参数是否为对象或数组,如果不是,直接返回。如果是,我们创建一个新的对象或数组,并遍历原对象或数组中的所有属性,递归地调用deepClone函数,将每一个属性进行深拷贝,并保存到新的对象或数组中。最后返回新的对象或数组。 需要注意的是,这个实现有一些缺陷,例如无法处理循环引用的对象,会导致栈溢出。在实际开发中,需要根据具体情况选择更加完善的深拷贝实现方式。
说说你对闭包的理解?闭包使用场景
闭包:函数内部嵌套一个函数,内部函数引用外部函数的数据,内部函数 称之为闭包
闭包的生命周期:
产生:当嵌套的内部函数定义执行完之后
死亡:当嵌套的内部函数成为一个垃圾对象的时候
闭包产生的条件是:
1、函数嵌套一个函数
2、内部函数引用外部函数的数据(变量/函数)
3、执行外部函数
闭包的作用?
1、延长局部变量的生命周期
2、让函数外部操作函数内部的数据(变量/函数)
闭包的缺点:
1.容易造成内存泄漏
2.需要进行手动释放 将要释放的对象设为null
闭包(Closure)是指在函数内部定义的函数,可以访问外层函数的变量和参数,并将其保存在内存中。闭包可以实现许多高级的编程技巧,例如函数柯里化、模块化等。 在 JavaScript 中,函数是一等公民,可以作为参数传递,也可以作为返回值返回。当一个函数作为参数传递到另一个函数中时,它可以访问外层函数的变量,并且这些变量在执行完外层函数后并不会被销毁,而是被内部函数引用,形成了一个闭包。 闭包的使用场景有很多,其中一些常见的场景包括:
- 实现函数柯里化:柯里化是一种将接受多个参数的函数转换成接受一个单一参数的函数,并返回接受余下参数且返回结果的新函数的技术。这可以通过闭包来实现,在内部函数中保存外层函数的参数,并返回一个新的函数,新函数继续接收参数,直到所有参数都被传入为止。
- 封装私有变量:JavaScript 中没有真正的私有变量,但是可以使用闭包来模拟私有变量的功能。在外层函数中定义一个变量,然后在内部函数中定义访问和修改这个变量的方法,并将这些方法返回,外部无法直接访问这个变量,只能通过内部函数来访问和修改。
- 延迟执行:可以使用闭包实现一个延迟执行的效果,例如在 setTimeout 中传入一个函数,函数中再返回一个函数,在内部函数中访问外层函数的变量,这样可以在一定的时间后执行这个内部函数,实现延迟执行的效果。 总之,闭包是一种强大的编程技巧,可以实现许多高级的功能和模式,但也需要注意内存泄漏和性能问题,合理使用闭包可以提高代码的可读性和可维护性。
JavaScript原型,原型链 ? 有什么特点?
JavaScript 是一门基于原型(Prototype)的语言,每个对象都有一个原型对象,原型对象又可以有自己的原型对象,形成一个原型链。原型链的最顶端是 Object.prototype,也就是所有对象的原型对象。 当我们访问一个对象的属性或方法时,会先在这个对象自身查找,如果没有找到,就会沿着原型链向上查找,直到找到该属性或方法或到达原型链的最顶端为止。如果找到了该属性或方法,就会直接返回它,否则就会返回 undefined。 原型链的特点如下:
- 所有的对象都有原型,原型也是对象,最终原型是 Object.prototype。
- 对象可以通过原型链来继承属性和方法。
- 当访问一个对象的属性或方法时,会先在对象本身查找,如果没有找到,就会沿着原型链向上查找。
- 通过原型链可以实现属性和方法的共享,从而节省内存和提高代码的复用性。
- 如果一个对象的原型发生了变化,那么它的所有子孙对象都会受到影响。
- 如果对象本身和原型对象都有同名的属性或方法,对象本身的属性或方法会覆盖原型对象的属性或方法。
- 通过修改原型对象,可以动态地给对象添加属性和方法,从而实现动态继承和多态等功能。 总之,原型链是 JavaScript 中非常重要的一个概念,在深入理解 JavaScript 对象和继承的原理以及如何高效地使用面向对象编程时,都需要对原型链有深入的了解。
JavaScript中执行上下文和执行栈是什么?
执行上下文(Execution Context)是 JavaScript 执行代码时的一个抽象概念,它是一个对象,包含了当前代码执行的环境信息,例如 this、变量、函数等。每当 JavaScript 执行一段可执行代码时,就会创建一个新的执行上下文,并将其压入执行栈中。 执行栈(Execution Stack)是一个栈数据结构,用于存储执行上下文。当 JavaScript 执行一段可执行代码时,会先创建一个全局执行上下文并将其压入执行栈底部,然后当遇到函数调用时,就会创建一个新的函数执行上下文并将其压入执行栈顶部。当函数执行完毕后,就会将其对应的执行上下文从执行栈中弹出,并将控制权交给下一个执行上下文。 执行上下文和执行栈的关系如下:
- 执行栈是一个栈数据结构,用于存储执行上下文。
- 每当 JavaScript 执行一段可执行代码时,就会创建一个新的执行上下文,并将其压入执行栈中。
- 当遇到函数调用时,就会创建一个新的函数执行上下文并将其压入执行栈顶部。
- 当函数执行完毕后,就会将其对应的执行上下文从执行栈中弹出,并将控制权交给下一个执行上下文。
- 执行栈的底部始终是全局执行上下文,顶部是当前正在执行的执行上下文。
- 当执行栈为空时,代码执行完毕。 执行上下文和执行栈是 JavaScript 执行代码的重要基础,深入理解它们可以帮助我们更好地理解 JavaScript 代码的执行过程,从而编写出更加高效、优雅的代码。
说说JavaScript中的事件模型
JavaScript 中的事件模型是指浏览器中的事件处理机制,它是一种基于观察者模式的设计模式,用于处理用户的交互行为和其他外部事件。 事件模型由三个部分组成:事件、事件监听器和事件处理程序。
- 事件:事件是用户的交互行为或其他外部事件,例如点击按钮、鼠标移动、键盘按键等。
- 事件监听器:事件监听器是一个函数,用于监听特定类型的事件。当事件触发时,浏览器会调用事件监听器并传递事件对象作为参数。
- 事件处理程序:事件处理程序是用于处理特定类型的事件的逻辑代码。当事件触发时,浏览器会调用事件处理程序来执行相应的逻辑。 JavaScript 中的事件模型分为三种:DOM0 事件模型、DOM2 事件模型和 IE 事件模型。
- DOM0 事件模型:通过给元素的事件属性绑定事件监听器来实现事件处理,例如 elem.onclick = function() { … }。
- DOM2 事件模型:通过 addEventListener() 方法来绑定事件监听器,可以同时绑定多个监听器,并且可以控制事件的捕获和冒泡,例如 elem.addEventListener(‘click’, function() { … }, false)。
- IE 事件模型:通过 attachEvent() 方法来绑定事件监听器,但是不能同时绑定多个监听器,并且只支持事件冒泡,例如 elem.attachEvent(‘onclick’, function() { … })。 在实际开发中,我们通常使用 DOM2 事件模型来绑定事件监听器,因为它具有更好的兼容性和可扩展性,同时也可以控制事件的捕获和冒泡,更加灵活。
什么是防抖和节流?有什么区别?如何实现?
防抖和节流是两种优化 JavaScript 函数性能的常用方法。 防抖(Debounce)指的是在连续触发某个事件时,只执行最后一次触发事件的函数。例如,当用户连续输入搜索关键字时,只在用户输入完成后进行搜索,避免频繁的网络请求。防抖的原理是通过清除计时器来实现,每次触发事件时,都会清除之前的计时器并重新计时,直到最后一次触发事件完成后,才会执行相应的函数。 节流(Throttle)指的是在连续触发某个事件时,每隔一定时间执行一次函数。例如,当用户连续滚动页面时,每隔一定时间再去判断当前滚动位置,避免频繁的计算。节流的原理是通过设置一个定时器来实现,每次触发事件时,都会判断定时器是否存在,如果不存在则执行函数并设置定时器,如果存在则忽略当前触发事件。 防抖和节流的区别在于,防抖只执行最后一次触发事件的函数,而节流则每隔一定时间执行一次函数。防抖适用于连续触发的事件,如输入框搜索、窗口缩放等,节流适用于间隔性触发的事件,如页面滚动、按钮点击等。 实现防抖和节流的方法如下: 防抖的实现:
javascriptCopy codefunction debounce(func, delay) {
let timer = null;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
调用 debounce 函数会返回一个新函数,这个新函数会在指定的时间(delay)内执行最后一次触发事件的函数(func)。 节流的实现:
javascriptCopy codefunction throttle(func, delay) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
}
};
}
调用 throttle 函数会返回一个新函数,这个新函数会每隔指定的时间(delay)执行一次触发事件的函数(func),直到事件触发结束。
元素水平垂直居中的方法有哪些?如果元素不定宽高呢
元素水平垂直居中的方法有以下几种:
使用 flexbox 布局。将父元素的 display 属性设置为 flex,然后使用 align-items 和 justify-content 属性分别设置垂直和水平居中方式。这种方法比较简单,但要求父元素必须是 flex 容器。
使用绝对定位和 transform 属性。将父元素设置为相对定位,子元素设置为绝对定位,然后使用 transform 属性设置 translate(-50%!, -50%!),即可实现水平垂直居中。这种方法适用于父元素和子元素都不定宽高的情况。
使用 table-cell 布局。将父元素的 display 属性设置为 table,子元素的 display 属性设置为 table-cell,然后使用 text-align 和 vertical-align 属性分别设置水平和垂直居中方式。这种方法适用于父元素和子元素都不定宽高的情况。
什么是HTTP? HTTP 和 HTTPS 的区别?
HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种基于请求与响应模式的、无状态的、应用层的协议,常用于客户端和服务器之间的通信。HTTP 协议是因特网上应用最为广泛的一种网络协议。 HTTP 和 HTTPS 的区别在于安全性和加密方式不同。HTTP 协议是明文传输,数据容易被窃听和篡改,因此不安全;而 HTTPS 协议在传输过程中使用 SSL/TLS 加密技术,数据传输更加安全,可以有效地防止数据被篡改和窃听。另外,HTTPS 还需要使用证书来验证服务器的身份,防止中间人攻击。 具体来说,HTTPS 在传输前需要进行 SSL 握手,建立安全通道。该过程主要包括以下步骤:
- 客户端向服务器发送 SSL 握手请求。
- 服务器响应 SSL 握手请求,并向客户端返回 SSL 证书。
- 客户端验证证书的合法性,如果证书合法,则生成一份随机数,并使用证书中的公钥加密。
- 服务器使用私钥解密客户端发送的数据,并生成一份随机数,使用公钥加密发送给客户端。
- 客户端使用先前生成的随机数解密服务器发送的数据,并生成一份新的随机数。
- 客户端和服务器使用三份随机数生成对称密钥,用于加密和解密后续的数据传输。 HTTPS 协议相对于 HTTP 协议来说更安全,但也存在一定的性能问题。HTTPS 协议需要进行 SSL 握手和加密操作,相比于 HTTP 协议,会增加一定的网络延迟和计算负担。因此,在安全性和性能之间需要做出权衡,根据实际需求选择适合的协议。
说说地址栏输入 URL 敲下回车后发生了什么?
当在地址栏中输入 URL 并敲下回车时,会发生以下过程:
- 浏览器会首先检查 URL 是否合法,如果不合法则会提示错误信息。
- 如果 URL 合法,则浏览器会判断该 URL 是否存在于缓存中,如果存在,则从缓存中读取网页并显示。
- 如果 URL 不在缓存中,则浏览器会向该 URL 对应的服务器发送请求。
- 服务器接收到请求后,会根据请求的 URL 查找对应的资源,并将资源返回给客户端。如果请求的是静态资源(如 HTML、CSS、JS 文件等),则服务器直接返回该资源;如果请求的是动态资源(如 PHP、ASP、JSP 等文件),则服务器会根据请求的参数动态生成资源,并将生成的资源返回给客户端。
- 浏览器接收到服务器返回的资源后,会进行解析和渲染。首先,浏览器会根据返回的 HTML 文件构建 DOM 树;然后,根据 CSS 文件和 HTML 文件中的样式信息进行样式计算,生成渲染树;最后,根据渲染树和浏览器窗口的大小进行布局和绘制,最终将页面呈现给用户。
- 在页面呈现的过程中,浏览器会同时下载页面中引用的其他资源,如图片、音频、视频等文件,以及 JavaScript 文件等。当这些文件下载完成后,浏览器会执行 JavaScript 代码,实现动态交互和页面功能。 总体来说,地址栏输入 URL 敲下回车后的过程可以分为 DNS 解析、建立 TCP 连接、发送 HTTP 请求、服务器处理请求、浏览器接收响应并渲染页面等多个阶段。每个阶段涉及的技术和细节都很复杂,需要深入学习和理解。
说说TCP为什么需要三次握手和四次挥手?
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层协议。TCP 协议使用三次握手建立连接和四次挥手断开连接,是为了保证数据传输的可靠性和正确性。 三次握手是为了建立连接:
- 第一次握手:客户端发送 SYN 报文,请求建立连接,并将 SYN 标志位设为 1,同时随机生成一个初始序号 seq。
- 第二次握手:服务器收到 SYN 报文后,如果同意建立连接,则发送 SYN+ACK 报文作为应答,将 SYN 和 ACK 标志位都设为 1,同时将确认序号 ack 设置为客户端的初始序号 seq+1,随机生成一个初始序号 seq。
- 第三次握手:客户端收到 SYN+ACK 报文后,向服务器发送 ACK 报文作为应答,将 ACK 标志位设为 1,确认序号 ack 设置为服务器的初始序号 seq+1。 三次握手的目的是为了确保客户端和服务器的状态同步,并且确认双方的发送和接收能力正常,从而保证数据传输的可靠性。 四次挥手是为了断开连接:
- 第一次挥手:客户端发送 FIN 报文,请求断开连接,并将 FIN 标志位设为 1,同时将序号 seq 设置为客户端最后一个字节的序号。
- 第二次挥手:服务器收到 FIN 报文后,发送 ACK 报文作为应答,将 ACK 标志位设为 1,确认序号 ack 设置为客户端的序号 seq+1。
- 第三次挥手:服务器发送 FIN 报文,请求断开连接,并将 FIN 标志位设为 1,同时将序号 seq 设置为服务器最后一个字节的序号。
- 第四次挥手:客户端收到 FIN 报文后,发送 ACK 报文作为应答,将 ACK 标志位设为 1,确认序号 ack 设置为服务器的序号 seq+1。 四次挥手的目的是为了确保客户端和服务器都能正确地关闭连接,并且在连接关闭后不会有任何遗留的数据包,从而保证数据传输的正确性。因为 TCP 协议是全双工的,所以需要客户端和服务器各发送一次 FIN 报文来关闭连接。