【2024前端复盘复习计划】Vue篇

【2024前端复盘复习计划】打包优化篇

【2024前端复盘复习计划】网络协议和浏览器篇

一、try...catch 可以捕获到异步代码中的错误吗

  1. 当异步函数抛出异常时,对于宏任务而言,执行函数时已经将该函数推入栈,此时并不在 try-catch 所在的栈,所以 try-catch 并不能捕获到错误
  2. 对于微任务而言,比如 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 的失败的回调中。
  • 参数
  1. 第一个参数(onFulfilled) :resolved时调用的回调函数,当传入非函数时,忽略非函数值,就将值传递给下一个then
  2. 第二个参数(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()})

  1. Thenable 对象(具有 then 方法的对象): Promise.resolve 会返回一个新的 Promise 实例,这个新 Promise 的状态将会跟随传入的 Thenable 对象的状态改变。
  2. 如果传入Promise 实例:将返回与 value 相同的对象引用,如果传入的是一个已经存在的 Promise,那么 Promise.resolve() 会直接返回这个 Promise,不会创建新的 Promise 实例。
  3. 其他值(非 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 和错误集合。

(二) 执行顺序

  1. Promise构造函数内部执行的代码是同步的。

三、for...in和for...of有什么区别

可迭代:也就是可以通过迭代器进行遍历的类型,例如数组、字符串、Set、Map、类数组、Generator。对象(Object)本身不是可迭代类型。

可枚举:对象的每个属性都有一个可枚举属性(enumerable),默认情况下,大多数内置对象的自有属性(非原型链上的属性)是可枚举的,可枚举性关注的是对象的属性是否能被显式列举

  • for...in 循环主要用于遍历对象的属性,它会将对象的每个可枚举属性作为迭代变量 key 的值。不仅会遍历对象自身的属性,还会遍历其原型链上的属性。遍历的是对象的键名,如是数组则获取的是下标
  • for...of 循环是一种用于遍历可迭代对象(如数组、字符串、Map、Set 等)的迭代方式,且保证按照对象中元素的顺序进行迭代,可直接访问可迭代对象的元素值
  • 总的来说:for in 遍历对象而生 for of遍历数组
  1. Date对象会变成格式化时间字符串(可以再次new Date创建对象);
  2. RegExp对象正则会变成空对象{};
  3. 函数会丢失;
  4. undefined会丢失;
  5. NaN、Infinity、-Infinity会变成null;
  6. Symbol会丢失;
  7. BigInt会抛出异常Do not know how to serialize a BigInt(可以调用BigInt.prototype.toString转为字符串);
  8. Set、Map会变成空对象{};
  9. 不可枚举属性会丢失;
  10. 循环引用会抛出异常。

四、链式调用的实现方式

关键字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:

  1. Number、Boolean、String通过valueOf转换后会变成相应的原始值
  2. Date类型,valueOf函数将日期转换为毫秒的形式
  3. Math 和 Error 对象没有 valueOf 方法
  4. 其他返回对象本身

toString(和String()区别):

  1. undefined和null没有此方法
  2. toString方法可以填写一个参数,该参数为一个进制数

(三) ToPrimitive(value,PreferredType?)方法

value式需要转换的值,PreferredType 来选择类型。

如果第二个参数为空并且 v 为 Date的实例,此时preferredType 会被设置为String,其他情况下preferredType都会被设置为 Number

如果PreferredType为Number则执行以下步骤,如果为String则2,3步骤对调

  1. 如果是基本类型,不进行转换
  2. 否则,调用valueOf方法,如果得到原始值,则返回
  3. 如果 valueOf 方法不存在,或者它返回的仍然是一个对象,调用toString方法,如果得到原始值,则返回
  4. 否则,报错

(四) ==比较

  • NaN不等于任何其它类型
  • Boolean 与其它类型进行比较,Boolean转换为Number
  • String 与 Number进行比较,String 转化为Number
  • null 与 undefined进行比较结果为true
  • null,undefined与其它任何类型进行比较结果都为false
  • 引用类型与值类型进行比较,引用类型先转换为值类型(调用[ToPrimitive])
  • 引用类型与引用类型,直接判断是否指向同一对象

[] == ![]

  1. !优先级高于==,因此先执行![] , !的执行步骤是将该对象先转换成布尔类型然后进行去非
  2. []转换成布尔,任何引用类型转布尔都是true,所以!true就等于false ( [] ==false)
  3. Boolean 与其它类型进行比较,都先转换Number, []转换成调用ToPrimitive,数组为对象先调用valueOf无原始值,继续调用toString原始值获得' ' 此时 ( ' ' ==0 )
  4. 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

  1. 检查对象是否是某个特定类型的实例,会一直往上寻找,直到原型链顶端,若无返回false
  2. 不能检测基本数据类型
  3. 原型链可能被修改,结果不准确

(二) constructor

  1. 实例通过constrcutor 对象访问它的构造函数。不建议使用
  2. null,undefined 没有构造函数,自然也就访问不到该属性
  3. constructor 可以被改写,所以不一定准确

(三) typeof

  1. 能准确的判断出基础类型(非null)和函数类型
  2. 数组,日期,普通对象、null 都返回object

(四) Object.prototype.toString.call

  1. 返回的是一个[object xxxx]字符串,第8位做一个截取获取数据类型

(五) isArray()判断是否数组

(六) 判断NaN的方法

由于NaN 是 number 类型,所以不能直接区分出 NaN 和普通数字

  1. Number.isNaN()是ES6新增的方法,可以判断传入的参数是否为Number类型并且为NaN,返回boolean
  2. isNaN()是挂载在全局变量下的方法,检查值是否不是一个普通数字或者是否不能转换为一个普通数字(存在隐式转换)。比如isNaN(new Date)返回false,isNaN(new Date().toString())返回true等多种怪异的转换问题

八、ajax的原理/axios区别

Ajax的原理

  1. 创建 Ajax 的核心对象 XMLHttpRequest 对象
  2. 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
  3. 构建请求所需的数据内容,并通过 XMLHttpRequest 对象的 send() 方法发送给服务器端
  4. 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
  5. 接受并处理服务端向客户端响应的数据结果
  6. 将处理结果更新到 HTML 页面中

区别:

  1. Ajax 主要是使用原生的XMLHttpRequest对象实例,实现异步数据交互,axios用promise对原生XMLHttpRequest封装,
  2. axios支持Promise API,支持拦截请求和响应,转换请求和响应数据,防御XSRF

九、new操作符具体干了什么

  1. 创建一个新的对象obj
  2. 将对象与构建函数通过原型链连接起来
  3. 将构建函数中的this绑定到新建的对象obj上
  4. 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
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对象的语法糖,用于简化异步编程的体验,是解决函数回调的最佳方案

原理

  1. 当 JavaScript 解析器遇到 async function 关键字时,它会对函数体进行编译转换,生成一个状态机来管理异步操作。
  2. 当 await 关键字后跟着一个 Promise 时,引擎会检查 Promise 是否已经 resolve 或 reject。如果是,则直接取得结果;否则,挂起当前函数的执行,并通过事件循环机制,在 Promise resolve 或 reject 后恢复执行。
  3. 如果 await 后面跟的是非 Promise 值,则该值会被转换为已 resolve 的 Promise。

十二、深拷贝要注意哪些问题

  1. 使用 JSON 的 JSON.parse/JSON.stringify 方法进行深拷贝时,它只能处理可被 JSON 序列化的值,例如它不支持函数、日期格式、正则表达式以及循环引用等。此外,Symbol 类型和 undefined 也会丢失。
  2. 复制类实例时,深拷贝只是简单地复制了所有属性,并没有复制构造函数和原型方法。对于特殊的内置对象类型如 Date、RegExp、Map、Set、WeakMap、WeakSet 等,深拷贝不仅要创建新实例,还需要确保新实例与原实例具有相同的属性和状态。
  3. 对象可能拥有不可枚举属性或者通过 Symbol 值定义的属性,在深拷贝时也需要特别注意将这些属性一并复制过去。
  4. 深拷贝必须递归地复制对象的所有层级,包括嵌套的对象和数组。如果只进行浅拷贝,那么内层对象或数组的引用会共享原对象的引用,修改时会导致原始对象也被修改。

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
    }
  }
}

