普通视图

发现新文章,点击刷新页面。
昨天以前首页

JS知识点汇总

2025年4月13日 16:00

jS中几种常见的类型判断方法****

  • 基本数据类型:string,number,boolean,null,undefinded,bigint,symbol
  • 引用数据类型:对象,函数,数组,日期
  1. Typeof    判断原始数据类型
  2. Instanceof 来判断引用数据类型
  3. Object.prototype.toString.call(this)  判断所有类型
  4. 判断数组  Array.isArray()

==和===

==(宽松相等):会在比较两个操作数时执行 类型转换尝试将两者转换为相同类型后再比较。 ===(严格相等):不会执行类型转换,仅在类型和值完全相同的情况下返回 true。 推荐使用 ===:因为它更严格、更符合预期,能避免潜在的错误。尤其是在需要精确判断值和类型时。

深拷贝和浅拷贝区别

浅拷贝,只拷贝了最外面的一层 ,只相当赋值给了 新的,新的改值,旧的值不会改变* 但是如果对象里面包对象,那么也只能拷贝最外面的一层,新的改值,旧的值也会改变!!!因为拷贝的还是地址,而不是对象****

  • 深拷贝和浅拷贝只针对引用类型
  • 浅拷贝:拷贝的是地址
  • 深拷贝:拷贝的是对象,不是地址

数组实现浅拷贝:concat  【...arr】 slice()

对象实现浅拷贝:Object.assgin  / ...展开运算 {。。。Obj}

深拷贝:Stringify  lodash/clonedeep  递归实现深拷贝

变量提升

变量提升是 JavaScript 的一种机制,在该机制下,变量和函数的声明会被提升到当前作用域的顶部。这意味着,无论变量和函数在代码中实际声明的位置如何,它们都可以在声明之前被访问。

函数声明提升

函数声明会被完整提升,这意味着在函数声明之前就可以调用该函数。

// 可以在函数声明之前调用函数
sayHello();

function sayHello() {
    console.log('Hello!');
}
变量声明提升

使用 var 声明的变量会被提升,但不会赋值,在变量声明之前访问该变量,其值为 undefined

// 变量提升,输出 undefined
console.log(message); 
var message = 'Hello, world!';

暂时性死区

暂时性死区是 ES6 引入 let 和 const 后出现的概念。使用 let 和 const 声明的变量不会像 var 那样被提升到作用域顶部并赋值为 undefined,而是存在于暂时性死区内。在变量声明之前访问这些变量会导致 ReferenceError

/ 报错:ReferenceError
console.log(myVariable); 
let myVariable = 'Hello';

在上面的代码中,myVariable 从块级作用域开始到它被声明之前都处于暂时性死区内。只有在声明之后,变量才能被正常访问

两者的区别

  • 变量提升:使用 var 声明的变量和函数声明会被提升到作用域顶部,函数可以在声明前调用,变量在声明前值为 undefined
  • 暂时性死区:使用 let 和 const 声明的变量不会被赋值为 undefined,在声明之前访问会导致 ReferenceError

var、let、const区别

var没有块级作⽤域,只有函数作⽤域。var只有在function{ }内部才有作⽤域的概念,其他地⽅没有。意味着函数以外⽤var定义的变量是同⼀个,我们所有的修改都是针对他的

  1. let和const增加块级作⽤域(JS没有块级作⽤域)

  2. let和const存在暂时性死区,不存在变量提升,不能在初始化前引⽤,调⽤ 返回 uninitialized

  3. let和const禁⽌重复声明,不能重新声明

  4. let和const不会成为全局对象属性,var声明的变量⾃动成为全局对象属性

  5. var 存在变量提升(执⾏前,编译器对代码预编译,当前作⽤域的变量/函数提升到作⽤域顶部),let约束变量提升。let和var都发⽣了变量提升,只是es6进⾏了约束,在我们看来,就像let禁⽌了变量提升

  6. 使⽤var,我们能对变量多次声明,后⾯声明的变量会覆盖前⾯的声明

WeakMap与Map的区别是什么

1. 什么是WeakMap

WeakMap 是 JavaScript 中的一种集合类型,它存储键值对,且键必须是对象,并且键是弱引用的。这意味着,如果键对象没有其他引用,它会被垃圾回收器回收,对应的键值对也会被自动删除。

2. 与Map的区别

键的类型

  • Map:键可以是任意类型,包括基本数据类型(像字符串、数字等)和引用类型(如对象、函数)。
  • WeakMap:键只能是对象,不能使用基本数据类型作为键。

