JS 的特点
- 单线程
- JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
- 同一时间只能做一件事,DOM 渲染会阻塞 JS 的执行
- 浏览器和 nodeis 已支持 JS 启动进程,如 Web Worker (只能执行 js 计算,无法渲染 DOM)
- 弱类型
- 跨平台
- 解释型语言
- 以事件驱动为核心
- 遵循 ES 标准
严格模式 的特点
- 全局变量必须先声明
- 禁止使用 with
- 创建 eval 作用域
- 禁止 this 指向 window
- 函数参数不能重名
- 禁止使用0开头的数字
数据类型有哪些
- 值类型( 6 种):Number、String、Boolean、Null、Undefined、Symbol(ES6新增)
- 引用类型(1 种):Object
解构赋值
【实战】互换赋值
let a=1, b=2;
[b,a] = [a,b]
console.log(a,b) //2 1
作用域
即变量的合法使用范围
- 全局作用域——全局可使用
- 函数作用域——函数内可使用
- 块级作用域(ES6 新增)——代码块内能使用 (如 if、for 语句的 {} 内)
自由变量
- 一个变量在当前作用域没有定义,但被使用了
- 在变量定义的地方(不是执行的地方)向上级作用域一层一层寻找,直至找到为止
- 如果到全局作用域都没找到,则报错 xx is not defined
函数
函数声明和函数表达式的区别
-
函数声明 function fn() {…}
-
函数表达式 const fn= function() {…}
-
函数声明会在代码执行前预加载,而函数表达式不会
函数在声明前调用,可以正常执行,改用函数表达式就不行,调用时变量 fn 还不是函数,会报错。
普通函数和箭头函数的区别
普通函数 | 箭头函数 | |
---|---|---|
声明方式 | function , 可以是具名/ 匿名函数 | => ,只能为匿名函数 |
this指向 | 运行时确定 可以根据调用方式不同而改变 |
定义时确定 总是指向定义时的上层作用域中的this (call , bind , apply 无法改变 this 指向) |
构造函数 | 可以用作构造函数 | 不能用作构造函数 |
arguments 对象 | 有自己的 arguments 对象 | 没有自己的 arguments 对象,继承父级作用域中的 arguments 对象 |
箭头函数不适用的场景
- 构造函数不能使用箭头函数(箭头函数没有自己的this,所以不能作为构造函数使用,也不能通过new操作符调用)
- 事件监听的回调函数不能使用箭头函数(事件监听的回调函数中需通过 this 读取元素本身,但箭头函数内的 this 为外层的 window)
- 对象的方法不适合使用箭头函数(对象方法需要通过 this 获取对象自己,但箭头函数内的 this 为外层的 window)
- 原型链的方法不适合使用箭头函数(原因与对象的方法相同)
- Vue 的生命周期和方法不能使用箭头函数(原因与对象的方法相同)
回调函数
被作为另一个函数的参数传入的函数
闭包
访问了外部变量的函数
this关键字是如何工作的?(this 的指向)
- 全局上下文中,this 指向全局对象(在浏览器中是window)
- 事件监听器中,this 指向触发事件的元素
- 类的静态方法中,this 指向类
- 普通函数中,this 指向全局对象(在浏览器中是window)
- 构造函数中,this 指向新创建的对象实例
- 箭头函数中,this 指向外层作用域的 this
- 对象的方法用普通函数书写,被调用时 this 指向对象,用箭头函数书写时,this 指向外层的 this (在浏览器中是window)
- bind、call、apply 方法创建的新函数中 ,this 指向方法的第一个参数
详见
https://mp.csdn.net/mp_blog/creation/success/123093256
原型 vs 原型链
原型链是 JavaScript 中实现对象继承的一种机制

