前端面试题(持续更新...)
HTML篇
cookie,localStorage,sessionStorage的区别
- 存储大小:cookie40kb左右,Storage20M左右
- 存储格式:cookie是字符串格式,Storage是键值对
- 通讯相关:cookie随请求携带,Storage不会自动携带
- 操作相关:cookie操作复杂,没有api前后端都可以操作;Storage操作简单,有api,只能前端操作。
- 时效相关:cookie默认是会话,可以手动设置;localStorage的存储是永久的,只有在手动删除或者卸载浏览器的时候才会被清除;sessionStorage是会话,当窗口或页面被关闭的时候会被清除
输入一个url到页面渲染发生了什么
- 浏览器地址栏输入URL并回车
- 浏览器查找当前URL是否存在缓存,并比较缓存是否过期
- DNS解析URL对应ip
- 根据ip建立TCP连接
- 发送http请求
- 服务器处理请求,浏览器接收HTTP响应
- 浏览器解析并渲染页面
- 关闭TCP连接
标准盒模型和怪异盒模型的区别,怎样转怪异盒模型
标准盒模型的组成:宽高(content) + padding + border + margin
怪异盒模型:width(content+border+padding)+margin
标准盒模型可以转成怪异盒模型:用属性box-sizing
box-sizing的属性有两个 border-box(怪异盒模型)/content-box(标准盒模型)
重绘和重排
重绘:元素的外观改变,列如元素的颜色和背景颜色发生改变
重排:重新排列元素,列如元素的尺寸,位置发生改变
JS篇
JS的数据类型
基本数据类型:
- Number
- String
- boolean
- undefined
- null
- symbol
引用数据类型:
- Object 对象
- Array 数组
- function 函数
检测数据类型的方法
- typeof检测基本数据类型
-
A instanceof B 检测当前实例是否属于某各类
- Object.prototype.toString.call()
setTimeout和setInterval定时器无法按时执行的原因
js是单线程,所以异步事件仅在空闲时才会被调用,代码执行异步事件时会按照将它们添加到队列的顺序执行,如果队列是空的,那么添加代码会立即执行,如果队列不是空的,那么就要等前面代码执行完在执行。
ES6新特性
- 新增块级作用域let定义变量和const定义常量。
- 变量的结构赋值。
- 模板字符串。
- 箭头函数。
- 扩展运算符。
- 模块(import/export)
- 类(class/extends)
new操作符具体做了什么
- 在内存创建一个新对象
- 把构造函数中的this指向新建的对象
- 会在新对象上添加一个__proto__属性,指向函数的原型对象prototype
- 判断函数返回值,如果值是引用数据类型就直接返回值,否则就返回this(新建对象)
this指向的各种情况
- 全局作用域中的函数,在非严格模式下this的指向是window
- 对象内部函数,其内部this指向对象本身
- 构造函数:其内部this指向生成的实例对象
- apply,call,bind改变的this的指向,其this指向第一个参数
- 箭头函数指向它的上级对象,
普通函数 箭头函数的区别
- 箭头函数没有原型 原型是undefined
- 箭头函数的this应该是创建时所在作用域指向的对象
- call,apply,bind方法改变不了箭头函数的指向
闭包的理解
产生的条件:
- 需要不销毁的函数,在函数当中return引用数据类型
- 嵌套的函数
- 内部函数要访问外部函数的变量
优点:
- 延长变量的声明周期
- 可以访问函数内部的变量
缺点:
- 因为闭包里面的变量长期占用内存,容易导致低版本IE浏览器造成内存泄漏,当我们使用完闭包之后,需要手动释放(f = null)
什么是原型
所有函数都有一个prototype(显示原型对象)属性
所有对象都有一个__proto__(隐式原型对象)属性
所有内置函数都是function这个函数的实例对象
原型链
对象在访问某一个对象时,会在自身找,如果没有会在原型对象上找,一直往上查找,就会形成原型链(注意:如果找不到,最后是null)
call(),apply(),bind()的区别
- call()的参数是直接放进去的,第二个参数之后的参数全部都用逗号分隔
- apply()的所有参数都必须在一个数组传进去
- bind()除了返回的是函数以外,它的参数和call()一样
- 三者都可以改变this的指向对象
- 三者第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null时,默认指向全局变量window
- 三者都可以传参,但是apply()是数组,而call()是参数列表,且apply()和call()是一次性传入参数,而bind()可以分为多次传入
- bind()是返回绑定this之后的函数,便于稍后调用;apply()和call()是立即执行
Promise和async,await
Promise是ES6中的一个内置对象,实际上是一个构造函数
特点:
- 三种状态:pending(进行中),resolved(完成),rejected(失败),只有异步操作的结果可以决定当前是哪一种状态,任何其它操作都不能改变这个状态。
- 两种状态的转化:pending(进行中)到resolved(完成),pending(进行中)到rejected(失败)。
- Promise构造函数的原型对象上,有then()和catch()等方法,then()第一个参数接收resolved()传来的数据,catch()第一个参数接收rejected()传来的数据。
作用:
- 通常用来解决异步调用的问题
- 解决回调地狱的问题
- 提高代码的可读性,便于维护
async/await是基于Promise实现的,它不能用于普通的回调函数
async/await使得异步代码看起来像同步代码
async/await和Promise都是非阻塞的
async/await比Promise的优越性
- 简洁:使用async.await能省去许多代码
- 错误处理:async/await能用相同的结构和try/catch处理同步和异步错误,错误堆栈能指出包含错误的函数
- 调试:async/await的一个极大的优势是更容易调试,使用async/await无需过多的箭头函数,并且能像正常调用的同步调用一样直接跨过await调用
vue篇
什么是生命周期
vue实例有一个完整的生命周期,也就是从开始创建,初始化数据,编译模板,挂载DOM>渲染,更新>渲染,卸载等一系列过程称之为生命周期。
dom节点在那个生命周期生成
mounted生命周期
什么是指令
指令就是一套vue内置的模板,主要用于在视图节点上动态绑定变量,指令实际上也是DOM操作
自定义指令
在vue中,除了内置指令外,我们还可以使用vue.directive()或directives选项来自定义指令。
v-model的原理
在vue中v-model指令在表单input等元素上创建双向数据绑定,v-model本质上是一个语法糖,v-model在内部不同的元素使用不同的属性并抛出不同的事件
<input v-model="set" />
相当于
<input v-bind:value="set" v-on:input="set"=$event.target.value />
vue响应式的原理
当vue组件被创建时,在生命周期的第一阶段(创建阶段)vue使用Object.defineProperty()对data选项进行遍历劫持并添加get/set钩子;在生命周期第二阶段(挂载阶段)指令与声明式变量touch时,会发生依赖收集,这时候再调用当前组件的watcher进行第一次Dom渲染,如果声明式变量发生变化时,vue会再次通知watcher更新视图。
理解vue的单向数据流
所有的props都使得其父子props之间形成了一个单向下行绑定:父级的更新会向下流动到子组件中,但是反过来不行,这样防止从子组件意外改变父组件的状态。
vue有哪些内置组件
插槽,动态组件,过渡动画,<component>
computed和watch的区别
computed是计算属性,依赖其它属性值,并且computed的值有缓存,只有它依赖的属性值发生改变,下次获取的属性值才会重新计算。
watch更多的是观察的作用,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调
怎么理解组件化
组件化:把重复的代码提取出来封装成一个组件,实现组件的复用。
组件:组件就是HTML的一个扩展,使用粒度较小的HTML元素封装成组件
如何自定义组件
封装自定义组件的时候,可以使用vue选项属性,比如data,temolate,methods等
组件封装后,必须要组件注册才能在vue中使用,注册自定义组件时,组件名必须由多个单词用中划线连接
使用temolate选项指定组件的视图结构
如何进行组件注册
全局注册:使用Vue.component()进行全局注册,在任何组件中都可以使用
局部注册:使用components:{}进行局部注册,只有在当前组件作用域中使用。
注意:组件注册时,组件名称必须由多个单词用中划线连接
组件化的三大技术
自定义属性: <qf-score :num='num' count='1'></qf-score>
自定义事件: <qf-score @change='' @click=''></qf-score>
自定义插槽: <qf-score> <div #default></div> </qf-score>
组件的关系与通信
约定:在MVVM框架中,当我们谈论“组件”概念时,通常指的是自定义组件。当在A组件视图结构中使用到B组件,就会形成组件关系(父子关系),当组件足够多的时,组件之间就会形成“组件树”。
通信:在vue中通信就是组件之间的数据交互。父组件向子组件通信使用自定义属性,在子组件中使用props接收;子组件向父组件通信,使用自定义事件,在子组件中使用this.$emit(‘自定义事件’,‘数据’)回传数据。
什么是MVVM框架
MVVM流程:M数据层,VM虚拟DOM层,V视图层
MVVM的优点:低耦合,可重用性,独立开发
动态组件
被<keep-alive>所包裹的组件就是动态组件,并且这个组件是不会被销毁的,相当于是组件的一种缓存。
被动态组件包裹过的组件多了两个生命周期钩子activated(激活),deactivated(休眠)
activated和mounted的区别
activated可以执行多次,mounted只能执行一次。
deactivated和beforeDestroy的区别
deactivated可以执行多次,而beforeDestroy只能执行一次
什么是插槽
插槽就是子组件提供给父组件使用的一个占位符,用<slot></slot>表示,父组件可以在插槽中填充任何代码,填充的内容会替换子组件的<slot><slot/>标签。
什么是具名插槽
具名插槽就是给插槽取个名字,一个子组件可以放多个插槽,而父组件填充内容的时候可以根据名字把内容填充到对应的插槽中。
什么是作用域插槽
作用域插槽就是绑定数据的插槽,组件内变量绑定在上,使用v-slot指令,变量上就会绑定传递的属性和值。
组件中data为什么是一个函数,new Vue实例是一个对象
因为组件是用来复用的,且在js里的对象是引用关系,如果组件中的data是一个对象,那么作用域就没有隔离,子组件中的data属性值会互相影响,如果组件中的data是一个函数,那么每个实例都会返回一个对象,组件之间的data属性值不会互相影响;而new Vue的实例不会被复用的,所以不存在引用对象的问题
vue2和vue3的区别
- 双向数据绑定发生变化:vue2双向数据绑定是利用ES5的object.defineProperty()对数据进行劫持,在结合发布订阅模式来实现的;vue3中使用ES6的ProxyAPI对数据代理
- 根节点:vue2是单一根节点;vue3是多根节点。
- API:vue2使用选项类型API(Options API),用属性来分割组件;vue3使用组合型API(Composition API),用方法来分割组件
- 数据建立:vue2把数据放在data中;vue3使用setup方法,在组件初始化的时候vue触发
- 生命钩子发生变化:
vue2 vue3 beforetCreate setup() created setup() beforeMount onBeforeMount mounted onMounted befoeUpdate onBeforeUpdate updated onUpdated beforeDestroy onBeforeUnmount destroyed onUnmounted activated onActivated deactivated onDeactivated - 传参不同:
父传子:vue3通过props接收并通过toRefs转成响应式toRefs(props)
子传父:在vue2中会调用到this.$emit然后传入事件名和参数对象,vue3中没用this,只能通过setup中的参数 传递。
- vue3较vue2体积小,速度快
- 在vue2中,主要使用的是观察者模式,不管数据多大,都会对数据进行创建检查;vue3对数据进行懒观察,仅对可见部分数据进行懒观察。
vue3里为什么要用Proxy替代defineProperty
defineProperty的弊端:
- vue2是通过object.defineproperty中的getter和setter函数进行数据劫持完成数据响应的
- 无法直接监听属性的改变
- 无法直接监听数组
proxy的优势:
- 作为vue3中替代defineProperty的API,相当于给一个对象的外层加了一层拦截,这层拦截可以对信息的过滤,修改。
- 因为是在整个对象外层加了一层拦截,所以操作对象中的属性同时监听属性的变化
- 可以直接监听数组变化
说一下nextTick是做什么的
nextTick()是将回调函数延迟在下一次Dom更新数据后调用,简单理解:当数据更新了,在dom中渲染后,自动执行该函数,next Tick多次调用会维持一个数组,之后会异步的把数组中的方法执行,这样就会在视图更新之后获取到真实的DOM元素。
nextTick和$nextTick的区别
- nextTick():当数据发生变化,更新后执行回调,在下次DOM更新结束之后延迟回调,在修改数据之后立即执行这个方法,获取更新后的DOM
- $nextTick():当DOM发生变化,更新后执行回调。将回调延迟到下次DOM更新之后执行。在修改数据立即使用它,然后等待DOM更新。
- 区别:nextTick(callback)是全局的方法;$nextTick(callback)是回调的this自动绑定到调用它的实例上
SPA单页面的理解,它的优点缺点分别是什么
SPA0(single-page application)仅在Web页面初始化时加载响应的HTML,JS和CSS。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载和跳转;取而代之的是利用路由机制实现HTML内容的变化,UI与用户的交互,避免页面重新加载。
优点:
- 用户体验好, 内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
- 基于上面一点,SPA相对对服务器压力小
- 前后端分离,架构清晰,前端进行交互逻辑,后端负责数据处理
缺点:
- 初次加载耗时多,为实现单页web应用功能及显示效果,需要统一加载,部分页面按需加载
- SEO难度较大:由于所有的内容都在一个页面中动态显示,所以在SEO上有着天然的弱势
watch深度监听
deep:true开启深度监听
什么是导航守卫
在router实例对象上有三个重要的全局钩子(beforeEach、beforeResolve、afterEach),每次url发生变化时,都会触发这三个钩子按顺序执行。那么以后我可以在这些钩子编写验证逻辑,如果验证通过就放你过去,你就可以正常访问你想访问的页面;如果验证失败,就阻止你访问目标页面,这就实现“守卫”的效用了。在路由中,使用导航守卫和路由元信息,可以做鉴权、还可以做权限设计。
vue中父子组件传值,父组件异步请求,子组件不能实时更新怎么解决?(vue中数据不能实时更新怎么解决?)
原因:因为生命周期只会执行一次,数据要等到异步请求之后才能拿到,子组件的mounted钩子执行的时候,还没拿到父组件传过来的数据
解决方法:
- 初始还没拿到后端接口的异步数据的时候,不让组件渲染,等拿到的时候再去渲染组件。使用v-if="变量"去控制
- 使用watch监听数据的变化
- 使用Vuex
vuex和window的区别
使用vuex保存数据和window保存数据原理一样,都是借助全局对象进行数据互通,不同的是vuex通过发布订阅机制将vue数据更新通知到所有订阅状态的组件上,实现数据更新,vuex和window单独使用的时候都会出现同一个问题,就是保存的数据再刷新页面,数据会丢失,出现这个问题是因为数据都是在运行内存保存的。
vuex中 mutations和action的区别
- 主要区别在于mutations只能同步操作,action是异步操作,而且可以通过action提交mutations
- mutations有个固有参数state,接收的是Vuex中的start对象
- action也有一个固有参数context,但是context是state的父级,包含state,getters
怎么封装axios
- 引入axios
- 创建一个实例
- 添加拦截器(请求拦截器,响应拦截器)
- 配置必传的参数
- 向外暴露
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构