2024年前端最全【面试题】做了一份前端面试复习计划,保熟~,2024年是做前端开发人员的绝佳时机

跳槽是每个人的职业生涯中都要经历的过程,不论你是搜索到的这篇文章还是无意中浏览到的这篇文章,希望你没有白白浪费停留在这里的时间,能给你接下来或者以后的笔试面试带来一些帮助。

也许是互联网未来10年中最好的一年。WINTER IS COMING。但是如果你不真正的自己去尝试尝试,你永远不知道市面上的行情如何。这次找工作下来,我自身感觉市场并没有那么可怕,也拿到了几个大厂的offer。在此进行一个总结,给自己,也希望能帮助到需要的同学。

面试准备

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

面试准备根据每个人掌握的知识不同,准备的时间也不一样。现在对于前端岗位,以前也许不是很重视算法这块,但是现在很多公司也都会考。建议大家平时有空的时候多刷刷leetcode。算法的准备时间比较长,是一个长期的过程。需要在掌握了大部分前端基础知识的情况下,再有针对性的去复习算法。面试的时候算法能做出来肯定加分,但做不出来也不会一票否决,面试官也会给你提供一些思路。

background-color: lightpink;
}
#main-wrap {
margin: 0190px0190px;
}
#left {
width: 190px;
height: 200px;
background-color: lightsalmon;
margin-left: -100%;
}
#right {
width: 190px;
height: 200px;
background-color: lightskyblue;
margin-left: -190px;
}
复制代码


tips:上述代码中 margin-left: -100% 相对的是父元素的 content 宽度,即不包含 paddig 、 border 的宽度。


其实以上问题需要掌握 margin 负值问题 即可很好理解。


##### 2.7 水平垂直居中多种实现方式


1. 利用绝对定位,设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 translate 来调整子元素的中心点到父元素的中心。该方法可以不定宽高。