- 声明构造函数/类时,不仅会创建该构造函数/类,还会创建相应的原型对象,通过构造函数/类的 protoType 属性可以访问,所有可以被继承的属性和方法都存储在该原型对象中
- 使用构造函数/类创建对象时,对象实例的
__proto__
属性存储了原型对象的引用 - 通过
__proto__
属性,便将对象继承的所有原型串联了起来,构成了原型链 - 当访问对象的属性/方法时,若对象中没有找到,就会由近及远去原型链中找,直到原型链的顶端 Object.prototype
异步
一种避免耗时较长的任务阻塞代码执行的 JS 代码运行机制 ( 详解下文 JS 代码在浏览器中的执行顺序
)
最初使用回调函数实现,但因代码层层嵌套,形成了回调地域
难以维护,ES6 新增了 Promise 实现异步,通过 Promise 可链式调用的特征,解决了回调函数的回调地域
问题。
Promise
Promise 是构造函数,专为异步设计,可通过 new 创建未来会执行的对象实例(一个状态为 pending 的 Promise 实例 )
let p1 = new Promise((resolve, reject) => {
});
pending 状态的 Promise 实例执行 resolve() 后,Promise 实例的状态会变为 resolved / fulfilled,并立即触发后续的 then 函数
let p1 = new Promise((resolve, reject) => {
resolve();
});
let p2 = p1.then(function () {
console.log("打印 p1 --", p1); // p1 状态为 fulfilled
});
// 若then函数内无报错,p2 状态为 fulfilled,若报错,则 p2 状态为 rejected
console.log("打印 p2 --", p2);
pending 状态的 Promise 实例执行 reject() 后,Promise 实例的状态会变为 rejected ,并立即触发后续的 catch 函数
let p1 = new Promise((resolve, reject) => {
reject();
});
let p2 = p1.catch(function () {
console.log("打印 p1 --", p1); // p1 状态为 rejected
});
// 若catch 函数内无报错,p2 状态为 fulfilled,若报错,则 p2 状态为 rejected
console.log("打印 p2 --", p2);
Promise 的串行执行
async function promise_serial() {
let result = [];
result.push((await promise_list[2]).data);
result.push((await promise_list[1]).data);
result.push((await promise_list[0]).data);
console.log(result);
}
promise_serial();
Promise 的并行执行
Promise.all() 用于将多个 Promise 实例,包装成一个新的 Promise 实例,实现等待多个 Promise 实例全部变为 fulfilled 状态才执行目标操作。
import axios from 'axios'
let infoList = []
let id_list = ['1', '2', '3']
let promise_list = []
for (let id of id_list) {
promise_list.push(axios.get(`http://jsonplaceholder.typicode.com/users/${
id}`))
}
Promise.all(promise_list).then((res) => {
infoList = res.map((item) => item.data)
console.log(infoList) // 得到预期结果
})
async await
async await 是基于Promise的语法糖,用于实现使用同步的语法实现异步
- async 函数返回的都是 Promise 对象
- await 只能在 async 函数中使用
- await 后跟 Promise 对象:会阻塞代码的运行,需等待 Promise 变为 resolved 状态返回结果后,才继续执行后续代码
- await 后跟非 Promise 对象:会原样返回
- await 相当于 Promise 的 then
- await 中的异常需用 try…catch 捕获
try {
const res = await p4;
console.log('await后的Promise执行成功后的返回值res:',res);
} catch (err) {
console.error('await后的Promise执行失败后的报错err:',err);
}
JS 代码在浏览器中的执行顺序
异步任务分为 微任务
和 宏任务
- 同步任务放入调用栈
Call Stack
- 微任务(Promise,async、await 等) 放入微任务队列
micro task queue
- 宏任务(setTimeout ,setInterval、ajax、Dom事件等) 放入
Web APIs
中
添加 async 的函数本身不是异步,内部代码会同步执行,遇到 await 时,await 后紧跟的函数会立即执行,是同步任务,await 语句后的代码才是异步微任务
async function async1() {
console.log("async1 start"); // 第 1 个打印
await async2(); // 先执行 async2() , 进入 async2 函数内
// await 后的代码都是微任务,将其放入微任务队列 --- 微任务 1
console.log("async1 end"); // 第 3 个打印
}
// 定义函数,先跳过
async function async2() {
console.log("async2"); // 第 2 个打印
}
// 代码从此开始执行,进入 async1 函数内
async1();
执行结果
async1 start
async2
async1 end
Promise 函数内的代码是同步执行,待 Promise 执行 resolve() 后会立即触发 then 函数,then 函数内的代码才是异步微任务
new Promise(function (resolve) {
console.log("promise 函数内"); // 第1个打印
resolve(); // Promise 状态变为 resolved , 立即触发了 then 函数
}).then(function () {
// then 函数是个微任务,将其放入微任务队列
console.log("then 函数内"); // 第3个打印
});
console.log("promise 函数外"); // 第2个打印
执行结果
promise 函数内
promise 函数外
then 函数内
setTimeout 和 setInterval 从被放入 Web APIs 开始计时,计时结束后,会放入回调队列(Callback Queue
),等待 event loop 触发执行
对象
创建对象的方法
- {}
- 构造函数,含 new Object()
- Object.create()
- 类
- 工厂函数
{}、Object()、new Object()、Object.create() 的区别
- {} 是通过字面量的方式创建空对象,原型为 Object.prototype,语法最简洁,也最常用。
- Object() 与 new Object() 功能相同,无论传入什么参数,其创建的对象的原型都是 Object.prototype,但 new Object() 创建对象的意图更明确
- Object.create() 创建的对象的原型由其传入的第一个参数决定。
【实战】判断变量是否为空对象
if(JSON.stringify(obj)==="{}"){
// 是空对象
}
数组
【实战】判断是否为数组
Array.isArray(val)
数组常用的 API
数组的API | 功能 | 入参 | 返回值 | 是否改变原数组 |
---|---|---|---|---|
unshift | 在数组头部追加元素 | 新元素 | 数组的新长度 | 改变 |
push | 在数组尾部追加元素 | 新元素 | 数组的新长度 | 改变 |
shift | 移除数组的第一项 | 无 | 被移除的元素 | 改变 |
pop | 移除数组最后一项 | 无 | 被移除的元素 | 改变 |
sort | 数组排序 | 排序规则函数 | 排序后的数组 | 改变 |
reverse | 数组反转 | 无 | 反转后的数组 | 改变 |
fill | 数组覆写 | 新元素,起始下标,终点下标 | 覆写后的数组 | 改变 |
splice | 拼接数组 | 下标,删除数量,新元素 | 移除元素的数组 | 改变 |
slice | 数组截取 | 起始下标,终点下标 | 截取的数组 | 不改变 |
filter | 数组过滤 | 过滤规则函数 | 过滤后的数组 | 不改变 |
concat | 数组合并 | 被合并的数组/元素 | 合并后的数组 | 不改变 |
map | 数组格式化 | 被合并的数组/元素 | 格式化后的数组 | 不改变 |
reduce | 数组缩减 | 缩减规则函数 | 计算结果 | 不改变 |
toString | 数组转字符串 | 无 | 字符串 | 不改变 |
join | 拼接元素 | 拼接符 | 字符串 | 不改变 |
数组的 API,有哪些是纯函数 ?
- concat
- map
- filter
- slice
纯函数:1.不改变源数组 2.返回一个数组
数组遍历
遍历方法 | 返回值 | 使用场景 | 备注 | 副作用 |
---|---|---|---|---|
for 循环 | —— | 遍历数组 | 通用 | 可以改变原数组 |
forEach 循环 | —— | 遍历数组 | ES5 新增,不支持中断和异步 | 可以改变原数组 |
for of 循环 | —— | 遍历数组 | ES6 新增 | 可以改变原数组 |
map | 格式化后的数组 | 格式化 | 数组的API | 不会改变原数组 |
filter | 过滤后的数组 | 过滤 | 数组的API | 不会改变原数组 |
reduce | 最终计算结果 | 累计 | 数组的API | 不会改变原数组 |
every | 匹配结果 | 全部匹配 | 数组的API | 不会改变原数组 |
some | 匹配结果 | 部分匹配 | 数组的API | 不会改变原数组 |
数组去重
最简单的是使用 Set
let oldList = [1, 2, 3, 3];
let newList = Array.from(new Set(oldList)); // 得到 [1, 2, 3]
其他思路: 创建一个新数组,循环遍历目标数组,若新数组中不存在则添加,否则不添加,用 reduce 可便捷实现
let arr = [1,2,3,4,4,1]
let newArr = arr.reduce((pre,cur)=>{
if(!pre.includes(cur)){
return pre.concat(cur)
}else{
return pre
}
},[])
console.log(newArr);// [1, 2, 3, 4]
事件
事件冒泡 vs 事件捕获
- 事件冒泡指事件从触发事件的元素开始,逐级向外传播至 html 节点
- 事件捕获指事件从 html 节点,逐级向内传播到触发事件的元素
异常
throw 语句手动抛出异常
// 用户定义的 throw 语句 --- 通用错误
throw new Error('The number is low');
try catch 语句手动捕获并处理异常
try {
// 执行目标代码
} catch (err) {
// 控制台打印报错信息
console.log(err);
} finally {
// 无论是否报错都会执行的代码
}
常见的异常:
- 通用错误 Error
- 语法错误 SyntaxError
- 类型错误 TypeError
- 引用错误 ReferenceError
- 范围错误 RangeError
垃圾回收 GC
导致内存泄漏的场景
- 不断创建全局变量
- 未及时清理的闭包
- DOM元素的引用
- 事件监听器
避免内存泄漏的方法
- 避免创建全局变量。
- 使用严格模式或者let/const声明变量。
- 及时解除DOM元素的引用。
- 在移除DOM元素之前,移除相关的事件监听器。
- 使用弱引用或者引用计数来处理循环引用的问题。(如使用 WeakMap 和 WeakSet)
- 使用try/catch/finally来确保资源在异常发生时也能被正确释放。
JS 的垃圾回收机制 GC
- 引用计数(之前)
- 标记清除(现代)
- 标记整理(优化)
- 分代式垃圾回收(V8引擎)
其他
延迟加载
https://blog.csdn.net/weixin_41192489/article/details/141133404
监控白屏、首屏
// 监听DOMContentLoaded事件来计算首屏时间和白屏时间
document.addEventListener('DOMContentLoaded', (event) => {
const perf = performance.timing;
// 首屏时间(First Paint)
const firstPaint = event.timeStamp - perf.navigationStart;
console.log(`首屏时间(First Paint): ${
firstPaint} ms`);
// 通过DOMContentLoaded事件的延迟来估算白屏时间
const whiteScreenTime = event.timeStamp - perf.fetchStart;
console.log(`估算的白屏时间: ${
whiteScreenTime} ms`);
});