【2024前端复盘复习计划】Vue篇
【2024前端复盘复习计划】打包优化篇
【2024前端复盘复习计划】网络协议和浏览器篇
一、try...catch 可以捕获到异步代码中的错误吗
- 当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在 try-catch 所在的栈,所以 try-catch 并不能捕获到错误
- 对于微任务而言,比如 promise,promise 的构造函数的异常只能被自带的 reject 也就是.catch 函数捕获到
解决:使用promise或async await或异步代码内部使用 try..catch
二、Promise
promise三个状态:进⾏中(pending)、已完成(fulfilled)、已拒绝(rejected)
Promise 是 ES6 新增的语法,异步编程的一种解决方案,解决了回调地狱的问题,
(一) API
1. Promise.then()
构造函数中的 resolve 或 reject 只有第一次执行有效, 多次调用没有任何
- 返回值:如果 then 的返回值是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中。
- 异常:如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中。
- 参数:
- 第一个参数(onFulfilled) :resolved时调用的回调函数,当传入非函数时,忽略非函数值,就将值传递给下一个then
- 第二个参数(onRejected) :rejected时调用的回调,和catch区别:onRejected和catch捕获错误信息的时候会就近原则,如函数当两者都存在时,则只有then的第二个参数能捕获到,如果then的第二个参数不存在,则catch方法会捕获到。如在onFulfilled抛出异常则只能后面的catch能捕获到
值传透例子:链式调用的时,当 then方法参数传入非函数时,忽略非函数值,就将值传递给下一个then
Promise.resolve(1)
.then(2)
.then((data) => {
console.log(data) // 1
})
2. Promise.resolve(value)
Promise.resolve()时,相当于new Promise(resolve => {resolve()})
- Thenable 对象(具有 then 方法的对象): Promise.resolve 会返回一个新的 Promise 实例,这个新 Promise 的状态将会跟随传入的 Thenable 对象的状态改变。
- 如果传入Promise 实例:将返回与 value 相同的对象引用,如果传入的是一个已经存在的 Promise,那么 Promise.resolve() 会直接返回这个 Promise,不会创建新的 Promise 实例。
- 其他值(非 Promise 和非 Thenable) :除上述两种情况之外的任何其他值包括基本类型、对象或函数等,Promise.resolve() 方法会返回一个新的 Promise 对象,该 Promise 的状态为 resolved,并且其 resolve 值就是传入的 value。
3. Promise.all
- 数组中所有的 Promise 都成功时时才成功,如果其中任何一个 Promise 失败,那么返回的 Promise 也会立即失败。
- 如何成功,返回的值是一个包含所有 Promise 结果的数组,结果的顺序与传入的 Promise 数组一致。
- 如果失败,返回的值是第一个失败的 Promise 的值
4. Promise.race
- 数组中的任意一个 Promise 成功或失败时就会完成。
- 将第一个成功或失败的 Promise 的结果作为自己的结果。换句话说,race 方法会返回最快成功或失败的 Promise 的结果。
5. promise.any()
- 只要其中任意一个 promise 成功,any 方法就返回那个已经成功的 promise。
- 如果数组中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和错误集合。
(二) 执行顺序
- Promise构造函数内部执行的代码是同步的。
三、for...in和for...of有什么区别
可迭代:也就是可以通过迭代器进行遍历的类型,例如数组、字符串、Set、Map、类数组、Generator。对象(Object)本身不是可迭代类型。
可枚举:对象的每个属性都有一个可枚举属性(enumerable),默认情况下,大多数内置对象的自有属性(非原型链上的属性)是可枚举的,可枚举性关注的是对象的属性是否能被显式列举
- for...in 循环主要用于遍历对象的属性,它会将对象的每个可枚举属性作为迭代变量 key 的值。不仅会遍历对象自身的属性,还会遍历其原型链上的属性。遍历的是对象的键名,如是数组则获取的是下标
- for...of 循环是一种用于遍历可迭代对象(如数组、字符串、Map、Set 等)的迭代方式,且保证按照对象中元素的顺序进行迭代,可直接访问可迭代对象的元素值
- 总的来说:for in 遍历对象而生 for of遍历数组
- Date对象会变成格式化时间字符串(可以再次new Date创建对象);
- RegExp对象正则会变成空对象{};
- 函数会丢失;
- undefined会丢失;
- NaN、Infinity、-Infinity会变成null;
- Symbol会丢失;
- BigInt会抛出异常Do not know how to serialize a BigInt(可以调用BigInt.prototype.toString转为字符串);
- Set、Map会变成空对象{};
- 不可枚举属性会丢失;
- 循环引用会抛出异常。
四、链式调用的实现方式
关键字return this
class Person {
#info = {};
constructor(name) {
this.#info.name = name;
}
getInfo() {
return this.#info;
}
setAge(age) {
this.#info.age = age;
return this;
}
}
// new Person('Bob').setAge(18).getInfo()
五、类型转换与== 比较
(一) 类型转换
类型 | 转Boolean结果 | 转Number | 转String |
undefined | false | NaN | "undefined" |
null | false | 0 | "null" |
Boolean | - | true转换1,false转换为+0 | "true" 或 "false" |
Number | 值为+0/-0或者NaN时:false 其他:true | - | 用字符串表示数字 |
String | 空字符串:false (除开' '里面有空格)其他:true | 字符串解析为数字:对应数字其他:NaN | 返回argument |
Symbol | true | TypeError 异常 | TypeError 异常 |
Object | 所有对象类型都直接转换为true | ToPrimitive(obj, Number)获取原始值,再进行转换 | ToPrimitive(argument, String)获取原始值,再进行转换 |
object例子
原始值 | Boolean | Number | String |
[] | true | 0 | "" |
[10] | true | 10 | "10" |
[10,11] | true | NaN | "10,11" |
function(){} | true | NaN | "function(){}" |
{} | true | NaN | "object Object" |
(二) valueOf方法和toString方法
valueOf:
- Number、Boolean、String通过valueOf转换后会变成相应的原始值
- Date类型,valueOf函数将日期转换为毫秒的形式
- Math 和 Error 对象没有 valueOf 方法
- 其他返回对象本身
toString(和String()区别):
- undefined和null没有此方法
- toString方法可以填写一个参数,该参数为一个进制数
(三) ToPrimitive(value,PreferredType?)方法
value式需要转换的值,PreferredType 来选择类型。
如果第二个参数为空并且 v 为 Date的实例,此时preferredType 会被设置为String,其他情况下preferredType都会被设置为 Number
如果PreferredType为Number则执行以下步骤,如果为String则2,3步骤对调
- 如果是基本类型,不进行转换
- 否则,调用valueOf方法,如果得到原始值,则返回
- 如果 valueOf 方法不存在,或者它返回的仍然是一个对象,调用toString方法,如果得到原始值,则返回
- 否则,报错
(四) ==比较
- NaN不等于任何其它类型
- Boolean 与其它类型进行比较,Boolean转换为Number
- String 与 Number进行比较,String 转化为Number
- null 与 undefined进行比较结果为true
- null,undefined与其它任何类型进行比较结果都为false
- 引用类型与值类型进行比较,引用类型先转换为值类型(调用[ToPrimitive])
- 引用类型与引用类型,直接判断是否指向同一对象
[] == ![]
- !优先级高于==,因此先执行![] , !的执行步骤是将该对象先转换成布尔类型然后进行去非
- []转换成布尔,任何引用类型转布尔都是true,所以!true就等于false ( [] ==false)
- Boolean 与其它类型进行比较,都先转换Number, []转换成调用ToPrimitive,数组为对象先调用valueOf无原始值,继续调用toString原始值获得' ' 此时 ( ' ' ==0 )
- String 与 Number进行比较,String 转化为Number,' '转换Number得到0,)(0 ==0)
(五) 四则运算
- 两个操作数都为数字时直接运行加法操作
- 若有一方为字符串,则将两个操作数都转换成字符串,进行字符串拼接操作。
- undefined + undefined 进行加法运算,结果为 NaN
- 都不是数字、字符串这种原始类型的值,他们自身会先执行.valueOf()方法,如果.valueOf()返回的仍然不是原始类型的值,就会再次执行.toString()方法,最终得到原始类型的值,如果还得不到就会报错
{}+[]//0
[]+{}//"[object Object]"
//{} + [] 只是调换了下顺序,为什么结果却是0呢,因为开头的 {} ,被js误认为是代码片段的花括号,而不是对象的花括号,所以 {} + []实际执行的是 +[],所以经过转换结果就是0了
六、call、apply、bind
- bind: 不会立即调用,返回一个绑定后的新函数, 需要手动调用才会执行 。
- call:立即调用,返回函数执行结果,this指向第一个参数, 后面的参数直接传递给被调用函数(需要一个一个地列举) ,适用于参数少的情况。
- apply:立即调用,返回函数的执行结果,this指向第一个参数,第二个参数是个数组或类数组对象,这个数组里内容是fn函数的参数,适用于参数很多的情况
七、判断数据类型
(一) instanceof
- 检查对象是否是某个特定类型的实例,会一直往上寻找,直到原型链顶端,若无返回false
- 不能检测基本数据类型
- 原型链可能被修改,结果不准确
(二) constructor
- 实例通过constrcutor 对象访问它的构造函数。不建议使用
- null,undefined 没有构造函数,自然也就访问不到该属性
- constructor 可以被改写,所以不一定准确
(三) typeof
- 能准确的判断出基础类型(非null)和函数类型
- 数组,日期,普通对象、null 都返回object
(四) Object.prototype.toString.call
- 返回的是一个[object xxxx]字符串,第8位做一个截取获取数据类型
(五) isArray()判断是否数组
(六) 判断NaN的方法
由于NaN 是 number 类型,所以不能直接区分出 NaN 和普通数字
- Number.isNaN()是ES6新增的方法,可以判断传入的参数是否为Number类型并且为NaN,返回boolean
- isNaN()是挂载在全局变量下的方法,检查值是否不是一个普通数字或者是否不能转换为一个普通数字(存在隐式转换)。比如isNaN(new Date)返回false,isNaN(new Date().toString())返回true等多种怪异的转换问题
八、ajax的原理/axios区别
Ajax的原理
- 创建 Ajax 的核心对象 XMLHttpRequest 对象
- 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
- 构建请求所需的数据内容,并通过 XMLHttpRequest 对象的 send() 方法发送给服务器端
- 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
- 接受并处理服务端向客户端响应的数据结果
- 将处理结果更新到 HTML 页面中
区别:
- Ajax 主要是使用原生的XMLHttpRequest对象实例,实现异步数据交互,axios用promise对原生XMLHttpRequest封装,
- axios支持Promise API,支持拦截请求和响应,转换请求和响应数据,防御XSRF
九、new操作符具体干了什么
- 创建一个新的对象obj
- 将对象与构建函数通过原型链连接起来
- 将构建函数中的this绑定到新建的对象obj上
- 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
function mynew(Func, ...args) {
// 1.创建一个新对象
const obj = {}
// 2.新对象原型指向构造函数原型对象
obj.__proto__ = Func.prototype
// 3.将构建函数的this指向新对象
let result = Func.apply(obj, args)
// 4.根据返回值判断
return result instanceof Object ? result : obj
}
十、require和import的区别
require 和 import 是 JavaScript 中用于模块导入的两种不同机制
1. require
根据 CommonJS 规范(Node.js 采用),使用module.exports导出接口,使用 require 引入模块,require是运行时调用,理论上可以运用在代码的任何地方
2. import
根据 ES6中的模块加载语法,使用 export 导出接口,用 import 引入模块,import编译时调用,所以必须放在文件开头
十一、async、await
实际上是Generator函数和Promise对象的语法糖,用于简化异步编程的体验,是解决函数回调的最佳方案
原理
- 当 JavaScript 解析器遇到 async function 关键字时,它会对函数体进行编译转换,生成一个状态机来管理异步操作。
- 当 await 关键字后跟着一个 Promise 时,引擎会检查 Promise 是否已经 resolve 或 reject。如果是,则直接取得结果;否则,挂起当前函数的执行,并通过事件循环机制,在 Promise resolve 或 reject 后恢复执行。
- 如果 await 后面跟的是非 Promise 值,则该值会被转换为已 resolve 的 Promise。
十二、深拷贝要注意哪些问题
- 使用 JSON 的 JSON.parse/JSON.stringify 方法进行深拷贝时,它只能处理可被 JSON 序列化的值,例如它不支持函数、日期格式、正则表达式以及循环引用等。此外,Symbol 类型和 undefined 也会丢失。
- 复制类实例时,深拷贝只是简单地复制了所有属性,并没有复制构造函数和原型方法。对于特殊的内置对象类型如 Date、RegExp、Map、Set、WeakMap、WeakSet 等,深拷贝不仅要创建新实例,还需要确保新实例与原实例具有相同的属性和状态。
- 对象可能拥有不可枚举属性或者通过 Symbol 值定义的属性,在深拷贝时也需要特别注意将这些属性一并复制过去。
- 深拷贝必须递归地复制对象的所有层级,包括嵌套的对象和数组。如果只进行浅拷贝,那么内层对象或数组的引用会共享原对象的引用,修改时会导致原始对象也被修改。
MessageChannel深拷贝
function deepclone(obj){
return new Promise((resolve)=>{
const { port1,port2 } = new MessageChannel();
port1.postMessage(obj);
port2.onmessage =(msg)=>{
resolve(msg.data);
}
})
}
十三、防抖和节流
1. 防抖(Debounce)
防抖的核心逻辑是“最后执行一次”。当一个事件被频繁触发时,只有在最后一次触发后的一段时间内没有新的触发,才会执行回调函数
多次执行变为只执行最后一次
应用场景
- 窗口大小改变时的重新布局
- 搜索框输入,当用户输入内容停止后,发送请求
- 输入框验证,例如手机号校验是否正确
- 按钮点击,重复提交
function debounce(fn, delay) {
let timer = null;
clearTimeout(timer); // 下次调用时会清除上次的timer, 然后重新延迟
timer = setTimeout(function(){
fn();
}, delay);
}
2. 节流(Throttle)
节流则是保证在一定时间间隔内只执行一次操作。无论事件被触发多少次,都会固定时间间隔来限制实际执行的次数
多次执行变为只执行第一次
应用场景
- 页面滚动事件
- 鼠标移入
function throttle(fn, wait) {
let timer = null
return function () {
let context = this
let args = arguments
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(context, args)
}, wait)
}
}
}
function throttle(fn, gapTime) {
let _lastTime = null;
return function () {
let _nowTime = new Date()
if (_nowTime - _lastTime > gapTime || !_lastTime) {
fn();
_lastTime = _nowTime
}
}
}
十四、造成内存泄漏的操作
- 定时器未清理
- 未释放闭包中的引用
- 未正确释放事件监听器
- 循环引用
十五、use strict 严格模式
严格模式提供了一系列额外的约束和更严格的错误检查机制,有助于编写更安全、简洁且易于维护的代码
优点:
- 消除隐式类型转换: 严格模式禁止了这些隐式类型转换,强制开发者显式处理类型问题
- 防止意外修改全局对象: 直接给全局对象添加属性或删除已有的全局变量会被禁止 ,有助于减少全局污染
- 增强作用域安全性:禁止在非函数内部使用 function 关键字定义函数(即不允许创建函数声明作为嵌套作用域内的代码块的一部分)
- 优化引擎性能:可以对代码进行更高效的优化,因为编译器能够确定更多的代码行为
- 未来兼容性:禁用了一些将来可能会成为保留字的关键字,以及一些在未来版本中可能被废弃或改变行为的特性
规则
- 必须明确声明变量: 因此所有变量都必须先声明再使用
- this值的变化: 函数内部的 this 不会默认绑定到全局对象(在浏览器环境中是 window 或者 Node.js 中的 global),而是保持 undefined(非构造函数调用时 )
- 禁止with语句: 可能导致作用域混乱和性能损失
- 禁止八进制字面量前缀:不允许使用 0 开头表示八进制数字
- 函数参数重名:不能在同一个函数中重复声明同名参数。
十六、箭头函数与普通函数区别
- this,就是定义时所在的对象,而不是使用时所在的对象
- 不可以当作构造函数:由于箭头函数没有自己的 this,所以不能使用 new 关键字调用箭头函数来创建实例
- 没有arguments对象:可以使用剩余参数语法 (...args) 来代替
- 没有原型:箭头函数没有 prototype 属性,因此不能被用于实现基于原型的继承
- 不可以使用yield命令,因此箭头函数不能用作Generator函数