.father {
position: relative;
}
.son {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
复制代码


2. 利用绝对定位,子元素所有方向都为 0 ,将 margin 设置为 auto ,由于宽高固定,对应方向实现平分,该方法必须盒子有宽高。



.father {
position: relative;
}
.son {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0px;
margin: auto;
height: 100px;
width: 100px;
}
复制代码


3. 利用绝对定位,设置 left: 50% 和 top: 50% 现将子元素左上角移到父元素中心位置,然后再通过 margin-left 和 margin-top 以子元素自己的一半宽高进行负值赋值。该方法必须定宽高。



.father {
position: relative;
}
.son {
position: absolute;
left: 50%;
top: 50%;
width: 200px;
height: 200px;
margin-left: -100px;
margin-top: -100px;
}
复制代码


4. 利用 flex ,最经典最方便的一种了,不用解释,定不定宽高无所谓的。



.father {
display: flex;
justify-content: center;
align-items: center;
}
复制代码


其实还有很多方法,比如 display: grid 或 display: table-cell 来做,有兴趣点击下面这篇文章可以了解下:


[面试官:你能实现多少种水平垂直居中的布局(定宽高和不定宽高)](https://bbs.csdn.net/topics/618166371)。


##### 2.8 flex 布局


这一块内容看 [Flex 布局教程](https://bbs.csdn.net/topics/618166371) 就够了。


这里有个小问题,很多时候我们会用到 flex: 1 ,它具体包含了以下的意思:


* flex-grow: 1 :该属性默认为 0 ,如果存在剩余空间,元素也不放大。设置为 1 代表会放大。


* flex-shrink: 1 :该属性默认为 1 ,如果空间不足,元素缩小。


* flex-basis: 0% :该属性定义在分配多余空间之前,元素占据的主轴空间。浏览器就是根据这个属性来计算是否有多余空间的。默认值为 auto ,即项目本身大小。设置为 0% 之后,因为有 flex-grow 和 flex-shrink 的设置会自动放大或缩小。在做两栏布局时,如果右边的自适应元素 flex-basis 设为 auto 的话,其本身大小将会是 0 。


##### 2.9 line-height 如何继承?


* 父元素的 line-height 写了具体数值,比如 30px,则子元素 line-height 继承该值。


* 父元素的 line-height 写了比例,比如 1.5 或 2,则子元素 line-height 也是继承该比例。


* 父元素的 line-height 写了百分比,比如 200%,则子元素 line-height 继承的是父元素 font-size \* 200% 计算出来的值。


### 三、js 基础




---


js 的考察其实来回就那些东西,不过就我自己而已学习的时候理解是真的理解了,但是忘也确实会忘(大家都说理解了一定不会忘,但是要答全的话还是需要理解+背)。


#### 1、数据类型


以下是比较重要的几个 js 变量要掌握的点。


##### 1.1 基本的数据类型介绍,及值类型和引用类型的理解


在 JS 中共有 8 种基础的数据类型,分别为: Undefined 、 Null 、 Boolean 、 Number 、 String 、 Object 、 Symbol 、 BigInt 。


其中 Symbol 和 BigInt 是 ES6 新增的数据类型,可能会被单独问:


* Symbol 代表独一无二的值,最大的用法是用来定义对象的唯一属性名。


* BigInt 可以表示任意大小的整数。


值类型的赋值变动过程如下:



let a = 100;
let b = a;
a = 200;
console.log(b); // 100复制代码





![](https://img-blog.csdnimg.cn/img_convert/7f2ab152d8524b67a4b5b2987e16ef86.webp?x-oss-process=image/format,png)



值类型是直接存储在\*\*栈(stack)\*\*中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;


引用类型的赋值变动过程如下:



let a = { age: 20 };
let b = a;
b.age = 30;
console.log(a.age); // 30复制代码





![](https://img-blog.csdnimg.cn/img_convert/7d4aad9759f04efab0cd7a1c03c609eb.webp?x-oss-process=image/format,png)



引用类型存储在\*\*堆(heap)\*\*中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;


##### 1.2 数据类型的判断


* typeof:能判断所有值类型,函数。不可对 null、对象、数组进行精确判断,因为都返回 object 。



console.log(typeofundefined); // undefinedconsole.log(typeof2); // numberconsole.log(typeoftrue); // booleanconsole.log(typeof"str"); // stringconsole.log(typeofSymbol(“foo”)); // symbolconsole.log(typeof2172141653n); // bigintconsole.log(typeoffunction () {}); // function// 不能判别console.log(typeof []); // objectconsole.log(typeof {}); // objectconsole.log(typeofnull); // object复制代码


* instanceof:能判断对象类型,不能判断基本数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。比如考虑以下代码:



classPeople {}
classStudentextendsPeople {}

const vortesnail = newStudent();

console.log(vortesnail instanceofPeople); // trueconsole.log(vortesnail instanceofStudent); // true复制代码


其实现就是顺着原型链去找,如果能找到对应的 Xxxxx.prototype 即为 true 。比如这里的 vortesnail 作为实例,顺着原型链能找到 Student.prototype 及 People.prototype ,所以都为 true 。


* Object.prototype.toString.call():所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。



Object.prototype.toString.call(2); // “[object Number]“Object.prototype.toString.call(””); // "[object String]"Object.prototype.toString.call(true); // "[object Boolean]"Object.prototype.toString.call(undefined); // "[object Undefined]"Object.prototype.toString.call(null); // "[object Null]"Object.prototype.toString.call(Math); // "[object Math]"Object.prototype.toString.call({}); // "[object Object]"Object.prototype.toString.call([]); // "[object Array]"Object.prototype.toString.call(function () {}); // "[object Function]"复制代码


在面试中有一个经常被问的问题就是:如何判断变量是否为数组?



Array.isArray(arr); // true
arr.proto === Array.prototype; // true
arr instanceofArray; // trueObject.prototype.toString.call(arr); // "[object Array]"复制代码


## 大厂面试题分享 面试题库


#### 前端面试题库 (面试必备) 推荐:★★★★★


地址:[前端面试题库](https://bbs.csdn.net/topics/618166371)



##### 1.3 手写深拷贝


这个题一定要会啊!笔者面试过程中疯狂被问到!


文章推荐:[如何写出一个惊艳面试官的深拷贝?](https://bbs.csdn.net/topics/618166371)



/**

  • 深拷贝
  • @param {Object} obj 要拷贝的对象
  • @param {Map} map 用于存储循环引用对象的地址
    */functiondeepClone(obj = {}, map = newMap()) {
    if (typeof obj !== “object”) {
    return obj;
    }
    if (map.get(obj)) {
    return map.get(obj);
    }

let result = {};
// 初始化返回结果if (
obj instanceofArray ||
// 加 || 的原因是为了防止 Array 的 prototype 被重写,Array.isArray 也是如此Object.prototype.toString(obj) === “[object Array]”
) {
result = [];
}
// 防止循环引用
map.set(obj, result);
for (const key in obj) {
// 保证 key 不是原型属性if (obj.hasOwnProperty(key)) {
// 递归调用
result[key] = deepClone(obj[key], map);
}
}

// 返回结果return result;
}
复制代码


##### 1.4 根据 0.1+0.2 ! == 0.3,讲讲 IEEE 754 ,如何让其相等?


建议先阅读这篇文章了解 IEEE 754 :[硬核基础二进制篇(一)0.1 + 0.2 != 0.3 和 IEEE-754 标准](https://bbs.csdn.net/topics/618166371)。 再阅读这篇文章了解如何运算:[0.1 + 0.2 不等于 0.3?为什么 JavaScript 有这种“骚”操作?](https://bbs.csdn.net/topics/618166371)。


原因总结:


* 进制转换 :js 在做数字计算的时候,0.1 和 0.2 都会被转成二进制后无限循环 ,但是 js 采用的 IEEE 754 二进制浮点运算,最大可以存储 53 位有效数字,于是大于 53 位后面的会全部截掉,将导致精度丢失。


* 对阶运算 :由于指数位数不相同,运算时需要对阶运算,阶小的尾数要根据阶差来右移(0舍1入),尾数位移时可能会发生数丢失的情况,影响精度。


解决办法:


1. 转为整数(大数)运算。



functionadd(a, b) {
const maxLen = Math.max(
a.toString().split(“.”)[1].length,
b.toString().split(“.”)[1].length
);
const base = 10 ** maxLen;
const bigA = BigInt(base * a);
const bigB = BigInt(base * b);
const bigRes = (bigA + bigB) / BigInt(base); // 如果是 (1n + 2n) / 10n 是等于 0n的。。。returnNumber(bigRes);
}
复制代码


这里代码是有问题的,因为最后计算 bigRes 的大数相除(即 /)是会把小数部分截掉的,所以我很疑惑为什么网络上很多文章都说可以通过先转为整数运算再除回去,为了防止转为的整数超出 js 表示范围,还可以运用到 ES6 新增的大数类型,我真的很疑惑,希望有好心人能解答下。


2. 使用 Number.EPSILON 误差范围。



functionisEqual(a, b) {
returnMath.abs(a - b) < Number.EPSILON;
}

console.log(isEqual(0.1 + 0.2, 0.3)); // true复制代码


Number.EPSILON 的实质是一个可以接受的最小误差范围,一般来说为 Math.pow(2, -52) 。


3. 转成字符串,对字符串做加法运算。



// 字符串数字相加var addStrings = function (num1, num2) {
let i = num1.length - 1;
let j = num2.length - 1;
const res = [];
let carry = 0;
while (i >= 0 || j >= 0) {
const n1 = i >= 0 ? Number(num1[i]) : 0;
const n2 = j >= 0 ? Number(num2[j]) : 0;
const sum = n1 + n2 + carry;
res.unshift(sum % 10);
carry = Math.floor(sum / 10);
i–;
j–;
}
if (carry) {
res.unshift(carry);
}
return res.join(“”);
};

functionisEqual(a, b, sum) {
const [intStr1, deciStr1] = a.toString().split(“.”);
const [intStr2, deciStr2] = b.toString().split(“.”);
const inteSum = addStrings(intStr1, intStr2); // 获取整数相加部分const deciSum = addStrings(deciStr1, deciStr2); // 获取小数相加部分return inteSum + “.” + deciSum === String(sum);
}

console.log(isEqual(0.1, 0.2, 0.3)); // true复制代码


这是 leetcode 上一道原题:[面试题库](https://bbs.csdn.net/topics/618166371)。区别在于原题没有考虑小数,但是也是很简单的,我们分为两个部分计算就行。


#### 2、 原型和原型链


可以说这部分每家面试官都会问了。。首先理解的话,其实一张图即可,一段代码即可。



functionFoo() {}

let f1 = newFoo();
let f2 = newFoo();
复制代码


千万别畏惧下面这张图,特别有用,一定要搞懂,熟到提笔就能默画出来。





![](https://img-blog.csdnimg.cn/img_convert/4ed62a19fb5146978ffc39010b05d365.webp?x-oss-process=image/format,png)



总结:


* 原型:每一个 JavaScript 对象(null 除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性,其实就是 prototype 对象。


* 原型链:由相互关联的原型组成的链状结构就是原型链。


先说出总结的话,再举例子说明如何顺着原型链找到某个属性。


推荐的阅读:[JavaScript 深入之从原型到原型链](https://bbs.csdn.net/topics/618166371) 掌握基本概念,再阅读这篇文章[轻松理解 JS 原型原型链](https://bbs.csdn.net/topics/618166371)加深上图的印象。


#### 3、 作用域与作用域链


* 作用域:规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。换句话说,作用域决定了代码区块中变量和其他资源的可见性。(全局作用域、函数作用域、块级作用域)


* 作用域链:从当前作用域开始一层层往上找某个变量,如果找到全局作用域还没找到,就放弃寻找 。这种层级关系就是作用域链。(由多个执行上下文的变量对象构成的链表就叫做作用域链,学习下面的内容之后再考虑这句话)


需要注意的是,js 采用的是静态作用域,所以函数的作用域在函数定义时就确定了。


推荐阅读:先阅读[JavaScript 深入之词法作用域和动态作用域](https://bbs.csdn.net/topics/618166371),再阅读[深入理解 JavaScript 作用域和作用域链](https://bbs.csdn.net/topics/618166371)。


#### 4、 执行上下文


总结:当 JavaScript 代码执行一段可执行代码时,会创建对应的执行上下文。对于每个执行上下文,都有三个重要属性:


* 变量对象(Variable object,VO);


* 作用域链(Scope chain);


* this。(关于 this 指向问题,在上面推荐的深入系列也有讲从 ES 规范讲的,但是实在是难懂,对于应付面试来说以下这篇阮一峰的文章应该就可以了:[JavaScript 的 this 原理](https://bbs.csdn.net/topics/618166371))


#### 5、 闭包


根据 MDN 中文的定义,闭包的定义如下:



> 
>  在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。可以在一个内层函数中访问到其外层函数的作用域。 
>  


也可以这样说:



> 
>  闭包是指那些能够访问自由变量的函数。 自由变量是指在函数中使用的,但既不是 
>  函数参数也不是 
>  函数的局部变量的 
>  变量。 闭包 = 函数 + 函数能够访问的自由变量。 
>  


在经过上一小节“执行上下文”的学习,再来阅读这篇文章:[JavaScript 深入之闭包](https://bbs.csdn.net/topics/618166371),你会对闭包的实质有一定的了解。在回答时,我们这样答:


在某个内部函数的执行上下文创建时,会将父级函数的活动对象加到内部函数的 [[scope]] 中,形成作用域链,所以即使父级函数的执行上下文销毁(即执行上下文栈弹出父级函数的执行上下文),但是因为其活动对象还是实际存储在内存中可被内部函数访问到的,从而实现了闭包。


闭包应用: 函数作为参数被传递:



functionprint(fn) {
const a = 200;
fn();
}

const a = 100;
functionfn() {
console.log(a);
}

print(fn); // 100复制代码


函数作为返回值被返回:



functioncreate() {
const a = 100;

returnfunction () {
console.log(a);
};
}

const fn = create();
const a = 200;
fn(); // 100复制代码


闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方。


应用实例:比如缓存工具,隐藏数据,只提供 API 。



functioncreateCache() {
const data = {}; // 闭包中被隐藏的数据,不被外界访问return {
set: function (key, val) {
data[key] = val;
},
get: function (key) {
return data[key];
},
};
}

const c = createCache();
c.set(“a”, 100);
console.log(c.get(“a”)); // 100复制代码


#### 6、 call、apply、bind 实现


这部分实现还是要知道的,就算工作中不会自己手写,但是说不准面试官就是要问,知道点原理也好,可以扩宽我们写代码的思路。


call



> 
>  call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。 
>  


举个例子:



var obj = {
value: “vortesnail”,
};

functionfn() {
console.log(this.value);
}

fn.call(obj); // vortesnail复制代码


通过 call 方法我们做到了以下两点:


* call 改变了 this 的指向,指向到 obj 。


* fn 函数执行了。


那么如果我们自己写 call 方法的话,可以怎么做呢?我们先考虑改造 obj 。



var obj = {
value: “vortesnail”,
fn: function () {
console.log(this.value);
},
};

obj.fn(); // vortesnail复制代码


这时候 this 就指向了 obj ,但是这样做我们手动给 obj 增加了一个 fn 属性,这显然是不行的,不用担心,我们执行完再使用对象属性的删除方法(delete)不就行了?



obj.fn = fn;
obj.fn();
delete obj.fn;
复制代码


根据这个思路,我们就可以写出来了:



Function.prototype.myCall = function (context) {
// 判断调用对象if (typeofthis !== “function”) {
thrownewError(“Type error”);
}
// 首先获取参数let args = […arguments].slice(1);
let result = null;
// 判断 context 是否传入,如果没有传就设置为 window
context = context || window;
// 将被调用的方法设置为 context 的属性// this 即为我们要调用的方法
context.fn = this;
// 执行要被调用的方法
result = context.fn(…args);
// 删除手动增加的属性方法delete context.fn;
// 将执行结果返回return result;
};
复制代码


apply


我们会了 call 的实现之后,apply 就变得很简单了,他们没有任何区别,除了传参方式。



Function.prototype.myApply = function (context) {
if (typeofthis !== “function”) {
thrownewError(“Type error”);
}
let result = null;
context = context || window;
// 与上面代码相比,我们使用 Symbol 来保证属性唯一// 也就是保证不会重写用户自己原来定义在 context 中的同名属性const fnSymbol = Symbol();
context[fnSymbol] = this;
// 执行要被调用的方法if (arguments[1]) {
result = contextfnSymbol;
} else {
result = contextfnSymbol;
}
delete context[fnSymbol];
return result;
};
复制代码


bind


bind 返回的是一个函数,这个地方可以详细阅读这篇文章,讲的非常清楚:解析 bind 原理,并手写 bind 实现。



Function.prototype.myBind = function (context) {
// 判断调用对象是否为函数if (typeofthis !== “function”) {
thrownewError(“Type error”);
}
// 获取参数const args = […arguments].slice(1),
const fn = this;
returnfunctionFn() {
return fn.apply(
thisinstanceofFn ? this : context,
// 当前的这个 arguments 是指 Fn 的参数
args.concat(…arguments)
);
};
};
复制代码


#### 7、 new 实现


1. 首先创一个新的空对象。


2. 根据原型链,设置空对象的 \_\_proto\_\_ 为构造函数的 prototype 。


3. 构造函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)。


4. 判断函数的返回值类型,如果是引用类型,就返回这个引用类型的对象。



functionmyNew(context) {
const obj = newObject();
obj.proto = context.prototype;
const res = context.apply(obj, […arguments].slice(1));
returntypeof res === “object” ? res : obj;
}
复制代码


## 大厂面试题分享 面试题库


#### 前端面试题库 (面试必备) 推荐:★★★★★


地址:[前端面试题库](https://bbs.csdn.net/topics/618166371)



#### 8、 异步


这部分着重要理解 Promise、async awiat、event loop 等。


##### 8.1 event loop、宏任务和微任务


首先推荐一个可以在线看代码流程的网站:[loupe](https://bbs.csdn.net/topics/618166371)。 然后看下这个视频学习下:[到底什么是 Event Loop 呢?](https://bbs.csdn.net/topics/618166371)


简单的例子:



console.log(“Hi”);

setTimeout(functioncb() {
console.log(“cb”); // cb 即 callback
}, 5000);

console.log(“Bye”);
复制代码


它的执行过程是这样的:





![](https://img-blog.csdnimg.cn/img_convert/50cae125cbdd15d2f7a6cbc14f731932.webp?x-oss-process=image/format,png)



Web APIs 会创建对应的线程,比如 setTimeout 会创建定时器线程,ajax 请求会创建 http 线程。。。这是由 js 的运行环境决定的,比如浏览器。


看完上面的视频之后,至少大家画 Event Loop 的图讲解不是啥问题了,但是涉及到宏任务和微任务,我们还得拜读一下这篇文章:这一次,彻底弄懂 JavaScript 执行机制。如果意犹未尽,不如再读下这篇非常详细带有大量动图的文章:做一些动图,学习一下 EventLoop。想了解事件循环和页面渲染之间关系的又可以再阅读这篇文章:深入解析你不知道的 EventLoop 和浏览器渲染、帧动画、空闲回调(动图演示)。


注意:1.Call Stack 调用栈空闲 -> 2.尝试 DOM 渲染 -> 触发 Event loop。


* 每次 Call Stack 清空(即每次轮询结束),即同步任务执行完。


* 都是 DOM 重新渲染的机会,DOM 结构有改变则重新渲染。


* 然后再去触发下一次 Event loop。


宏任务:setTimeout,setInterval,Ajax,DOM 事件。 微任务:Promise async/await。


两者区别:


* 宏任务:DOM 渲染后触发,如 setTimeout 、setInterval 、DOM 事件 、script 。


* 微任务:DOM 渲染前触发,如 Promise.then 、MutationObserver 、Node 环境下的 process.nextTick 。


从 event loop 解释,为何微任务执行更早?


* 微任务是 ES6 语法规定的(被压入 micro task queue)。


* 宏任务是由浏览器规定的(通过 Web APIs 压入 Callback queue)。


* 宏任务执行时间一般比较长。


* 每一次宏任务开始之前一定是伴随着一次 event loop 结束的,而微任务是在一次 event loop 结束前执行的。


##### 8.2 Promise


关于这一块儿没什么好说的,最好是实现一遍 Promise A+ 规范,多少有点印象,当然面试官也不会叫你默写一个完整的出来,但是你起码要知道实现原理。



> 
>  关于 Promise 的所有使用方式,可参照这篇文章: 
>  [ECMAScript 6 入门 - Promise 对象](https://bbs.csdn.net/topics/618166371)。 手写 Promise 源码的解析文章,可阅读此篇文章: 
>  [从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节](https://bbs.csdn.net/topics/618166371)。 关于 Promise 的面试题,可参考这篇文章: 
>  [要就来 45 道 Promise 面试题一次爽到底](https://bbs.csdn.net/topics/618166371)。 
>  


实现一个 Promise.all:



Promise.all = function (promises) {
returnnewPromise((resolve, reject) => {
// 参数可以不是数组,但必须具有 Iterator 接口if (typeof promises[Symbol.iterator] !== “function”) {
reject(“Type error”);
}
if (promises.length === 0) {
resolve([]);
} else {
const res = [];
let count = 0;
const len = promises.length;
for (let i = 0; i < len; i++) {
//考虑到 promises[i] 可能是 thenable 对象也可能是普通值Promise.resolve(promises[i])
.then((data) => {
res[i] = data;
if (++count === len) {
resolve(res);
}
})
.catch((err) => {
reject(err);
});
}
}
});
};
复制代码


##### 8.3 async/await 和 Promise 的关系


* async/await 是消灭异步回调的终极武器。


* 但和 Promise 并不互斥,反而,两者相辅相成。


* 执行 async 函数,返回的一定是 Promise 对象。


* await 相当于 Promise 的 then。


* tru...catch 可捕获异常,代替了 Promise 的 catch。


#### 9、 浏览器的垃圾回收机制


这里看这篇文章即可:「硬核 JS」你真的了解垃圾回收机制吗。


总结一下:


有两种垃圾回收策略:


* 标记清除:标记阶段即为所有活动对象做上标记,清除阶段则把没有标记(也就是非活动对象)销毁。


* 引用计数:它把对象是否不再需要简化定义为对象有没有其他对象引用到它。如果没有引用指向该对象(引用计数为 0),对象将被垃圾回收机制回收。


标记清除的缺点:


* 内存碎片化,空闲内存块是不连续的,容易出现很多空闲内存块,还可能会出现分配所需内存过大的对象时找不到合适的块。


* 分配速度慢,因为即便是使用 First-fit 策略,其操作仍是一个 O(n) 的操作,最坏情况是每次都要遍历到最后,同时因为碎片化,大对象的分配效率会更慢。


解决以上的缺点可以使用 \*\*标记整理(Mark-Compact)算法 \*\*,标记结束后,标记整理算法会将活着的对象(即不需要清理的对象)向内存的一端移动,最后清理掉边界的内存(如下图)





![](https://img-blog.csdnimg.cn/img_convert/76ab3061aa514e3bbc596b2a16c0dcc7.webp?x-oss-process=image/format,png)



引用计数的缺点:


* 需要一个计数器,所占内存空间大,因为我们也不知道被引用数量的上限。


* 解决不了循环引用导致的无法回收问题。


V8 的垃圾回收机制也是基于标记清除算法,不过对其做了一些优化。


* 针对新生区采用并行回收。


* 针对老生区采用增量标记与惰性回收。


#### 10、 实现一个 EventMitter 类


EventMitter 就是发布订阅模式的典型应用:



exportclassEventEmitter {
private_events: Record<string, Array>;

constructor() {
this._events = Object.create(null);
}

emit(evt: string, …args: any[]) {
if (!this._events[evt]) returnfalse;

const fns = [...this._events[evt]];
fns.forEach((fn) => {
  fn.apply(this, args);
});

returntrue;

}

on(evt: string, fn: Function) {
if (typeof fn !== “function”) {
thrownewTypeError(“The evet-triggered callback must be a function”);
}
if (!this._events[evt]) {
this._events[evt] = [fn];
} else {
this._events[evt].push(fn);
}
}

once(evt: string, fn: Function) {
constexecFn = () => {
fn.apply(this);
this.off(evt, execFn);
};
this.on(evt, execFn);
}

off(evt: string, fn?: Function) {
if (!this._events[evt]) return;
if (!fn) {
this._events[evt] && (this._events[evt].length = 0);
}

let cb;
const cbLen = this._events[evt].length;
for (let i = 0; i < cbLen; i++) {
  cb = this._events[evt][i];
  if (cb === fn) {
    this._events[evt].splice(i, 1);
    break;
  }
}

}

removeAllListeners(evt?: string) {
if (evt) {
this._events[evt] && (this._events[evt].length = 0);
} else {
this._events = Object.create(null);
}
}
}
复制代码


### 四、web 存储




---


要掌握 cookie,localStorage 和 sessionStorage。


#### 1、cookie


* 本身用于浏览器和 server 通讯。


* 被“借用”到本地存储来的。


* 可用 document.cookie = '...' 来修改。


其缺点:


* 存储大小限制为 4KB。


* http 请求时需要发送到服务端,增加请求数量。


* 只能用 document.cookie = '...' 来修改,太过简陋。


#### 2、localStorage 和 sessionStorage


* HTML5 专门为存储来设计的,最大可存 5M。


* API 简单易用, setItem getItem。


* 不会随着 http 请求被发送到服务端。


它们的区别:


* localStorage 数据会永久存储,除非代码删除或手动删除。


* sessionStorage 数据只存在于当前会话,浏览器关闭则清空。


* 一般用 localStorage 会多一些。


### 五、Http




---


前端工程师做出网页,需要通过网络请求向后端获取数据,因此 http 协议是前端面试的必考内容。


#### 1、http 状态码


##### 1.1 状态码分类


* 1xx - 服务器收到请求。


* 2xx - 请求成功,如 200。


* 3xx - 重定向,如 302。


* 4xx - 客户端错误,如 404。


* 5xx - 服务端错误,如 500。


##### 1.2 常见状态码


* 200 - 成功。


* 301 - 永久重定向(配合 location,浏览器自动处理)。


* 302 - 临时重定向(配合 location,浏览器自动处理)。


* 304 - 资源未被修改。


* 403 - 没权限。


* 404 - 资源未找到。


* 500 - 服务器错误。


* 504 - 网关超时。


##### 1.3 关于协议和规范


* 状态码都是约定出来的。


* 要求大家都跟着执行。


* 不要违反规范,例如 IE 浏览器。


#### 2、http 缓存


* 关于缓存的介绍。


* http 缓存策略(强制缓存 + 协商缓存)。


* 刷新操作方式,对缓存的影响。


##### 4.1 关于缓存


什么是缓存? 把一些不需要重新获取的内容再重新获取一次


为什么需要缓存? 网络请求相比于 CPU 的计算和页面渲染是非常非常慢的。


哪些资源可以被缓存? 静态资源,比如 js css img。


##### 4.2 强制缓存





![](https://img-blog.csdnimg.cn/img_convert/df44dcca645746fc92f4a3d997dfc6f0.webp?x-oss-process=image/format,png)



Cache-Control:


* 在 Response Headers 中。


* 控制强制缓存的逻辑。


* 例如 Cache-Control: max-age=3153600(单位是秒)


Cache-Control 有哪些值:


* max-age:缓存最大过期时间。


* no-cache:可以在客户端存储资源,每次都必须去服务端做新鲜度校验,来决定从服务端获取新的资源(200)还是使用客户端缓存(304)。


* no-store:永远都不要在客户端存储资源,永远都去原始服务器去获取资源。


##### 4.3 协商缓存(对比缓存)


* 服务端缓存策略。


* 服务端判断客户端资源,是否和服务端资源一样。


* 一致则返回 304,否则返回 200 和最新的资源。





![](https://img-blog.csdnimg.cn/img_convert/81c8b04616e841d38d2e5ba97d88711e.webp?x-oss-process=image/format,png)



资源标识:


* 在 Response Headers 中,有两种。


* Last-Modified:资源的最后修改时间。


* Etag:资源的唯一标识(一个字符串,类似于人类的指纹)。


Last-Modified:





![](https://img-blog.csdnimg.cn/img_convert/9f15eaa68c3b4a6aae76a4d1bc1880a9.webp?x-oss-process=image/format,png)



服务端拿到 if-Modified-Since 之后拿这个时间去和服务端资源最后修改时间做比较,如果一致则返回 304 ,不一致(也就是资源已经更新了)就返回 200 和新的资源及新的 Last-Modified。


Etag:





![](https://img-blog.csdnimg.cn/img_convert/27d4e9e99d7a4357a4c46dad8320d7fc.webp?x-oss-process=image/format,png)



其实 Etag 和 Last-Modified 一样的,只不过 Etag 是服务端对资源按照一定方式(比如 contenthash)计算出来的唯一标识,就像人类指纹一样,传给客户端之后,客户端再传过来时候,服务端会将其与现在的资源计算出来的唯一标识做比较,一致则返回 304,不一致就返回 200 和新的资源及新的 Etag。


两者比较:


* 优先使用 Etag。


* Last-Modified 只能精确到秒级。


* 如果资源被重复生成,而内容不变,则 Etag 更精确。


##### 4.4 综述





![](https://img-blog.csdnimg.cn/img_convert/51d41b5a54c345ad95645e9347ec156d.webp?x-oss-process=image/format,png)



##### 4.4 三种刷新操作对 http 缓存的影响


* 正常操作:地址栏输入 url,跳转链接,前进后退等。


* 手动刷新:f5,点击刷新按钮,右键菜单刷新。


* 强制刷新:ctrl + f5,shift+command+r。


正常操作:强制缓存有效,协商缓存有效。 手动刷新:强制缓存失效,协商缓存有效。 强制刷新:强制缓存失效,协商缓存失效。


#### 3. 面试


对于更多面试中可能出现的问题,我还是建议精读这篇三元的文章:HTTP 灵魂之问,巩固你的 HTTP 知识体系。


比如会被经常问到的: GET 和 POST 的区别。


* 从缓存的角度,GET 请求会被浏览器主动缓存下来,留下历史记录,而 POST 默认不会。


* 从编码的角度,GET 只能进行 URL 编码,只能接收 ASCII 字符,而 POST 没有限制。


* 从参数的角度,GET 一般放在 URL 中,因此不安全,POST 放在请求体中,更适合传输敏感信息。


* 从幂等性的角度,GET 是幂等的,而 POST 不是。(幂等表示执行相同的操作,结果也是相同的)


* 从 TCP 的角度,GET 请求会把请求报文一次性发出去,而 POST 会分为两个 TCP 数据包,首先发 header 部分,如果服务器响应 100(continue), 然后发 body 部分。(火狐浏览器除外,它的 POST 请求只发一个 TCP 包)


HTTP/2 有哪些改进?(很大可能问原理)


* 头部压缩。


* 多路复用。


* 服务器推送。


关于 HTTPS 的一些原理,可以阅读这篇文章:这一次,彻底理解 https 原理。接着你可以观看这个视频进行更进一步的学习:HTTPS 底层原理,面试官直接下跪,唱征服!


关于跨域问题,大部分文章都是理论性比较强,还不如读这篇文章,[聊聊跨域的原理与解决方法](https://bbs.csdn.net/topics/618166371),讲的非常清晰,我个人觉得对付面试就是先知道使用流程,把这个流程能自己说出来,然后再讲下原理即可。



## 大厂面试题分享 面试题库


#### 前端面试题库 (面试必备) 推荐:★★★★★


地址:[前端面试题库](https://bbs.csdn.net/topics/618166371)



### 六、React




---


#### 1、 React 事件机制,React 16 和 React 17 事件机制的不同


阅读这篇文章即可:[一文吃透 react 事件系统原理](https://bbs.csdn.net/topics/618166371)。


为什么要自定义事件机制?


* 抹平浏览器差异,实现更好的跨平台。


* 避免垃圾回收,React 引入事件池,在事件池中获取或释放事件对象,避免频繁地去创建和销毁。


* 方便事件统一管理和事务机制。


#### 2、class component


不排除现在还会有面试官问关于 class component 的问题。


##### 2.1 生命周期


* 初始化阶段。


发生在 constructor 中的内容,在 constructor 中进行 state 、props 的初始化,在这个阶段修改 state,不会执行更新阶段的生命周期,可以直接对 state 赋值。


* 挂载阶段。



  1. componentWillMount
    发生在 render 函数之前,还没有挂载 Dom2. render
  2. componentDidMount
    发生在 render 函数之后,已经挂载 Dom复制代码

* 更新阶段。


更新阶段分为由 state 更新引起和 props 更新引起。



props 更新时:
1.componentWillReceiveProps(nextProps,nextState)
这个生命周期主要为我们提供对 props 发生改变的监听,如果你需要在 props 发生改变后,相应改变组件的一些 state。在这个方法中改变 state 不会二次渲染,而是直接合并 state。
2.shouldComponentUpdate(nextProps,nextState)
这个生命周期需要返回一个 Boolean 类型的值,判断是否需要更新渲染组件,优化 react 应用的主要手段之一,当返回 false 就不会再向下执行生命周期了,在这个阶段不可以 setState(),会导致循环调用。
3.componentWillUpdate(nextProps,nextState)
这个生命周期主要是给我们一个时机能够处理一些在 Dom 发生更新之前的事情,如获得 Dom 更新前某些元素的坐标、大小等,在这个阶段不可以 setState(),会导致循环调用。
一直到这里 this.props 和 this.state 都还未发生更新
4. render
5.componentDidUpdate(prevProps, prevState)
在此时已经完成渲染,Dom 已经发生变化,state 已经发生更新,prevProps、prevState 均为上一个状态的值。

state 更新时(具体同上)

  1. shouldComponentUpdate
  2. componentWillUpdate
  3. render
  4. componentDidUpdate
    复制代码

* 卸载阶段。



  1. componentWillUnmount
    在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount 中创建的订阅等。componentWillUnmount 中不应调用 setState,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

复制代码


在 React 16 中官方已经建议删除以下三个方法,非要使用必须加前缀:UNSAVE\_ 。



componentWillMount;
componentWillReceiveProps;
componentWillUpdate;
复制代码


取代这两三个生命周期的以下两个新的。



1.staticgetDerivedStateFromProps(nextProps,nextState)
在组件实例化、接收到新的 props 、组件状态更新时会被调用
2. getSnapshotBeforeUpdate(prevProps,prevState)
在这个阶段我们可以拿到上一个状态 Dom 元素的坐标、大小的等相关信息。用于替代旧的生命周期中的 componentWillUpdate。
该函数的返回值将会作为 componentDidUpdate 的第三个参数出现。
复制代码


需要注意的是,一般都会问为什么要废弃三个生命周期,原因是什么。


##### 2.2 setState 同步还是异步


setState 本身代码的执行肯定是同步的,这里的异步是指是多个 state 会合成到一起进行批量更新。 同步还是异步取决于它被调用的环境。


* 如果 setState 在 React 能够控制的范围被调用,它就是异步的。比如合成事件处理函数,生命周期函数, 此时会进行批量更新,也就是将状态合并后再进行 DOM 更新。


* 如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。比如原生事件处理函数,定时器回调函数,Ajax 回调函数中,此时 setState 被调用后会立即更新 DOM 。


#### 3、对函数式编程的理解


总结一下: 函数式编程有两个核心概念。


* 数据不可变(无副作用): 它要求你所有的数据都是不可变的,这意味着如果你想修改一个对象,那你应该创建一个新的对象用来修改,而不是修改已有的对象。


* 无状态: 主要是强调对于一个函数,不管你何时运行,它都应该像第一次运行一样,给定相同的输入,给出相同的输出,完全不依赖外部状态的变化。


纯函数带来的意义。


* 便于测试和优化:这个意义在实际项目开发中意义非常大,由于纯函数对于相同的输入永远会返回相同的结果,因此我们可以轻松断言函数的执行结果,同时也可以保证函数的优化不会影响其他代码的执行。


* 可缓存性:因为相同的输入总是可以返回相同的输出,因此,我们可以提前缓存函数的执行结果。


* 更少的 Bug:使用纯函数意味着你的函数中不存在指向不明的 this,不存在对全局变量的引用,不存在对参数的修改,这些共享状态往往是绝大多数 bug 的源头。


#### 4、react hooks


现在应该大多数面试官会问 hooks 相关的啦。这里我强烈推荐三篇文章,即使没看过源码,也能比较好地理解一些原理:


##### 4.1 为什么不能在条件语句中写 hook


hook 在每次渲染时的查找是根据一个“全局”的下标对链表进行查找的,如果放在条件语句中使用,有一定几率会造成拿到的状态出现错乱。


##### 4.2 HOC 和 hook 的区别


hoc 能复用逻辑和视图,hook 只能复用逻辑。


##### 4.3 useEffect 和 useLayoutEffect 区别


对于 React 的函数组件来说,其更新过程大致分为以下步骤:


1. 因为某个事件 state 发生变化。


2. React 内部更新 state 变量。


3. React 处理更新组件中 return 出来的 DOM 节点(进行一系列 dom diff 、调度等流程)。


4. 将更新过后的 DOM 数据绘制到浏览器中。


5. 用户看到新的页面。


useEffect 在第 4 步之后执行,且是异步的,保证了不会阻塞浏览器进程。 useLayoutEffect 在第 3 步至第 4 步之间执行,且是同步代码,所以会阻塞后面代码的执行。


##### 4.4 useEffect 依赖为空数组与 componentDidMount 区别


在 render 执行之后,componentDidMount 会执行,如果在这个生命周期中再一次 setState ,会导致再次 render ,返回了新的值,浏览器只会渲染第二次 render 返回的值,这样可以避免闪屏。


但是 useEffect 是在真实的 DOM 渲染之后才会去执行,这会造成两次 render ,有可能会闪屏。


实际上 useLayoutEffect 会更接近 componentDidMount 的表现,它们都同步执行且会阻碍真实的 DOM 渲染的。


##### 4.5 React.memo() 和 React.useMemo() 的区别


* memo 是一个高阶组件,默认情况下会对 props 进行浅比较,如果相等不会重新渲染。多数情况下我们比较的都是引用类型,浅比较就会失效,所以我们可以传入第二个参数手动控制。


* useMemo 返回的是一个缓存值,只有依赖发生变化时才会去重新执行作为第一个参数的函数,需要记住的是,useMemo 是在 render 阶段执行的,所以不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴。


##### 4.6 React.useCallback() 和 React.useMemo() 的区别


* useCallback 可缓存函数,其实就是避免每次重新渲染后都去重新执行一个新的函数。


* useMemo 可缓存值。


有很多时候,我们在 useEffect 中使用某个定义的外部函数,是要添加到 deps 数组中的,如果不用 useCallback 缓存,这个函数在每次重新渲染时都是一个完全新的函数,也就是引用地址发生了变化,这就会导致 useEffect 总会无意义的执行。


##### 4.7 React.forwardRef 是什么及其作用


这里还是阅读官方文档来的清晰:[React.forwardRef](https://bbs.csdn.net/topics/618166371)。 一般在父组件要拿到子组件的某个实际的 DOM 元素时会用到。


#### 6、react hooks 与 class 组件对比


[react hooks 与 class 组件对比](https://bbs.csdn.net/topics/618166371) [函数式组件与类组件有何不同](https://bbs.csdn.net/topics/618166371)


#### 7、介绍 React dom diff 算法


[让虚拟 DOM 和 DOM-diff 不再成为你的绊脚石](https://bbs.csdn.net/topics/618166371)。


#### 8、对 React Fiber 的理解


关于这块儿我觉得可以好好阅读下这篇无敌的博客了:Build your own React。 它可以教你一步步实现一个简单的基于 React Fiber 的 React,可以学到很多 React 的设计思想,毕竟为了面试我们可能大多数人是没有时间或能力去阅读源码的了。


然后我们再阅读下其它作者对于 React Fiber 的理解,再转化为我们自己的思考总结,以下是推荐文章: 这可能是最通俗的 React Fiber(时间分片) 打开方式




#### react和vue的比较

相同
1)vitual dom
2)组件化
3)props,单一数据流

不同点
1)react是jsx和模板;(jsx可以进行更多的js逻辑和操作)
2)状态管理(react)
3)对象属性(vue)
4)vue:view——medol之间双向绑定
5)vue:组件之间的通信(props,callback,emit)

>**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

![](https://img-blog.csdnimg.cn/img_convert/2dab3bfe88006b18b4183cbf7cd91c54.webp?x-oss-process=image/format,png)

![](https://img-blog.csdnimg.cn/img_convert/8aa3f6670839e81cc749b0a0ef433b2f.webp?x-oss-process=image/format,png)

unt 的表现,它们都同步执行且会阻碍真实的 DOM 渲染的。


##### 4.5 React.memo() 和 React.useMemo() 的区别


* memo 是一个高阶组件,默认情况下会对 props 进行浅比较,如果相等不会重新渲染。多数情况下我们比较的都是引用类型,浅比较就会失效,所以我们可以传入第二个参数手动控制。


* useMemo 返回的是一个缓存值,只有依赖发生变化时才会去重新执行作为第一个参数的函数,需要记住的是,useMemo 是在 render 阶段执行的,所以不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴。


##### 4.6 React.useCallback() 和 React.useMemo() 的区别


* useCallback 可缓存函数,其实就是避免每次重新渲染后都去重新执行一个新的函数。


* useMemo 可缓存值。


有很多时候,我们在 useEffect 中使用某个定义的外部函数,是要添加到 deps 数组中的,如果不用 useCallback 缓存,这个函数在每次重新渲染时都是一个完全新的函数,也就是引用地址发生了变化,这就会导致 useEffect 总会无意义的执行。


##### 4.7 React.forwardRef 是什么及其作用


这里还是阅读官方文档来的清晰:[React.forwardRef](https://bbs.csdn.net/topics/618166371)。 一般在父组件要拿到子组件的某个实际的 DOM 元素时会用到。


#### 6、react hooks 与 class 组件对比


[react hooks 与 class 组件对比](https://bbs.csdn.net/topics/618166371) [函数式组件与类组件有何不同](https://bbs.csdn.net/topics/618166371)


#### 7、介绍 React dom diff 算法


[让虚拟 DOM 和 DOM-diff 不再成为你的绊脚石](https://bbs.csdn.net/topics/618166371)。


#### 8、对 React Fiber 的理解


关于这块儿我觉得可以好好阅读下这篇无敌的博客了:Build your own React。 它可以教你一步步实现一个简单的基于 React Fiber 的 React,可以学到很多 React 的设计思想,毕竟为了面试我们可能大多数人是没有时间或能力去阅读源码的了。


然后我们再阅读下其它作者对于 React Fiber 的理解,再转化为我们自己的思考总结,以下是推荐文章: 这可能是最通俗的 React Fiber(时间分片) 打开方式




#### react和vue的比较

相同
1)vitual dom
2)组件化
3)props,单一数据流

不同点
1)react是jsx和模板;(jsx可以进行更多的js逻辑和操作)
2)状态管理(react)
3)对象属性(vue)
4)vue:view——medol之间双向绑定
5)vue:组件之间的通信(props,callback,emit)

>**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

[外链图片转存中...(img-wKglWoMG-1714992171161)]

[外链图片转存中...(img-3BdWeNIs-1714992171162)]

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2024前端面试题可能会涉及以下几个方面的内容: 1. HTML/CSS基础知识:包括HTML标签的使用、CSS选择器、盒模型、浮动、定位等基本概念和常见问题。 2. JavaScript基础知识:包括数据类型、变量、运算符、流程控制语句、函数、作用域、闭包等基本概念和常见问题。 3. 前端框架和库:例如React、Vue等,可能会涉及到它们的基本原理、生命周期、组件通信等方面的问题。 4. 前端性能优化:包括减少HTTP请求、压缩和合并文件、使用CDN加速、懒加载、缓存等方面的知识。 5. 前端工程化:包括模块化开发、构建工具(如Webpack)、版本控制(如Git)、自动化测试等方面的知识。 6. 前端安全:包括XSS攻击、CSRF攻击、点击劫持等常见安全问题及其防范措施。 7. 前端跨域问题:包括同源策略、跨域请求的方法(如JSONP、CORS等)以及解决跨域问题的方案。 8. 移动端开发:包括响应式设计、移动端适配、触摸事件、移动端性能优化等方面的知识。 9. Web标准和浏览器兼容性:包括HTML5、CSS3的新特性以及不同浏览器之间的差异和兼容性问题。 10. 数据可视化:包括使用图表库(如Echarts、D3.js)进行数据可视化的基本原理和常见问题。 以上只是一些可能涉及到的内容,具体的面试题目还会根据面试官的要求和公司的需求而有所不同。在准备面试时,建议多一些实际项目练习,加深对前端知识的理解和应用能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值