垃圾回收机制

  • Map:对键所引用的对象是强引用。只要 Map 存在,键引用的对象就不会被垃圾回收,即便其他地方无该对象的引用。
  • WeakMap:对键所引用的对象是弱引用。若对象没有其他强引用,垃圾回收时对象会被回收,WeakMap 里对应的键值对也会自动移除。

可遍历性

  • Map:是可迭代的,能使用 for...of 循环、forEach 方法等遍历其键值对。
  • WeakMap:不可迭代,没有 keys()values()entries() 这些迭代方法,也不能用 for...of 或 forEach 遍历。

方法和属性

  • Map:有 size 属性来获取键值对数量,还有 set()get()has()delete()clear() 等方法。
  • WeakMap:只有 set()get()has()delete() 方法,没有 size 属性和 clear() 方法。

使用场景

  • Map:适用于需存储任意类型键值对,且要对这些键值对进行遍历和操作的场景,如缓存数据。
  • WeakMap:常用于避免内存泄漏的场景,例如给对象添加私有数据,当对象被销毁时,WeakMap 里相关数据也会被清理。

This指向

// this指向的情况

  1. 函数调用模式 fn(),this 指向 window(默认绑定)

  2. 方法调用模式 obj.fn(),this 指向调用者(隐式绑定),虽然没有刻意的绑定,但是在执行时会自动将函数的 this 指向该对象。

  3. 上下文调用模式:想指向谁就指向谁(显式绑定/硬绑定)

// call, apply, bind

fn.call(this指向的内容, 参数1, 参数2, ...)

fn.apply(this指向的内容, [参数1, 参数2, ...])

const newFn = fn.bind(this指向的内容)

call apply bind 总结

  • 相同点:都可以改变函数内部的this指向,
  • 区别点:
  1. call 和 apply 会调用函数,并且改变函数内部this指向,
  2. call 和 apply 传递的参数不一样,call 传递参数 aru1, aru2..形式
  3. apply 必须数组形式[arg]bind 不会调用函数,可以改变函数内部this指向
  • 主要应用场景:
  1. call 调用函数并且可以传递参数
  2. apply 经常跟数组有关系.比如借助于数学对象实现数组最大值最小值
  3. bind 不调用函数,但是还想改变this指向.比如改变定时器内部的this指向

闭包

闭包:内层函数+外层函数的变量

闭包的核心特性:

  • 访问外部函数作用域的变量
  • 即使外部函数执行结束,变量依然被保留
  • 不会被垃圾回收,直到闭包不再被引用

闭包的应用场景:

  • 私有变量(模拟封装)
function createcount(){
let count=0
return{
add:()=>++count }
}

const count=createcount()
count.add()  // 1
count.count  undefinded  外部无法直接访问
  • 回调 & 事件监听
  • 定时器 & 异步操作

闭包的缺点:

  • 可能导致内存泄漏:
  • 闭包会持有外部变量的引用,导致变量无法被垃圾回收
  • 解决方案:手动将变量置为 null 或谨慎管理作用域

内存泄露

内存泄露是指在程序运行过程中,程序未能释放不再使用的内存空间,导致内存资源被浪费

JS 内存泄露的常见原因

l 意外的全局变量:忘记使用 var,let,const 声明变量时,变量会被挂载到全局对象上

l 闭包:闭包中引用了不再需要的外部变量,导致这些变量无法被垃圾回收

l 未清理的 DOM 引用:删除 DOM 元素时,未能清理相关的事件监听器或引用

l 定时器和回调:未能清理不再需要的 setInterval 或 setTimeout 回调  

JS 作用域和作用域链

作用域:变量的可访问范围,分为 全局作用域、函数作用域、块级作用域。

作用域链:变量查找机制,从当前作用域 逐级向上查找,直到全局作用域或 ReferenceError。

原型和原型链

  1. 原型(Prototype)

每个 函数(构造函数)都有一个 prototype 属性,指向其 原型对象。

每个 对象 都有一个 proto 指向其构造函数的 prototype,形成继承关系。   2. 原型链(Prototype Chain)

查找⼀个属性先在⾃身查找,如果找不到,就沿着 proto 属性在原型对象上进⾏查找,如果还找不到,就沿着原型对象的 proto 属性进⾏查找,直到查找到直到找到Object的原型对象,如果还没有找到就会返回undefined

Object.prototype.proto === null,原型链的顶端是 是 Object.prototype.proto

  • 使⽤ hasOwnProperty() ⽅法来判断属性是否属于原型链的属性:

每个实例对象都有私有属性(proto)指向它构造函数的原型对象。

每个构造函数都有prototype原型对象

prototype原型对象的constructor指向构造函数本身

有默认constructor属性,记录实例由哪个构造函数创建

