JavaScript
![JavaScript 重难点整理,包括数据类型、执行上下文、事件循环等内容。 JavaScript 重难点整理,包括数据类型、执行上下文、事件循环等内容。](/js/index.webp)
数据
数据类型
七种基本数据类型,一种复杂数据类型:
number
:+-(2^53-1) 范围内的数字string
:字符串boolean
:true 和 falsenull
:未知的值undefined
:未定义的值bigint
:任意长度的整数symbol
:唯一标识符object
:复杂的数据结构
数据存储:
- 栈:基本类型和引用类型地址
- 堆:引用类型地址实际指向的数据
类型检查
typeof
运算符:
typeof (()=>{})
会返回"function"
typeof []
会返回"object"
typeof null
会返回"object"
,但实际上它并不是一个对象
instanceof
运算符:
- 支持继承的对象检测方法
Object.prototype.toString.call([])
:
Object.prototype.toString.call([]) === '[object Array]'
Object.prototype.toString.call(()=>{}) === '[object Function]'
类型转换
window.isNaN('abc')
:true
Number.isNaN('abc')
:此方法不进行类型转换,因此为false
[0] == true
:数组调用toString
再变为数字 0,因此为false
执行上下文和变量提升
区别 let
和 var
:
let
拥有块级作用域let
声明的全局变量不是全局对象的属性let
用于循环中可以正常创建副本let
不允许重定义
执行上下文可以理解为当前代码的执行环境,它会形成一个作用域:
- 全局环境
- 函数环境
eval
变量提升被认为是对执行上下文工作方式的一种认识,例如:
|
|
等价于:
|
|
闭包
闭包简单来说就是函数中的函数;闭包可以通过一个函数去访问原本在外层无法直接访问到的数据,并且保证数据不被回收。
暂时性死区
和直接使用 var
定义不同,由于没有变量提升,let
、const
定义的变量在声明之前使用会报错。
Event Loop 事件循环
首先将事件分为宏任务与微任务:
- 宏任务:主线 JS 代码、事件、
setTimeout
和setInterval
等 - 微任务:
process.nextTick
和Promise
回调等
事件循环流程:
- 首先执行主线同步任务
- 当遇到异步任务时将任务搁置一边独立执行,当异步任务有了结果将其放入对应的异步任务队列
- 主线同步任务执行完毕,检查微任务队列是否有内容,若有则一直执行至清空
- 进入下一轮循环,检查宏任务队列是否有内容,若有则一直执行至清空
- 继续执行主线同步任务
代码例:
|
|
解析:
- 首先执行主线同步任务,输出 Start、Promise 1 和 End
- 此时宏任务队列中有
timer1
和timer2
;微任务队列中有promise1
的第一个then
- 检查微任务队列,输出 Promise 1 Fulfilled 和 Promise 1 Then Fulfilled
- 此时宏任务队列中有
timer1
、timer2
和timer3
;微任务队列中为空 - 下一轮循环,输出 Timer 1;微任务队列仍为空
- 下一轮循环,输出 Timer 2 in Promise 2;微任务队列中加入
promise2
的第一个then
- 优先微任务队列,输出 Promise 2 Then 1 和 Promise 2 Then 2
- 下一轮循环,输出 Timer 3 in Promise 1
注意:在 Promise 同步构造函数中,在没有返回值的情况下,resolve()
后的代码依旧会被执行,只是无法再使用 reject()
改变该 Promise 的状态。
Map 与 WeakMap
- WeakMap 的 key 只接受使用对象
- WeakMap 不支持迭代,
keys()
等方法无法使用
在 Map 中,若将某对象设置为 key,则后期将对该对象的直接引用置为 null
,GC 不会回收,通过 Map.keys()
可以找到该对象;而在 WeakMap 中,该对象会被回收,但无法保证 GC 何时工作。
遍历和迭代
for...in...
:
- 遍历对象及其原型链上可枚举属性
- 遍历数组元素及自定义属性
- 返回 string 类型的 key
Object.keys()
:
- 遍历对象本身的可枚举属性
- 遍历数组元素及自定义属性
- 返回 string 类型的 key
for...of...
:
- 遍历任何可迭代对象 (普通对象不支持)
- 返回 any 类型的 value
Promise
Promise.all()
:所有成功则成功;任一失败则失败Promise.any()
:任一成功则成功;所有失败则失败Promise.race()
:任一成功则成功;任一失败则失败Promise.allSettled()
:所有都成功或失败最后返回结果
This 指向绑定
对于 this
指向在函数执行时才能确定,创建时无法确定。
对于一般函数,this
指向调用者:
|
|
|
|
对于箭头函数,this
指向其父级执行上下文:
|
|
|
|
call
、apply
、bind
都用于绑定 this
指向:
call
:第一参数为 this 指向,剩余参数为参数列表,临时改变 this 并立即执行apply
:第一参数为 this 指向,第二参数为参数数组,临时改变 this 并立即执行bind
:第一参数为 this 指向,剩余参数为参数列表,返回 this 指向确定的函数,同时在调用返回的函数时还可以添加剩余参数
继承与原型链
每个实例对象都有一个私有属性 __proto__
指向它的原型对象,该原型对象也有原型,一直到 null
为止。
__proto__
:常见的浏览器原型链实现getPrototypeOf()
获取[[Prototype]]
:等价于__proto__
prototype
函数才有,指向原型对象;原型对象也有constructor
属性与之对应
|
|
模拟实现 new
|
|
原型链继承
|
|
- 引用类型的属性会被所有子实例共享
- 无法向 Parent 传参
组合继承
引入经典继承 (借用构造函数):
|
|
- 调用了两次父构造函数,导致
Child.prototype
和child1
中有重复的names
寄生组合继承
引入寄生式继承 (使用父原型而不是父实例作为 Child.prototype
):
|
|
DOM API
Element 与 Node
- Node 是一个基类,DOM 中的 Element、Text 和 Comment 都继承于它
- Node 包含了其内部的 Element 结点,除此之外还有直接插入的文本,注释等内容
- NodeList 和 ElementCollcetion 都不是真正的数组
元素相关属性
clientHeight
:content + paddingoffsetHeight
:content + padding + border + scrollbarscrollHeight
:滚动部分总高度,包括当前不可见部分scrollTop
:滚动部分顶端距离可见部分顶端的高度offsetTop
:当前元素顶部距离最近父元素顶部的距离
事件、冒泡和捕获
事件传播的三个阶段:
- 捕获阶段
- 目标阶段
- 冒泡阶段
当一个事件发生在一个元素上,它会首先运行在该元素上的处理程序,然后运行其父元素上的处理程序,然后一直向上到其他祖先上的处理程序;几乎所有事件都会冒泡,但也有例如 focus
的事件不会冒泡。
event.target
:是引发事件的目标元素,冒泡过程中不会发生变化this
:当前元素,其中有一个当前正在运行的处理程序
ESM 与 CommonJS 模块
加载与解析
CJS 模块同步加载,输出的是值的拷贝;对于基本类型,一旦输出,模块内部的变化影响不到这个值;对于引用类型,效果同引用类型的赋值操作;通过导出 getter 函数可以获取模块内部的变化;ES 模块是动态引用,并且不会缓存值。
由于 CJS 模块是同步的,因此可以放在代码段的任何位置;而 ESM 只是静态定义,在代码解析阶段就会被执行:import
会被提升到头部执行,export
与 var
定义的变量提升有类似的效果。
两种模块都不会重复执行。
循环依赖
CJS 模块当遇到 require()
语句时,会执行模块中的代码,得到的是已经执行部分的结果。
ES6+ 新要素
let
和const
- 扩展运算符
- 箭头函数
class
- Map 和 Set
箭头函数特性
- 没有自己的
this
,内部this
指向父级执行上下文 - 无法作为构造函数,没有
prototype
属性 - 无法使用
arguments
,需要 rest 参数 - 无法作为 generator 函数使用
ES 标准流程
Stage 0
:开放提交,提议、想法Stage 1
:正式建议,初步 demo 和标准Stage 2
:草案,标准语言解释和实验性实现Stage 3
:接近完成,等待测试、审核和用户反馈Stage 4
:确认会被包含到将来的标准中