十四、造成内存泄漏的操作

  1. 定时器未清理
  2. 未释放闭包中的引用
  3. 未正确释放事件监听器
  4. 循环引用

十五、use strict 严格模式

严格模式提供了一系列额外的约束和更严格的错误检查机制,有助于编写更安全、简洁且易于维护的代码

优点:

  1. 消除隐式类型转换: 严格模式禁止了这些隐式类型转换,强制开发者显式处理类型问题
  2. 防止意外修改全局对象: 直接给全局对象添加属性或删除已有的全局变量会被禁止 ,有助于减少全局污染
  3. 增强作用域安全性:禁止在非函数内部使用 function 关键字定义函数(即不允许创建函数声明作为嵌套作用域内的代码块的一部分)
  4. 优化引擎性能:可以对代码进行更高效的优化,因为编译器能够确定更多的代码行为
  5. 未来兼容性:禁用了一些将来可能会成为保留字的关键字,以及一些在未来版本中可能被废弃或改变行为的特性

规则

  1. 必须明确声明变量: 因此所有变量都必须先声明再使用
  2. this值的变化: 函数内部的 this 不会默认绑定到全局对象(在浏览器环境中是 window 或者 Node.js 中的 global),而是保持 undefined(非构造函数调用时 )
  3. 禁止with语句: 可能导致作用域混乱和性能损失
  4. 禁止八进制字面量前缀:不允许使用 0 开头表示八进制数字
  5. 函数参数重名:不能在同一个函数中重复声明同名参数。

十六、箭头函数与普通函数区别

  • this,就是定义时所在的对象,而不是使用时所在的对象
  • 不可以当作构造函数:由于箭头函数没有自己的 this,所以不能使用 new 关键字调用箭头函数来创建实例
  • 没有arguments对象:可以使用剩余参数语法 (...args) 来代替
  • 没有原型:箭头函数没有 prototype 属性,因此不能被用于实现基于原型的继承
  • 可以使用yield命令,因此箭头函数不能用作Generator函数