ES6新特性

  1. const、let

  2. 模板字符串

  3. 箭头函数

  4. 函数参数默认值

  5. 解构赋值

  6. for...of ⽤于数组,for...in⽤于对象

  7. Promise

  8. 展开运算符(...)

  9. 对象字⾯量、class(原型链的语法糖)

箭头函数和普通函数区别

  • 1.语法更加简洁,没有function =》
  • 2.没有动态参数,但是有剩余参数
  • 3.没有自己的this,会继承外层的this
  • 4.不支持bind/apply/call
  • 5.不能用作构造函数,没有prototype属性

js数组⽅法

  • forEach map
  • push pop shift unshift
  • splice slice concat join
  • sort reverse some every filter
### 改变原数组的方法

-   `push`:把一个或多个元素添加到数组末尾,并且返回新的数组长度。
-   `pop`:移除数组的最后一个元素,同时返回该元素。
-   `shift`:移除数组的第一个元素,然后返回该元素。
-   `unshift`:在数组开头添加一个或多个元素,并且返回新的数组长度。
-   `splice`:从数组中添加或删除元素,然后返回被删除的元素组成的数组。
-   `sort`:对数组的元素进行排序,并且返回排序后的数组。
-   `reverse`:颠倒数组中元素的顺序,并且返回颠倒后的数组。
`map``slice``concat``join``some``every``filter` 这些方法不会改变原数组  会返回新数组
- `map`:创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
- `slice`:返回一个新的数组对象,这一对象是一个由 `begin` 和 `end` 决定的原数组的浅拷贝(包括 `begin`,不包括 `end`)。
`concat`:用于合并两个或多个数组,此方法不会更改现有数组,而是返回一个新数组
`join`:将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串
- `some`:测试数组中是不是至少有 1 个元素通过了被提供的函数测试,它返回的是一个布尔值。
`every`:测试一个数组内的所有元素是否都能通过某个指定函数的测试,它返回一个布尔值
- `filter`:创建一个新数组,其包含通过所提供函数实现的测试的所有元素。

防抖节流

  • 防抖:单位时间内,频繁触发事件,只执行最后一次
  • 使用场景:手机号,邮箱输入检测,用户最后一次输入完,再检测
  • 节流:单位时间内,频繁触发事件,只执行一次
  • 使用场景:鼠标移动,滚动条滚动
  • 手撕:
  1. 防抖函数:声明定时器变量,判断是否有定时器,如果有,清除定时器,重新声明定时器,执行函数
  2. 节流函数:声明定时器变量为空,判断如果没有定时器,声明定时器,执行函数,定时器清空
  3. Lodash库的 防抖和节流函数     _.debounce  _.throttle

如何理解 JS 单线程?

  • js是一门单线程的语言。因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。
  • 渲染主线程要执行很多任务,解析html,执行js等。
  • 如果主线程采用同步的方式执行代码,可能会阻塞主线程的执行,导致消息队列中的其他任务无法执行。会浪费时间,以及导致页面无法及时更新卡死。
  • 所以采用异步的方式去解决。当遇到一些异步的任务,渲染主线程将这些任务交给其他线程去处理,自身立即结束执行任务,执行后续代码。当其他线程完成,将事先传递的回调函数包装成任务,放入消息队列的末尾,等待主线程的调度执行。
  • 在这种异步模式下,浏览器就不会阻塞,单线程也可以流畅运行

js的异步理解

  • js是一门单线程的语言。因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。
  • 渲染主线程要执行很多任务,解析html,执行js等。
  • 如果主线程采用同步的方式执行代码,可能会阻塞主线程的执行,导致消息队列中的其他任务无法执行。会浪费时间,以及导致页面无法及时更新卡死。
  • 所以采用异步的方式去解决。当遇到一些异步的任务,渲染主线程将这些任务交给其他线程去处理,自身立即结束执行任务,执行后续代码。当其他线程完成,将事先传递的回调函数包装成任务,放入消息队列的末尾,等待主线程的调度执行。
  • 在这种异步模式下,浏览器就不会阻塞,单线程也可以流畅运行。

事件循环

事件循环又称之为消息循环,是浏览器渲染主线程的工作方式。 在chrome的源码中,它开启一个死循环,每次循环从消息队列中取出第一个任务执行,其他线程只需要在合适的时候把任务放入消息队列的末尾即可。 过去就是微任务队列和宏任务队列。目前的浏览器情况更加复杂,灵活的处理。 最新的W3c解释如下:

  • 每个任务都有一种任务类型,相同类型的任务必须放在一个队列中,不同类型的任务放在不同的队列中。
  • 不同的任务队列有不同的优先级
  • 在一次事件循环中,浏览器可根据实际情况选取队列执行任务。
  • 但是浏览器必须准备好一个微任务队列,微任务队列中的任务优先其他所有任务执行。必须优先调度。

