2024前端面试题整理(详细)更新中
一、基础部分
1.1、常用的块与行属性内标签有哪些?有什么特征?
块标签:div、h1~h6、ul、li、table、p、br、form。
特征:独占一行,换行显示,可以设置宽高,可以嵌套块和行
行标签:span、a、img、textarea、select、option、input。
特征:只有在行内显示,内容撑开宽、高,不可以设置宽、高(img、input、textarea等除外)
input(type)、textarea、select、img(src)、object 是置换元素
一个内容不受 CSS 视觉格式化模型控制,CSS 渲染模型并不考虑对此内容的渲染本身一般拥有固有尺寸(宽度,高度,宽高比)的元素,被称之为置换元素
1.2、常见的盒子垂直居中的方法有哪些请举例几种?
第一:position + margin
示例:
<style>
.container{
width: 300px;
height: 300px;
position: relative;
}
.conter{
width: 100px;
height: 100px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px;
margin-left: -50px;
}
</style>
第二:position + transform
<style>
.container{
position: relative;
}
.conter{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
</style>
第三:position
<style>
.container{
position: relative;
}
.conter{
position: absolute;
left:0;
right:0;
top:0,
bottom:0
margin:auto
}
</style>
第四、flex 弹性布局来实现
<style>
.container{
display: flex;
justify-content: center;
align-items: center;
}
.conter{
}
</style>
第五、使用grid
<style>
.container{
display: grid;
}
.conter{
justify-self: center;
align-self: center;
}
</style>
第六、使用table
.container{
display: table;
}
.conter{
display: table-cell;
vertical-align: middle;
text-align: center;
}
第七、使用伪类
.container{
font-size: 0;
text-align: center;
}
.container::before {
content: "";
display: inline-block;
width: 0;
height: 100%;
vertical-align: middle;
}
.conter{
display: inline-block;
vertical-align: middle;
}
1.3、Css优先级
!importent>行内> id> 类,伪类,属性>标签,伪元素选择器 > 继承和通配符
1.4、如何解决盒子塌陷?
父盒子设置上边距
overflow:hidden
子盒子脱标
父盒子上 padding
1.5、清楚浮动的方法?
父盒子设置高度
overflow:hidden
伪元素
双伪元素
在父盒子末尾添加一个空盒子,设置 clear:both
1.6、
二、js部分
2.1、== 和 ===的区别?
==是非严格意义上的相等。值相等就相等
===是严格意义上的相等,会比较两边的数据类型和值大小。值和引用地址都相等才相等
2.2、js数据类型有哪些,区别是什么?
基本类型:string,number,boolean,null,undefined,symbol,bigInt
引用类型: object,array
基本类型存储在栈中,空间小,操作频繁
引用数据类型存放在堆中,它的地址在栈中,一般我们访问就是它的地址
2.3、判断数组的方法,请分别介绍它们之间的区别和优劣
Object.prototype.toString.call()、instanceof、Array.isArray()以及typeof
1)Object.prototype.toString.call()
每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [object type],其中 type 为对象的类型。但当除了 Object 类型的对象外,其他类型直接使用 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用call或者apply方法来改变toString方法的执行上下文。
const an = ['Hello','World'];
an.toString(); // "Hello,World"
Object.prototype.toString.call(an); // "[object Array]"
这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
但是无法区分自定义对象类型,自定义类型可以采用instanceof区分
console.log(Object.prototype.toString.call("this"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call({name: "this"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]
Object.prototype.toString.call() 常用于判断浏览器内置对象。
2)instanceof
instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
使用 instanceof判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的原型,找到返回 true,否则返回 false。
但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。
instanceof Array; // true
instanceof Object; // true
'a' instanceof String; //false
3)Array.isArray()
功能:用来判断对象是否为数组
instanceof 与 isArray
当检测Array实例时,Array.isArray 优于 instanceof ,因为 Array.isArray 可以检测出 iframes
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
xArray = window.frames[window.frames.length-1].Array;
var arr = new xArray(1,2,3); // [1,2,3]
// Correctly checking for Array
Array.isArray(arr); // true
Object.prototype.toString.call(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false
Array.isArray() 与 Object.prototype.toString.call()
Array.isArray()是ES5新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现。
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
4)typeof
typeof 只能检测基本数据类型,包括boolean、undefined、string、number、symbol,而null、Array、Object ,使用typeof检测出来都是Object,无法检测具体是哪种引用类型。
2.4、let和const 的区别是什么?
let 命令不存在变量提升,如果在 let 前使用,会导致报错
如果块区中存在 let 和 const 命令,就会形成封闭作用域
不允许重复声明
const定义的是常量,不能修改,但是如果定义的是对象,可以修改对象内部的数据
2.5、什么是防抖和节流?有什么区别?应用场景?
防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。
节流(throttle):当持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。
函数节流是:在固定的时间内触发事件,每隔n秒触发一次。
函数防抖是:当你频繁触发后,n秒内只执行一次。
区别
函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。
实现
1、防抖(类似于 电梯 屏保 类的)
固定时间内没有触发事件会在固定时间结束后触发,如果在固定时间内触发了就会延长固定时间
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间
每次触发事件时都取消之前的延时调用方法
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又创建一个新的 setTimeout, 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
2、节流
无论在固定时间内是否有事件触发,都会按照固定时间规律触发
高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
节流的实现方法有两种:
第一种:时间戳
//时间戳版本实现节流
function throttle(func,wait) {
//定义初始时间
var oldTime=0;
return function() {
var _this=this;
var args=arguments;
//当前时间戳
var newTime=+new Date();
//判断用当前时间减去旧的时间,如果大于wait指定的时间就会触发
if(newTime-oldTime>wait) {
//执行触发的函数
func.apply(_this,args)
//将旧时间更新
oldTime=newTime;
}
}
第二种:定时器
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => { // 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
应用场景:
防抖:搜索文章或其他search时,用户在不断输入值时,用防抖来节约请求资源。
频繁操作点赞和取消点赞,因此需要获取最后一次操作结果并发送给服务器
节流:鼠标不断点击触发,mousedown(单位时间内只触发一次)
window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
具体参考:https://blog.csdn.net/weixin_46419373/article/details/108330981
2.6、什么是重绘和回流?
这里借鉴了另一个面试题,有点详细
https://segmentfault.com/a/1190000021247934?utm_source=sf-similar-article
先来看下浏览器的渲染过程
从上面这个图上,我们可以看到,浏览器渲染过程如下:1、解析HTML,生成DOM树,解析CSS,生成CSSOM树
2、将DOM树和CSSOM树结合,生成渲染树(Render Tree)
3、Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
4、Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
5、Display:将像素发送给GPU,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中,而css3硬件加速的原理则是新建合成层)渲染过程看起来很简单,让我们来具体了解下每一步具体做了什么。生成渲染树
为了构建渲染树,浏览器主要完成了以下工作:从DOM树的根节点开始遍历每个可见节点。对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。根据每个可见节点以及其对应的样式,组合生成渲染树。第一步中,既然说到了要遍历可见的节点,那么我们得先知道,什么节点是不可见的。不可见的节点包括:一些不会渲染输出的节点,比如script、meta、link等。一些通过css进行隐藏的节点。比如display:none。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。注意:渲染树只包含可见的节点回流前面我们通过构造渲染树,我们将可见DOM节点以及它对应的样式结合起来,可是我们还需要计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流。为了弄清每个对象在网站上的确切大小和位置,浏览器从渲染树的根节点开始遍历,我们可以以下面这个实例来表示:<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Critial Path: Hello world!</title>
</head>
<body>
<div style="width: 50%">
<div style="width: 50%">Hello world!</div>
</div>
</body>
</html>我们可以看到,第一个div将节点的显示尺寸设置为视口宽度的50%,第二个div将其尺寸设置为父节点的50%。而在回流这个阶段,我们就需要根据视口具体的宽度,将其转为实际的像素值。重绘最终,我们通过构造渲染树和回流阶段,我们知道了哪些节点是可见的,以及可见节点的样式和具体的几何信息(位置、大小),那么我们就可以将渲染树的每个节点都转换为屏幕上的实际像素,这个阶段就叫做重绘节点。既然知道了浏览器的渲染过程后,我们就来探讨下,何时会发生回流重绘。何时发生回流重绘我们前面知道了,回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流。比如以下情况:添加或删除可见的DOM元素元素的位置发生变化元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。页面一开始渲染的时候(这肯定避免不了)浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)注意:回流一定会触发重绘,而重绘不一定会回流根据改变的范围和程度,渲染树中或大或小的部分需要重新计算,有些改变会触发整个页面的重排,比如,滚动条出现的时候或者修改了根节点。
浏览器的优化机制
现代的浏览器都是很聪明的,由于每次重绘都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重绘过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:
offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle()
getBoundingClientRect
具体可以访问这个网站:https://gist.github.com/pauli...
以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来。
减少回流和重绘
好了,到了我们今天的重头戏,前面说了这么多背景和理论知识,接下来让我们谈谈如何减少回流和重绘。
最小化重绘和重排
由于重绘和重排可能代价比较昂贵,因此最好就是可以减少它的发生次数。为了减少发生次数,我们可以合并多次对DOM和样式的修改,然后一次处理掉。考虑这个例子
const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
例子中,有三个样式属性被修改了,每一个都会影响元素的几何结构,引起回流。当然,大部分现代浏览器都对其做了优化,因此,只会触发一次重排。但是如果在旧版的浏览器或者在上面代码执行的时候,有其他代码访问了布局信息(上文中的会触发回流的布局信息),那么就会导致三次重排。
因此,我们可以合并所有的改变然后依次处理,比如我们可以采取以下的方式:
使用cssText
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
修改CSS的class
const el = document.getElementById('test');
el.className += ' active';
具体参考:https://segmentfault.com/a/1190000021247934?utm_source=sf-similar-article
2.7、文字两端对齐
div {
margin: 10px 0;
width: 100px;
border: 1px solid red;
text-align: justify; //居中
text-align-last: justify; //两端对齐
}
2.8、['1', '2', '3'].map(parseInt)的结果是什么?
先说结果:
['1', NaN, NaN]
为什么不是['1', '2', '3']呢,下面开始分析
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
map() 方法按照原始数组元素顺序依次处理元素。
map(parseInt)其实是:
map(function(item, index){
return parseInt(item, index);
})
即依次运行的是:
parseInt('1', 0);
parseInt('2', 1);
parseInt('3', 2);
parseInt的用法
parseInt(string, radix) 函数可解析一个字符串,并返回一个整数。
当参数 radix 的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。
radix 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。
所以:
parseInt('1', 0);//'1'
parseInt('2', 1);//NaN
parseInt('3', 2);//NaN,由于2进制中没有3
2.9、数组去重的方法都有哪些?
1、ES6 set方法
var arr = [2,0,1,9,1,0,2,1,4];
var a_arr = [...new Set(arr)]
console.log(a_arr);
2、indexOf
var arr = [2,0,1,9,1,0,2,1];
var a_arr = [];
for(let i=0;i<arr.length;i++){
if(a_arr.indexOf(arr[i]) == -1){
a_arr.push(arr[i]);
}
}
console.log(a_arr);
3、filter方法
var arr = [2,0,1,9,1,0,2,1,4];
function unique(array) {
var res = array.filter(function(item, index, array){
return array.indexOf(item) === index;
})
return res;
}
console.log(unique(arr));
4、for循环
var arr = [2,0,1,9,1,0,2,1,4];
var a_arr = [];
for(let i=0;i<arr.length;i++){
var flag = true;
for(let j=0;j<a_arr.length;j++){
if(arr[i] == arr[j]){
flag = false;
}
}
if(flag){
a_arr.push(arr[i]);
}
}
console.log(a_arr);
2.10、一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
1、浏览器的地址栏输入URL并按下回车。
2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
3、DNS解析URL对应的IP。
4、根据IP建立TCP连接(三次握手)。
5、HTTP发起请求。
6、服务器处理请求,浏览器接收HTTP响应。
7、渲染页面,构建DOM树。
8、关闭TCP连接(四次挥手)。
具体参考:https://juejin.cn/post/6986416221323264030
2.11、ajax实现原理及方法使用
Ajax的原理:通过javascript的方式,将前台数据通过xmlhttp对象传递到后台,后台在接收到请求后,将需要的结果,再传回到前台,这样就可以实现不需要页面的回发,页是数据实现来回传递,从页实现无刷新。
简单来说,实际上就是通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面
原生ajax的请求步骤
//创建 XMLHttpRequest 对象
var ajax = new XMLHttpRequest();
//规定请求的类型、URL 以及是否异步处理请求。
ajax.open('GET',url,true);
//发送信息至服务器时内容编码类型
ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
//发送请求
ajax.send(null);
//接受服务器响应数据
ajax.onreadystatechange = function () {
if (ajax.readyState == 4 && (ajax.status == 200 || ajax.status == 304)) {
}
};
三、vue框架
3.1、Vue的生命周期都有哪些?
beforeCreate(创建前)、created(创建后)、beforeMount(载入前)、mounted(载入后)、beforeUpdate(更新前)、updated(更新后)、beforeDestroy(销毁前)、destroyed(销毁后)
第一次页面加载时,会执行那些?
beforeCreate(创建前)、created(创建后)、beforeMount(载入前)、mounted(载入后)
如果使用了keep-alive会在多两个:activated、deactivated
3.2、vue双向数据绑定的原理?
双向数据绑定是基于Object.defineProperty()重新定义get和set方法实现的。修改触发set方法赋值,获取触发get方法取值,并通过数据劫持发布信息。
3.3、v-if 和v-show有什么区别?
相同点:都可以控制dom元素的显示和隐藏
不同点:v-show只是改变display属性,dom元素并未消失,切换时不需要重新渲染页面
v-if直接将dom元素从页面删除,再次切换需要重新渲染页面
3.4、v-for 循环为什么一定要绑定key ?
给每个dom元素加上key作为唯一标识 ,diff算法可以正确的识别这个节点,使页面渲染更加迅速!
3.5、Vuex 的 5 个核心属性是什么?
state => 基本数据
getters => 从基本数据(state)派生的数据,相当于state的计算属性
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex
简述vuex数据传递过程
页面通过mapAction异步提交事件到action。action通过commit把对应参数同步提交到mutation,mutation会修改state中对应的值。 最后通过getter把对应值跑出去,在页面的计算属性中,通过,mapGetter来动态获取state中的值
mutations(修改state里面的数据,但是他只能执行同步的操作,异步必须写在action里面)
state(放数据)
action(执行异步操作)
getter(计算属性)
moudel(允许将单一store拆分多个store并且同时保存在单一的状态中)
3.6、请说出vue.cli项目中src目录每个文件夹和文件的用法?
assets文件夹是放静态资源;
components是方组件;
router是定义路由相关的配置
view是视图
app.vue是一个应用主组件
main.js是入口文件
3.7、promise是什么,有什么作用?
promise 是一个对象, 可以从改变对象获取异步操作信息
他可以解决回调地狱的问题,也就是异步深层嵌套问题
3.8、vue中 key 的原理?
便于diff算法的更新,key的唯一性,能让算法更快的找到需要更新的dom,需要注意的是,key要唯一,不然会出现很隐蔽性的更新问题。
3.9、$nextTick有什么作用?
处理数据动态变化后,dom还未及时更新的问题。$nextTick就可以获取到数据更新后最新的dom变化
3.9、vue项目优化的手段有哪些?
前端方面:
1、路由懒加载
2、图片,资源放cdn
3、页面图片较多进行懒加载
4、骨架屏方案
5、采用服务端渲染---nuxt.js
服务器端:
开启gzip
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。