JS知识点汇总
jS中几种常见的类型判断方法****
- 基本数据类型:string,number,boolean,null,undefinded,bigint,symbol
- 引用数据类型:对象,函数,数组,日期
- Typeof 判断原始数据类型
- Instanceof 来判断引用数据类型
- Object.prototype.toString.call(this) 判断所有类型
- 判断数组 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定义的变量是同⼀个,我们所有的修改都是针对他的
-
let和const增加块级作⽤域(JS没有块级作⽤域)
-
let和const存在暂时性死区,不存在变量提升,不能在初始化前引⽤,调⽤ 返回 uninitialized
-
let和const禁⽌重复声明,不能重新声明
-
let和const不会成为全局对象属性,var声明的变量⾃动成为全局对象属性
-
var 存在变量提升(执⾏前,编译器对代码预编译,当前作⽤域的变量/函数提升到作⽤域顶部),let约束变量提升。let和var都发⽣了变量提升,只是es6进⾏了约束,在我们看来,就像let禁⽌了变量提升
-
使⽤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指向的情况
-
函数调用模式 fn(),this 指向 window(默认绑定)
-
方法调用模式 obj.fn(),this 指向调用者(隐式绑定),虽然没有刻意的绑定,但是在执行时会自动将函数的 this 指向该对象。
-
上下文调用模式:想指向谁就指向谁(显式绑定/硬绑定)
// call, apply, bind
fn.call(this指向的内容, 参数1, 参数2, ...)
fn.apply(this指向的内容, [参数1, 参数2, ...])
const newFn = fn.bind(this指向的内容)
call apply bind 总结
- 相同点:都可以改变函数内部的this指向,
- 区别点:
- call 和 apply 会调用函数,并且改变函数内部this指向,
- call 和 apply 传递的参数不一样,call 传递参数 aru1, aru2..形式
- apply 必须数组形式[arg]bind 不会调用函数,可以改变函数内部this指向
- 主要应用场景:
- call 调用函数并且可以传递参数
- apply 经常跟数组有关系.比如借助于数学对象实现数组最大值最小值
- 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。
原型和原型链
- 原型(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新特性
-
const、let
-
模板字符串
-
箭头函数
-
函数参数默认值
-
解构赋值
-
for...of ⽤于数组,for...in⽤于对象
-
Promise
-
展开运算符(...)
-
对象字⾯量、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`:创建一个新数组,其包含通过所提供函数实现的测试的所有元素。
防抖节流
- 防抖:单位时间内,频繁触发事件,只执行最后一次
- 使用场景:手机号,邮箱输入检测,用户最后一次输入完,再检测
- 节流:单位时间内,频繁触发事件,只执行一次
- 使用场景:鼠标移动,滚动条滚动
- 手撕:
- 防抖函数:声明定时器变量,判断是否有定时器,如果有,清除定时器,重新声明定时器,执行函数
- 节流函数:声明定时器变量为空,判断如果没有定时器,声明定时器,执行函数,定时器清空
- 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
可以让异步代码更清晰。