最开始的时候,渲染主线程会开启无限循环。

  • 每一次循环,都会判断消息队列中是否有任务存在,如果有,就执行取出第一个任务执行。执行完以后,进入下一次循环,没有的话,就会进入休眠状态。
  • 其他所有线程可以随时向消息队列里面添加任务,新任务会加入消息队列的末尾。如果是休眠状态,会唤醒主线程,继续循环执行任务。 整个过程,就是事件循环(消息循环)

事件循环机制

宏任务主要包括:setTimeout、setInterval

微任务主要包括:promise、process.nextTick()

执⾏规则:同步代码直接进⼊主线程执⾏,JS引擎判断主线程是否为空,如果为空,则读取 微任务Event

Queue 中所有的消息,并依次执⾏。主线程和微任务 Event Queue 都为空后,读取 宏任务Event Queue 中的第⼀个消息进⼊主线程执⾏,来回微宏。

async/await

async/await 是 ES2017(ES8)引入的 基于 Promise 的语法糖,用于更清晰地编写异步代码,使其看起来像同步代码,提高可读性。

async 关键字:用于声明一个异步函数,返回值始终是 Promise。

await 关键字:只能在 async 函数中使用,等待 Promise 解析(resolve)并返回结果,而不会阻塞线程。

Promise

  • Promise 对象是异步编程的⼀种解决⽅案。Promise 是⼀个构造函数,接收⼀个函数作为参数,返回⼀个 Promise 实例。
  • ⼀个 Promise 实例有三种状态,分别是pending、fulfilled 和 rejected。
  • 实例的状态只能由 pending 转变 fulfilled 或者 rejected 状态,并且状态⼀经改变,⽆法再被改变了。
  • 状态的改变是通过传⼊的 resolve() 和 reject() 函数来实现的,当我们调⽤resolve回调函数时,会执⾏Promise对象的then⽅法传⼊的第⼀个回调函数,当我们调⽤reject回调函数时,会执⾏Promise对象的then⽅法传⼊的第⼆个回调函数,或者catch⽅法传⼊的回调函数。

Promise的实例有两个过程:

pending -> fulfilled : Resolved(已完成)

pending -> rejected:Rejected(已拒绝)

⼀旦从进⾏状态变成为其他状态就永远不能更改状态了。

在通过new创建Promise对象时,我们需要传⼊⼀个回调函数,我们称之为executor

✓ 这个回调函数会被⽴即执⾏,并且给传⼊另外两个回调函数resolve、reject;

✓ 当我们调⽤resolve回调函数时,会执⾏Promise对象的then⽅法传⼊的回调函数;

✓ 当我们调⽤reject回调函数时,会执⾏Promise对象的catch⽅法传⼊的回调函数;

resolve的三种状态

情况⼀:如果resolve传⼊⼀个普通的值或者对象,那么这个值会作为then回调的参数;

情况⼆:如果resolve中传⼊的是另外⼀个Promise,那么这个新Promise会决定原Promise的状态:

情况三:如果resolve中传⼊的是⼀个对象,并且这个对象有实现then⽅法,那么会执⾏该then⽅法,并且根据then⽅法的结 果来决定Promise的状态:

then⽅法接受两个参数:

fulfilled的回调函数:当状态变成fulfilled时会回调的函数;

reject的回调函数:当状态变成reject时会回调的函数;

Promise 串行执行

多个异步操作依次执行(避免回调地狱)

function step1() {
  return new Promise((resolve) => setTimeout(() => resolve('Step 1 完成'), 1000))
}
function step2() {
  return new Promise((resolve) => setTimeout(() => resolve('Step 2 完成'), 1000))
}

step1()
  .then((result) => {
    console.log(result)
    return step2() // 返回 Promise
  })
  .then((result) => console.log(result))
  .catch((error) => console.error('错误:', error))
  
  输出:
  Step 1 完成
  Step 2 完成

Promise 并行执行

多个异步任务同时执行,全部完成后再处理

const p1 = new Promise((resolve) => setTimeout(() => resolve('任务 1'), 1000))
const p2 = new Promise((resolve) => setTimeout(() => resolve('任务 2'), 1500))

Promise.all([p1, p2])
  .then((results) => console.log('所有任务完成:', results))
  .catch((error) => console.error('任务失败:', error))

如果只要最快完成的结果

Promise.race([p1, p2])
  .then((result) => console.log('最先完成的:', result))
  .catch((error) => console.error('失败:', error))

总结

Promise 解决异步回调问题,提供 .then().catch().finally() 处理状态变化。支持 Promise.all() 并行执行,Promise.race() 竞争执行。用 async/await 可以让异步代码更清晰。

❌
❌