前端八股文学习笔记
Javascript
EventLoop 微任务与宏任务
参考资料: 深入浅出 JavaScript 事件循环与异步编程技巧 一文详解Event Loop事件循环、微任务、宏任务
1.调用栈
调用栈是 JavaScript 执行同步代码的地方。它是一个后进先出的数据结构,用来记录函数的调用顺序。每当一个函数被调用时,它就会被推入调用栈;当函数执行完毕后,它就会从调用栈中弹出。
例如:
//eloop1.js
function foo() {
console.log('foo');
}
function bar() {
foo();
console.log('bar');
}
bar();
// foo
// bar
2.任务队列
任务队列用于存放异步任务的回调函数。当异步任务(如 setTimeout、setInterval、I/O 操作等)完成时,它们的回调函数会被放入任务队列中,等待事件循环处理。
具体的来说,异步任务又分为宏任务(Macrotask)和微任务(Microtask)
宏任务
宏任务是事件循环中的主要任务单元。每次事件循环会执行一个宏任务,然后检查并执行所有的微任务。
常见宏任务有:
| 宏任务类型 | 具体形态 |
|---|---|
| 定时器操作 | setTimeout,setInterval |
| I/O 操作 | 文件读写、网络请求等 |
| UI 渲染 | 浏览器中的 UI 更新。 |
| 事件回调 | click、scroll 等 DOM 事件 |
requestAnimationFrame | 浏览器中的动画帧回调(优先级高于普通宏任务) |
setImmediate | (Node.js 特有):在当前事件循环结束后立即执行。 |
微任务
微任务队列用于存放优先级更高的异步任务回调,微任务会在当前任务执行完毕后立即执行且会在下一个宏任务开始之前清空。
| 微任务类型 | 具体形态 |
|---|---|
Promise的回调 | then,catch,finally |
async / await | 异步方法 |
queueMicrotask | 将任务推入微队列的API |
MutationObserver | 监听DOM变化的回调 |
3.事件循环的流程
-
执行同步代码 :从调用栈里执行同步任务
-
异步任务压入任务队列:遇到宏/微任务,加入对应的任务队列
-
清空微任务队列:循环执行所有微任务(如 Promise 的回调)。
-
执行一个宏任务:从宏任务队列中取出一个宏任务执行。
参考示例
//eloop2.js
console.log('Script start');
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve(
console.log("Behind Promise")
).then(() => {
console.log('Promise');
});
console.log('Script end');
// Script start
// Behind Promise
// Script end
// Promise
// Timeout
4.不得不品的易错点!
Promise本身是一个同步的代码(只是容器),只有它后面调用的then()方法里面的回调才是微任务
new Promise(resolve =>{
console.log('111')
resolve()
}).then(function(){
console.log("222")
})
console.log('333')
// 111
// 333
// 222
await右边的第一个表达式还是会立即执行,表达式之后的代码才是微任务
console.log(1) //1
async function async1() {
await async2()//3
console.log(2)//6
await async3()//7
console.log(3)
}
async function async2() {
console.log(4)//4
}
async function async3() {
console.log(5)//8
}
async1()//2
console.log(6)//5
// 1 4 6 2 5 3
5.例题
解析:
1.先执行主线程上的同步代码,打印1
2.执行第9行的函数,进⼊async1内部,async1其实是声明了⼀个promise,promise是同步代码,会顺序执⾏打印async2函数里的4 ,只有.then⾥⾯的代码会加⼊微任务队列⾥,这⾥相当于执⾏了async2()之后,再将后面的代码加⼊⼀个微任务队列中。
3.回主线程中,遇到setTimeout(),加⼊到宏任务队列
4.主线程继续往后执⾏,前⾯说过,promise是同步代码,.then后⾯的回调会加⼊微任务队列,所以会打印13⾏的7
5.主线程执⾏完成,开始执⾏微任务队列内的任务,遵循先进先出的原则,打印第四⾏的2。然后接着执行第5行第二个awaite右边的代码,打印5。第6行这个时候就被加入微任务队列。
6.接着会执行第二个微任务,也就是16行代码,打印8。第17行的then这个时候也会加入微任务队列。再依次执行第6行和第17行的两个微任务,打印3和9
7.微任务执⾏结束,开始执⾏宏任务setTimeout,打印11⾏的6.
6.总结
所有同步任务都在主线程上执行,形成一个执行栈(call stack)。
遇到异步任务, 进入异步处理模块并注册回调函数; 等到指定的事件完成(如ajax请求响应返回, setTimeout延迟到指定时间)时,异步处理模块会将这个回调函数移入异步任务队列。
当栈中的代码执行完毕,执行栈中的任务为空时,主线程会先检查微任务队列中是否有任务,如果有,就将微任务队列中的所有任务依次执行,直到微任务队列为空; 之后再检查宏任务队列中是否有任务,如果有,则取出第一个宏任务加入到执行栈中,之后再清空执行栈,检查微任务,以此循环,直到全部的任务都执行完成。
本地化存储
参考资料 深度解析本地化存储方案与项目实战经验 前端知识库
Cookies
Cookies是什么
Cookie 是浏览器和服务器之间传递小型数据的一种机制。它通常用于存储客户端(如浏览器)的一些信息,这些信息会在随后的请求中自动发送给服务器,帮助服务器记住用户的状态或者行为。例如,可以用 Cookie 记录用户的登录状态、购物车内容等。 特点:
- 容量小:每个Cookie通常只能存储4KB左右的数据。
- 跨浏览器支持:几乎所有现代浏览器都支持Cookies。
- 自动发送:浏览器在每次HTTP请求中都会自动发送与网站相关的Cookies。
Cookies在HTTP中的使用
在 HTTP 请求和响应中,Cookie 的使用主要通过 HTTP 头部来进行。
-
服务器设置 Cookie: 当服务器希望在浏览器中存储一些信息时,它会通过 HTTP 响应头中的
Set-Cookie字段来设置 Cookie。HTTP/1.1 200 OK Content-Type: text/html Set-Cookie: userId=12345; Expires=Thu, 01 Jan 2026 00:00:00 GMT; Path=/ -
客户端发送 Cookie:当浏览器向服务器发送请求时,浏览器会自动将存储的 Cookie 通过 Cookie 头发送给服务器。例如:
GET /index.html HTTP/1.1 Host: www.example.com Cookie: userId=12345Cookie 的生命周期
- 会话 Cookie:没有设置
Expires或Max-Age的 Cookie 是会话 Cookie,它仅在当前浏览器会话有效,浏览器关闭时会自动删除。 - 持久 Cookie:设置了
Expires或Max-Age,则该 Cookie 是持久化的,直到指定的时间点才会过期。
Cookies的设置
可以通过 document.cookie 来设置 Cookie。基本语法如下:
document.cookie = 'name=value; expires=expiration_date; path=path; domain=domain; secure';
name=value:Cookie 的键值对。expires:指定过期时间,必须是有效的 UTC 格式。如果不设置,则默认为会话 Cookie。path:指定路径,默认为当前页面路径。domain:指定 Cookie 的有效域名。(解决跨域问题)secure:表示该 Cookie 只通过 HTTPS 协议发送。
Cookies的安全性
-
HttpOnly:如果设置了HttpOnly标志,该 Cookie 不能被 JavaScript 访问,避免 XSS 攻击窃取 Cookie 中的敏感信息。 -
Secure:如果设置了Secure标志,Cookie 只能通过 HTTPS 连接发送,避免在 HTTP 连接中泄漏。 -
SameSite:SameSite属性可以防止跨站请求伪造攻击。它有三种值:
-
Strict:严格限制,只有在相同站点内的请求中才会发送 Cookie。
-
Lax:宽松限制,允许部分跨站请求(如通过链接跳转)。
-
None:没有限制,允许跨站请求发送 Cookie,但需要同时设置 Secure。
Cookie的问题
- 储存空间限制,Cookie至多4kb左右
- 性能问题,Cookie塞在http请求头中会拖慢请求速度
- 跨域问题,Cookie只能在同域名下收发,可以通过设置
domain解决 - Cookie 会在客户端存储一些信息,因此容易受到各种安全攻击,可以通过设置好
secure,samesite属性来解决
Cookies总结
Cookie 是一种用于存储客户端信息的小型数据,通常用于保持用户会话、保存用户偏好设置或跟踪用户行为。它通过 HTTP 头部在客户端与服务器之间传递,可以是会话 Cookie(在浏览器会话期间有效)或 持久 Cookie(有明确的过期时间)。 虽然 Cookie 使用广泛,但它也存在一些缺点,如存储空间有限、性能问题、安全性隐患、隐私问题和跨域限制等。 因此,在使用 Cookie 时,需要考虑适当的安全措施并合理管理其生命周期。
localStorage
localStorage是什么
LocalStorage 是一种 Web 存储机制,允许在客户端(即浏览器)以键值对的形式存储数据。 与 Cookie 相比,LocalStorage 提供了更大的存储空间(通常为 5MB 或更多),并且数据存储在浏览器中。 即使关闭浏览器或重新启动电脑,数据也会保持直到被手动删除或过期。 特点:
- 容量大:允许每个域名存储5MB甚至更多
- 永久存储:除非主动删除,否则存储的数据会在浏览器关闭后仍然存在,直到明确地被删除。
- 数据隔离:LocalStorage 中的数据按 域名 隔离,不同域名间的数据无法访问。
- 同步操作:LocalStorage 的访问是 同步的,每次操作都会立即影响存储的数据。
- 安全性好:与Cookie相比,localStorage不会主动发送到服务器
localStorage的使用
LocalStorage 使用 键值对 存储数据,你可以通过 localStorage 对象的 API 来设置、获取、删除数据。
我们可以使用 localStorage.setItem(key, value) 方法存储数据。
这里的 key 是数据的键,value 是存储的数据。
值必须是字符串,如果要存储非字符串数据,需要先将其转换为字符串(通常使用 JSON.stringify())。
// 存储数据
localStorage.setItem('username', 'JohnDoe');
localStorage.setItem('age', '30');
通过 localStorage.getItem(key) 方法来读取存储的数据。它会返回该 key 对应的值,如果该键不存在,则返回 null。
使用 localStorage.removeItem(key) 来删除指定的键值对。
使用 localStorage.clear() 清除存储在 LocalStorage 中的所有数据。
// 存储对象
const user = {
username: 'JohnDoe',
age: 30,
};
// 使用 JSON.stringify 转换对象为字符串
localStorage.setItem('user', JSON.stringify(user));
// 读取对象并使用 JSON.parse 转换回原对象
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.username); // "JohnDoe"
console.log(storedUser.age); // 30
LocalStorage的缺点
- 只能存储字符串:LocalStorage 只能存储字符串类型的数据,其他类型的数据需要进行序列化(
JSON.Stringify)。 - 同步操作:LocalStorage 是同步的,可能会对性能产生影响,尤其是当存储的数据量较大时。
- 同源策略限制:LocalStorage 中的数据是 按域名隔离 的,无法在不同域之间共享数据。
- 没有过期时间:LocalStorage 中的数据不会自动过期,因此需要手动管理存储数据的生命周期。如果不手动清理过期数据,可能会造成存储空间浪费。
- 不适用于敏感信息:LocalStorage 数据容易被浏览器中的脚本访问,因此不适合存储敏感的用户数据(如密码、令牌等)。它不具备像 Cookie 的 HttpOnly、Secure 和 SameSite 等安全特性。
localStorage总结
LocalStorage 是一种客户端存储机制,允许在浏览器中存储数据,具有较大的存储空间(通常为 5MB 或更多),并且数据会在浏览器关闭后保持,直到被手动删除。 它非常适合用于持久化存储用户数据、缓存数据和管理 SPA(单网页应用) 的状态。但需要注意的是,LocalStorage 存储的数据容易受到 XSS 攻击,因此不适合存储敏感数据. 同时,它的数据存储是同步的,可能会影响性能。在使用时,要谨慎选择合适的数据存储方式,确保数据的安全性和有效管理。
SessionStorage
SessionStorages是什么
SessionStorage 提供了一种简单的方式来在浏览器会话中存储数据,且该数据是 仅限于当前会话 的。 每个浏览器标签页或窗口都有独立的 SessionStorage,彼此之间的数据不能共享。SessionStorage 与 LocalStorage 类似,但它的生命周期更加短暂。 特点:
- 会话级存储:数据只在当前浏览器会话期间有效。会话结束时,数据会自动清除。
- 每个标签页独立:每个浏览器标签页或窗口都有独立的 SessionStorage,标签页之间不能共享 SessionStorage 数据。
- 存储容量:SessionStorage 的存储空间通常与 LocalStorage 相同,通常为 5MB 或更多,具体取决于浏览器。
- 同步 API:SessionStorage 的 API 是同步的,意味着每次读取、存储或删除数据时,操作会立即完成。
sessionStorage的使用
语法同localStorage
localStorage VS sessionStorage 你们知道吗
| 特性 | SessionStorage | LocalStorage |
|---|---|---|
| 存储容量 | 5MB 或更多,取决于浏览器 | 5MB 或更多,取决于浏览器 |
| 生命周期 | 仅在当前会话(标签页)有效,会话结束时删除 | 永久存储,直到显式删除 |
| 数据隔离性 | 不同标签页、不同窗口的数据隔离 | 同一域下的数据共享 |
| 存储方式 | 键值对,字符串 | 键值对,字符串 |
| 传输到服务器 | 不会随每个请求自动发送 | 会随每个 HTTP 请求自动发送到服务器 |
| API 异步性 | 同步 API | 同步 API |
| 适用场景 | 临时存储会话信息,如用户登录状态、购物车等 | 长期存储用户设置、偏好、浏览历史等 |
sessionStorage总结
sessionStorage 一般用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。
JavaScript数组Array
参考资料:
菜鸟教程
Array的描述
在 JavaScript 中,数组不是原始类型,而是具有以下核心特征的Array对象(引用类型):
-
JavaScript 数组是可调整大小的,并且可以包含不同的数据类型。(当不需要这些特征时,可以使用类型化数组。)
-
JavaScript 数组不是关联数组,因此,不能使用任意字符串作为索引访问数组元素,但必须使用非负整数(或它们各自的字符串形式)作为索引访问。
-
JavaScript 数组的索引从 0 开始:数组的第一个元素在索引 0 处,第二个在索引 1 处,以此类推,最后一个元素是数组的 length 属性减去 1 的值。
-
JavaScript 数组复制操作创建浅拷贝。(所有 JavaScript 对象的标准内置复制操作都会创建浅拷贝,而不是深拷贝)。
关于深浅拷贝 浅拷贝(Shallow Copy) 浅拷贝创建一个新对象,但只复制原始对象的引用类型字段的地址,而不复制引用指向的对象本身。这意味着新对象和原始对象的引用类型字段将指向相同的对象。如果原始对象中的字段是基本数据类型,则这些字段的值会被复制到新对象中。由于引用类型字段共享,对新对象的修改可能会影响原始对象。 深拷贝(Deep Copy) 深拷贝则创建一个全新的对象,并递归地复制原始对象的所有字段,包括引用指向的对象。这种方法会复制整个对象结构,确保新对象和原始对象之间的所有关系都是独立的。因此,对新对象的修改不会影响到原始对象。
Array的数组索引
Array 对象不能使用任意字符串作为元素索引(如关联数组),必须使用非负整数(或它们的字符串形式)。 JavaScript 语法要求使用方括号表示法来访问以数字开头的属性,形如:
let arr = ['a','b','c'];
console.log(arr[0])
//或者
console.log(arr['0'])
注意: JavaScript 引擎通过隐式的toString,将 years[2] 中的 2 强制转换为字符串。因此,‘2’ 和 ‘02’ 将指向 years 对象上的两个不同的槽位。
只有years['2']是一个实际的数组索引。而years['02']是一个在数组迭代中不会被访问的任意字符串属性。
Array的数组方法
1. 检测方法
-
Array.isArray()判断传入的值是否是一个数组。// true Array.isArray([1, 2, 3]) // false Array.isArray({foo: 123}) // false Array.isArray('foobar') // false Array.isArray(undefined) -
typeofconsole.log(typeof [1, 2, 3, "a"]); // "object" -
Object.prototype.toString.call()适用于所有类型的详细判断console.log(Object.prototype.toString.call([1, 2, 3, "a"])); // "[object Array]" -
constructor属性let i = [1, 2, 3, "a"]; console.log(i.constructor.name); // "Array"
2. 创建数组方法
-
Array.from()Array.from()方法用于将类数组对象和可迭代对象转为真正的数组,并且返回一个新的,浅拷贝的数组实例。// 报错 Array.from(undefined) // 报错 Array.from(null) // ["f", "o", "o"] console.log(Array.from('foo')) // [] console.log(Array.from('')) // [] console.log(Array.from(123)) // [] console.log(Array.from(NaN)) -
Array.of()Array.of()创建一个包含所有传入参数的数组,不考虑参数的数量或类型,返回一个新数组。Array.of() // [] Array.of(undefined) // [undefined] Array.of(null) // [null] Array.of(NaN) // [NaN] Array.of(1) // [1] Array.of(1, 2) // [1, 2] Array.of([1,2,3]) // [[1,2,3]] Array.of({id: 1},{id: 2}) // [{id:1}, {id:2}]
3. 遍历(迭代)方法
-
forEach()对数组中的每一项运行指定的函数。这个方法返回undefined,即使你return了一个值。-
Array.forEach()参数语法:
element index array 当前元素 当前元素的索引 数组本身 -
第二个参数(可选):当执行回调函数时用作 this 的值。
-
注意: Array.forEach()不能中断循环(使用break,或continue语句)。只能用return退出本次回调,进行下一次回调。
const arr = [{id: 1,name: 'zhangsan'},{id: 2,name: 'lisi'}]
// 1 - zhangsan
// 2 - lisi
arr.forEach(el => {
console.log(`${el.id} - ${el.name}`);
});
const obj = {
handle: function(n){
return n + 2
}
};
// true
[{id: 1,name: 'zhangsan'},{id: 2,name: 'lisi'}].forEach(function(el,index,arr){
if(el.id === 1) {
return
}
console.log(this === obj)
},obj);
-
map()返回一个新数组,结果是该数组中的每个元素都调用提供的函数后返回的结果。-
Array.map()参数语法:
参数 描述 element 当前元素 index 当前元素的索引 (可选) array 数组本身 (可选) -
第二个参数(可选):当执行回调函数时用作 this 的值。
-
const arr = [{id: 1},{id: 2},{id: 3}]
const newArr = arr.map((el,index,arr) => {
el.age = 20
return el
});
// [{id: 1,age: 20},{id: 2,age: 20},{id: 3,age: 20}]
console.log(newArr);
-
filter()对数组中的每一项运行指定的函数,返回该函数会返回true的项组成的新的数组。如果没有任何数组元素通过测试,则返回空数组。-
Array.filter()参数语法:
参数 描述 element 当前元素 index 当前元素的索引 (可选) array 数组本身 (可选) -
第二个参数(可选):当执行回调函数时用作 this 的值。
const arr = [{id: 1},{id: 2},{id: 3}] const newArr = arr.filter((el,index,arr) => { el.age = 20 return el }); // [{id: 1,age: 20},{id: 2,age: 20},{id: 3,age: 20}] console.log(newArr);
-
-
some()检测数组中的是否有满足判断条件的元素。
对数组中的每一项运行指定的函数,如果该函数对任一项返回true,则返回true,并且剩余的元素不会再执行检测。如果没有满足条件的元素,则返回false。
-
Array.some()参数语法:
参数 描述 element 当前元素 index 当前元素的索引 (可选) array 数组本身 (可选) -
第二个参数(可选):当执行回调函数时用作 this 的值。
const arr = [{id: 1},{id: 2},{id: 3}] const someResult = arr.some((el,index,arr) => { return el.id === 1 }); // true console.log(someResult)every()检测数组所有元素是否都符合判断条件。
对数组中的每一项运行指定的函数,如果该函数对每一项都返回true,则返回true。若收到一个空数组,此方法在一切情况下都会返回true。如果数组中检测到有一个元素不满足,则返回 false,且剩余的元素不会再进行检测。
JavaScript中比较常考的点
数据类型
-
在Number与String运算时,只有
+操作时Number会隐式转换为String,其余情况下都是String隐式转换为Number -
在JavaScript中,数据类型分两大类: 原始类型(Primitive Types):
类型名 类型英文 备注 字符串 String 数字 Number 布尔值 Boolean 未定义 Undefined 空值 Null 使用 typeof操作符的结果是”Object”符号 Symbol ES6新特性 大整数 BigInt ES6新特性 引用类型(Reference Types):
类型名 类型英文 备注 对象 Object 数组 Array 函数 Function 日期 Date 正则表达式 RegExp 错误 Error