前端面试题+算法题(三)
一、LeeCode 算法题
1、724. 寻找数组的中心下标
题目:给你一个整数数组
nums
,请计算数组的 中心下标 。数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
- 如果中心下标位于数组最左端,那么左侧数之和视为
0
,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。- 如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回
-1
。场景1:输入 nums = [1, 7, 3, 6, 5, 6] 输出 3
场景2:输入 nums = [1, 2, 3] 输出 -1
场景3:输入 nums = [2, 1, -1] 输出 0
小编思路:
左侧和 = 右侧和,则左侧和 * 2 + 当前元素值 = 总和
- reduce()方法:计算总和
- for()循环:lSum 记录左侧和,当满足 左侧和 * 2 + 当前元素值 = 总和 时,直接返回当前下标,不满足则当前元素加给 lSum,进入下一个循环
- 循环结束都没有满足条件则说明不存在中心下标,则返回 -1
var pivotIndex = function(nums) {let sum = nums.reduce((pre,cur)=>pre+cur, 0)let lSum = 0;for (let i = 0; i < nums.length; i++) {if(lSum * 2 + nums[i] === sum){return i}lSum += nums[i]}return -1 };
2、733. 图像渲染
题目:有一幅以
m x n
的二维整数数组表示的图画image
,其中image[i][j]
表示该图画的像素值大小。你也被给予三个整数sr
,sc
和color
。你应该从像素image[sr][sc]
开始对图像进行上色 填充 。为了完成 上色工作:
- 从初始像素开始,将其颜色改为
color
。- 对初始坐标的 上下左右四个方向上 相邻且与初始像素的原始颜色同色的像素点执行相同操作。
- 通过检查与初始像素的原始颜色相同的相邻像素并修改其颜色来继续 重复 此过程。
- 当 没有 其它原始颜色的相邻像素时 停止 操作。
最后返回经过上色渲染 修改 后的图像 。
场景1:输入 image = [[1, 1, 1], [1, 1, 0], [1, 0, 1]], 1, 1, 2 输出 [[2,2,2],[2,2,0],[2,0,1]]
场景2:输入 image = [[0, 0, 0], [0, 0, 0]], 0, 0, 0 输出 [[0,0,0],[0,0,0]]
小编思路:
- oldColor:记录待填充上色的像素
- onFill:递归函数,不断循环找出当前中心的四周是否像素相同,相同则染色,并继续递归该染色的四周
var floodFill = function (image, sr, sc, color) {let m = image.length;let n = image[0].length;let oldColor = image[sr][sc];if (oldColor === color) return image;const onFill = (x, y) => {if (x < 0 || x >= m || y < 0 || y >= n || image[x][y] !== oldColor) {return}image[x][y] = color;onFill(x - 1, y)onFill(x + 1, y)onFill(x, y - 1)onFill(x, y + 1)}onFill(sr,sc)return image };
二、面试题
1、JS 变量在内存中的堆栈存储了解多少?
基础类型存放于栈,引用类型存放在堆
(1)代码案例
function fn(obj) {obj = { m: 10 };console.log(obj.m); // 10
}
const obj1 = { m: 20 };
fn(obj1);
console.log(obj1.m); // 20
(2)代码解析
①当执行 const obj1 = { m: 20 }; 时,会在堆内存开辟一块空间,存储 { m: 20 } ,同时利用变量 obj1 记录该堆内存地址,而 obj1 存放于栈
②接着执行 fn(obj1),会把 obj1 记录的地址值作为实参传递到方法 fn 中,同时记录在 obj 副本变量中(注意:JS的传参都是值传递)
③再下来执行 obj = { m: 10 };,相当于重新开辟了一个堆内存空间存储 { m: 10 },同时把地址记录到 obj 中
④然后执行 console.log(obj.m) 会根据 obj 记录的地址2,找到 { m: 10 },所以输出10
⑤同理执行 console.log(obj1.m) 会根据 obj1 记录的地址2,找到 { m: 20 },所以会输出20
2、变量提升与函数提升哪个优先级更高?
(1)问题示例
以下代码输出结果是什么?
console.log(a);
var a = 2;
function a() {}
console.log(a);
// 答案
[Function: a]
2
(2)演变过程
①优先级:函数提升 > 变量提升
②代码执行流程
- 函数提升
-
function a() {} console.log(a); var a = 2; console.log(a);
- 变量提升
-
function a() {} console.log(a); a = 2; console.log(a);
3、setTimeout 与 setInterval 实现倒计时有什么区别?
(1)基本认识
- setTimeout
- 每隔一秒生成一个任务,等待一秒后执行,执行完成后,再生成下一个任务,等待一秒后执行,如此循环【保证的是每个任务间的间隔是1秒】
- 简言之:任务结束到下一个任务开始的间隔
- setInterval
- 无视执行时间,每个一秒往任务队列添加一个任务,等待一秒后执行,这会导致任务执行间隔小于1秒,甚至任务堆积
- 简言之:任务开始到下一个任务开始的间隔
(2)实现代码
- setTimeout
-
const timer = (time) => {setTimeout(() => {console.log(time);if (time > 0) timer(time - 1);}, 1000); };timer(10);
-
- setInterval
-
let time = 10; const timer = setInterval(() => {console.log(time);time--;if (time < 0) clearInterval(timer); }, 1000);
-
(3)图例
4、事件循环
(1)基本认识
事件循环:同步任务、微任务、宏任务
通俗易懂的比喻:把 JavaScript 事件循环想象成一个银行柜台业务
- 只有一个柜台(单线程):JavaScript 是单线程的,同一个时间只能处理一件事情
- 客户(任务):每个办理的业务都是一个任务
①三种角色的定义
1. 同步客户(同步任务)
1.1 特征:必须立即处理,不办完后面的人就得等着
1.2 例子:console.log()、变量声明、普通的运算
2. 微任务客户2.1 特征:VIP 客户,有专门的 VIP 快速通道。只要柜台一有空,必须立即处理所有 VIP 客户,比普通客户优先级高
2.2 例子:Promise.then() / Promise.catch() / Promise.finally()、await(后面的代码)、MutationObserver(监听DOM变化)、queueMicrotask()
3. 宏任务客户3.1 特征:普通客户,需要排队取号,在下一个普通队列中处理
3.2 例子:setTimeout()、setInterval()、I/O 操作(读取文件)、UI渲染
②规则流程
1. 先处理完所有同步客户(清空大厅)
2. 然后立即处理所有 VIP 客户(微任务)
3. 如果有 UI 渲染需要,进行一次页面渲染
4. 最后从普通队列(宏任务)中取一个客户处理
5. 重复这个过程
③示例一
- 代码
-
console.log('1. 开始营业'); // 同步任务setTimeout(() => {console.log('6. 宏任务: setTimeout'); // 宏任务 }, 0);Promise.resolve().then(() => {console.log('4. 微任务: Promise 1'); // 微任务 });console.log('2. 同步任务进行中'); // 同步任务Promise.resolve().then(() => {console.log('5. 微任务: Promise 2'); // 微任务 });console.log('3. 同步任务结束'); // 同步任务// 执行结果顺序: // 1. 开始营业 // 2. 同步任务进行中 // 3. 同步任务结束 // 4. 微任务: Promise 1 // 5. 微任务: Promise 2 // 6. 宏任务: setTimeout
- 过程分析
- 🏃♂️ 执行所有同步任务:1 → 2 → 3
- 🚀 执行所有微任务:4 → 5
- ⏰ 执行一个宏任务:6
④示例二
- 代码
-
console.log("1");Promise.resolve().then(() => {console.log("2");setTimeout(() => {console.log("3");}, 0); });setTimeout(() => {console.log("4");new Promise((resolve) => {console.log("5");resolve();}).then(() => {console.log("6");}); }, 0);console.log("7");// 输出:1 7 2 4 5 6 3
- 过程分析
- 1. 🏃♂️ 执行所有同步任务,并放置对应的宏任务、微任务:1 → 7
-
// 输出:1 7// 宏任务列表 const macroTaskQueue = [{console.log('4');new Promise((resolve) => {console.log('5');resolve();}).then(() => {console.log('6');}); }];// 微任务列表 const microTaskQueue = [{console.log('2');setTimeout(() => {console.log('3');}, 0); }]
- 2. 🚀 取出微任务列表执行:2
-
// 输出:1 7 2// 宏任务列表 const macroTaskQueue = [{console.log('4');new Promise((resolve) => {console.log('5');resolve();}).then(() => {console.log('6');}); }, {console.log('3'); }];// 微任务列表 const microTaskQueue = []
- 3. ⏰ 执行宏任务:4 → 5
-
// 输出:1 7 2 4 5// 宏任务列表 const macroTaskQueue = [{console.log('3'); }]; // 微任务列表 const microTaskQueue = [{console.log('6'); }];
- 4. 🚀 二次取出微任务列表执行:6
-
// 输出:1 7 2 4 5 6// 宏任务列表 const macroTaskQueue = [{console.log('3'); }]; // 微任务列表 const microTaskQueue = []
- 5. ⏰ 二次取出宏任务列表执行:3
-
// 输出:1 7 2 4 5 6 3// 宏任务列表 const macroTaskQueue = []// 微任务列表 const microTaskQueue = []
(2)深入认识
①代码示例
setTimeout(() => {console.log('timeout');
});function test() {console.log('test');return Promise.resolve().then(() => {test();});
}test();
②过程分析
以上代码示例,会持续输出 test 且不会输出 timeout
- 1. 🏃♂️ 执行所有同步任务,并放置对应的宏任务、微任务:1 → 2
-
// 输出:test// 宏任务列表 const macroTaskQueue = [{console.log('timeout'); }]// 微任务列表 const microTaskQueue = [{test(); }]
- 2. 🚀 执行所有微任务:test()
-
// 输出:test test// 宏任务列表 const macroTaskQueue = [{console.log('timeout'); }]// 微任务列表 const microTaskQueue = [{test(); }]
- 3. ⏰ 执行微任务反复生成新的 test() 微任务,陷入微任务无限循环,宏任务无法排上队
(3)进阶一
①代码示例
Promise.resolve().then(() => {console.log(0);return Promise.resolve(4);
}).then((res) => {console.log(res);
});Promise.resolve().then(() => {console.log(1);
}).then(() => {console.log(2);
}).then(() => {console.log(3);
}).then(() => {console.log(5);
});
②过程分析
结果输出:0 1 2 3 4 5
- 1. 🏃♂️ 执行所有同步任务,并放置对应的宏任务、微任务,无输出
-
// 输出:// 微任务列表 const microTaskQueue = [{(() => {console.log(0);return Promise.resolve(4); // 返回一个新的Promise}).then((res) => {console.log(res);}),(() => {console.log(1);}).then(() => {console.log(2);}).then(() => {console.log(3);}).then(() => {console.log(5);}) }];// 宏任务列表 const macroTaskQueue = [];
- 2. 🚀 执行第一个微任务,碰到下一轮微任务则放回微任务列表,输出:0
-
// 输出:0// 处理第一个微任务 (() => {return Promise.resolve().then(() => {return 4;}) }).then((res) => {console.log(res); })// 微任务列表 const microTaskQueue = [{(() => {console.log(1);}).then(() => {console.log(2);}).then(() => {console.log(3);}).then(() => {console.log(5);}) }];// 宏任务列表 const macroTaskQueue = [];//======================================================================== // 输出:0// 处理第一个微任务 Promise.resolve().then(() => {return 4; }).then((x) => {return x; }).then((res) => {console.log(res); })// 微任务列表 const microTaskQueue = [{(() => {console.log(1);}).then(() => {console.log(2);}).then(() => {console.log(3);}).then(() => {console.log(5);}) }];// 宏任务列表 const macroTaskQueue = [];//======================================================================== // 输出:0// 微任务列表 const microTaskQueue = [{(() => {console.log(1);}).then(() => {console.log(2);}).then(() => {console.log(3);}).then(() => {console.log(5);}),(() => {return 4;}).then((x) => {return x;}).then((res) => {console.log(res);}) }];// 宏任务列表 const macroTaskQueue = [];
- 3. 🚀 执行下一个微任务,碰到下一轮微任务则放回微任务列表,输出:0 1
-
// 输出:0 1// 微任务列表 const microTaskQueue = [{(() => {return 4;}).then((x) => {return x;}).then((res) => {console.log(res);}),(() => {console.log(2);}).then(() => {console.log(3);}).then(() => {console.log(5);}) }];// 宏任务列表 const macroTaskQueue = [];//================================================================= // 输出:0 1// 微任务列表 const microTaskQueue = [{(() => {console.log(2);}).then(() => {console.log(3);}).then(() => {console.log(5);}),(() => {return 4;}).then((res) => {console.log(res);}), }];// 宏任务列表 const macroTaskQueue = [];
- 4. 🚀 执行下一个微任务,碰到下一轮微任务则放回微任务列表,输出:0 1 2
-
// 输出:0 1 2// 微任务列表 const microTaskQueue = [{(() => {return 4;}).then((res) => {console.log(res);}),(() => {console.log(3);}).then(() => {console.log(5);}) }];// 宏任务列表 const macroTaskQueue = [];//====================================================================== // 输出:0 1 2// 微任务列表 const microTaskQueue = [{(() => {console.log(3);}).then(() => {console.log(5);}),(() => {console.log(4);}), }];// 宏任务列表 const macroTaskQueue = [];
- 5. 🚀 执行下一个微任务,碰到下一轮微任务则放回微任务列表,输出:0 1 2 3
-
// 输出:0 1 2 3// 微任务列表 const microTaskQueue = [{(() => {console.log(4);}),(() => {console.log(5);}), }];// 宏任务列表 const macroTaskQueue = [];
- 6. 🚀 执行下一个微任务,输出:0 1 2 3 4 5
-
// 输出:0 1 2 3 4 5// 微任务列表 const microTaskQueue = [];// 宏任务列表 const macroTaskQueue = [];
(4)进阶二
①代码示例
const first = () => (new Promise((resolve, reject) => {console.log(3);let p = new Promise((resolve, reject) => {console.log(7);setTimeout(() => {console.log(5);resolve(6);console.log(p);}, 0);resolve(1);});resolve(2);p.then(arg => {console.log(arg);});
}));first().then(arg => {console.log(arg);
})console.log(4);
②过程分析
输出结果:3 7 4 1 2 5 Promise { 1 }
- 1. 🏃♂️ 执行所有同步任务,并放置对应的宏任务、微任务,输出:3 7 4
-
// 输出: 3 7 4// 微任务列表 const microTaskQueue = [p.then(arg => {console.log(arg);})first().then(arg => {console.log(arg);}) ];// 宏任务列表 const macroTaskQueue = [() => {console.log(5);resolve(6);console.log(p);} ];
- 2. 🚀 执行第一轮微任务,输出:3 7 4 1 2
-
// 执行微任务 p.then((arg = 1) => {console.log(arg); })// 输出: 3 7 4 1// 微任务列表 const microTaskQueue = [first().then(arg => {console.log(arg);}) ];// 宏任务列表 const macroTaskQueue = [() => {console.log(5);resolve(6);console.log(p);} ];//============================================================================ // 执行微任务 first().then((arg = 2) => {console.log(arg); }) // 输出: 3 7 4 1 2// 微任务列表 const microTaskQueue = [];// 宏任务列表 const macroTaskQueue = [() => {console.log(5);resolve(6);console.log(p);} ];
- 3. ⏰ 执行宏任务,输出:3 7 4 1 2 5 Promise { 1 }
-
// 执行宏任务 () => {console.log(5);resolve(6);console.log(p); }// 输出: 3 7 4 1 2 5 Promise { 1 }// 微任务列表 const microTaskQueue = [];// 宏任务列表 const macroTaskQueue = [];
(5)进阶三
①代码示例
let a;
let b = new Promise((resolve) => {console.log(1);setTimeout(() => {resolve();}, 1000);
}).then(() => {console.log(2);
});a = new Promise(async (resolve) => {console.log(a);await b;console.log(a);console.log(3);await a;resolve(true);console.log(4);
})console.log(5);
②过程分析
输出结果:1 undefined 5 2 Promise { <pending> }
先将 await 转换为 promise 便于理解
let a;
let b = new Promise((resolve) => {console.log(1);setTimeout(() => {resolve();}, 1000);
}).then(() => {console.log(2);
});a = new Promise(async (resolve) => {console.log(a);b.then(() => {console.log(a);console.log(3);a.then(() => {resolve(true);console.log(4);})});
})console.log(5);
- 1. 🏃♂️ 执行所有同步任务,并放置对应的宏任务、微任务,输出:1 undefined 5
-
// 输出:1 undefined 5// 微任务列表 const microTaskQueue = [b.then(() => {console.log(2);}).then(() => {console.log(a);console.log(3);a.then(() => {resolve(true);console.log(4);})}) ];// 宏任务列表 const macroTaskQueue = [(() => {Promise{b}.resolve();}, 1000) ];
- 2. 🚀 执行微任务,输出:1 undefined 5 (等待一秒)
-
// 由于b.then 在 setTimeout 中 resolve,因此优先执行 (() => {Promise{b}.resolve(); }, 1000)// 输出:1 undefined 5 (等待一秒)// 微任务列表 const microTaskQueue = [(() => {console.log(2);}).then(() => {console.log(a);console.log(3);a.then(() => {resolve(true);console.log(4);})}) ];// 宏任务列表 const macroTaskQueue = [];
- 3. 🚀 执行微任务,输出:1 undefined 5 (等待一秒) 2
-
// 执行微任务 (() => {console.log(2); }).then(() => {console.log(a);console.log(3);a.then(() => {resolve(true);console.log(4);}) })// 输出:1 undefined 5 (等待一秒) 2// 微任务列表 const microTaskQueue = [() => {console.log(a);console.log(3);a.then(() => {resolve(true);console.log(4);})} ];// 宏任务列表 const macroTaskQueue = [];
- 4. 🚀 执行微任务,输出:1 undefined 5 (等待一秒) 2 Promise { <pending> } 3
-
// 执行微任务 () => {console.log(a);console.log(3);a.then(() => {resolve(true);console.log(4);}) }// 输出:1 undefined 5 (等待一秒) 2 Promise { <pending> } 3// 微任务列表 const microTaskQueue = [a.then(() => {resolve(true);console.log(4);}) ];// 宏任务列表 const macroTaskQueue = [];
- 5. 结束:因为 a.then 需要被 resolve 才会被执行,而 resolve 又在 a.then 内,因此 a.then 无法执行
(6)进阶四
①代码示例
const promiseA = Promise.resolve('1');
promiseA.then((res) => {console.log('a:', res);
}).then((res) => {console.log('a:', res);
});const promiseB = Promise.resolve('2');
promiseB.then((res) => {console.log('b:', res);
})
promiseB.then((res) => {console.log('b:', res);
});
②过程分析
输出结果:a:1 b:2 b:2 a: undefined
- 1. 🏃♂️ 执行所有同步任务,并放置对应的宏任务、微任务,无输出
-
// 微任务列表 const microTaskQueue = [((res = '1') => {console.log('a:', res);}).then((res) => {console.log('a:', res);}),(res = '2') => {console.log('b:', res);},(res = '2') => {console.log('b:', res);} ];// 宏任务列表 const macroTaskQueue = [];
- 2. 🚀 执行微任务,输出:a:1
-
// 执行微任务 ((res = '1') => {console.log('a:', res); }).then((res) => {console.log('a:', res); }),// 输出:a: 1// 微任务列表 const microTaskQueue = [(res = '2') => {console.log('b:', res);},(res = '2') => {console.log('b:', res);},(res = undefined) => {console.log('a:', res);} ];// 宏任务列表 const macroTaskQueue = [];
- 3. 🚀 执行微任务,输出:a:1 b:2
-
// 执行微任务 (res = '2') => {console.log('b:', res); },// 输出:a: 1 b: 2// 微任务列表 const microTaskQueue = [(res = '2') => {console.log('b:', res);},(res = undefined) => {console.log('a:', res);} ];// 宏任务列表 const macroTaskQueue = [];
- 4. 🚀 执行微任务,输出:a:1 b:2 b:2
-
// 执行微任务 (res = '2') => {console.log('b:', res); },// 输出:a: 1 b: 2 b: 2// 微任务列表 const microTaskQueue = [(res = undefined) => {console.log('a:', res);} ];// 宏任务列表 const macroTaskQueue = [];
- 5. 🚀 执行微任务,输出:a:1 b:2 b:2 a: undefined
-
// 执行微任务 (res = undefined) => {console.log('a:', res); }// 输出:a: 1 b: 2 b: 2 a: undefined// 微任务列表 const microTaskQueue = [];// 宏任务列表 const macroTaskQueue = [];
(7)进阶五
①代码示例
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script type="text/javascript">console.log(1);Promise.resolve().then(() => {console.log(2);})setTimeout(() => {console.log(3);}, 0)</script><script type="text/javascript">console.log(4);</script>
</body>
</html>
②过程分析
输出结果:1 2 4 3
- 1. script 代码片段会被浏览器内容做为 task 调度,放入宏任务队列
-
// 微任务列表 const microTaskQueue = [];// 宏任务列表 const macroTaskQueue = [{console.log(1);Promise.resolve().then(() => {console.log(2);})setTimeout(() => {console.log(3);}, 0) }, {console.log(4); }];
- 2. ⏰ 执行宏任务,输出:1
-
// 执行宏任务 {console.log(1);Promise.resolve().then(() => {console.log(2);})setTimeout(() => {console.log(3);}, 0) }// 输出:1// 微任务列表 const microTaskQueue = [{console.log(2); }];// 宏任务列表 const macroTaskQueue = [{console.log(4); },{console.log(3); }];
- 3. 🚀 执行微任务,输出:1 2
-
// 执行微任务 {console.log(2); }// 输出:1 2// 微任务列表 const microTaskQueue = [];// 宏任务列表 const macroTaskQueue = [{console.log(4); },{console.log(3); }];
- 4. ⏰ 执行宏任务,输出:1 2 4
-
// 执行微任务 {console.log(4); }// 输出:1 2 4// 微任务列表 const microTaskQueue = [];// 宏任务列表 const macroTaskQueue = [{console.log(3); }];
- 5. ⏰ 执行宏任务,输出:1 2 4 3
-
// 执行微任务 {console.log(3); }// 输出:1 2 4 3// 微任务列表 const microTaskQueue = [];// 宏任务列表 const macroTaskQueue = [];