这个文章主要用来收集一些面试题目
每次面试要共享屏幕手写面试题都会挂掉
有种尿尿被人看着的紧张感,脑袋里一片空白
2024
依次执行一系列任务,并可以中断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
|
function processTasks(...tasks) { let isPaused = false; let currentTaskIndex = 0;
async function start() { while (currentTaskIndex < tasks.length) { if (isPaused) return;
const task = tasks[currentTaskIndex]; await task(); currentTaskIndex++; } }
function pause() { isPaused = true; }
function resume() { if (isPaused) { isPaused = false; start(); } }
return { start, pause, resume }; }
const task1 = () => new Promise((resolve) => setTimeout(() => { console.log("Task 1 done"); resolve(); }, 1000) ); const task2 = () => new Promise((resolve) => setTimeout(() => { console.log("Task 2 done"); resolve(); }, 1000) ); const task3 = () => new Promise((resolve) => setTimeout(() => { console.log("Task 3 done"); resolve(); }, 1000) );
const { start, pause, resume } = processTasks(task1, task2, task3);
start();
setTimeout(() => { pause(); console.log("Tasks paused"); }, 2000);
setTimeout(() => { resume(); console.log("Tasks resumed"); }, 4000);
|
闭包漏洞与解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var obj1 = (function () { var obj2 = { a: 1, b: 2, };
return { get: function (k) { return obj2[k]; }, }; })();
Object.defineProperty(Object.prototype, "n", { get() { return this; }, });
obj1.get("n").c = 1; obj1.get("c");
|
解决闭包漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| var obj1 = (function () { var obj2 = { a: 1, b: 2, }; Object.setPrototypeOf(obj2, null); return { get: function (k) { return obj2[k]; }, }; })();
var obj1 = (function () { var obj2 = { a: 1, b: 2, }; return { get: function (k) { if (!obj2.hasOwnProperty(k)) { return `${k}在此对象上不存在`; } return obj2[k]; }, }; })();
|
(todo) 关于 interface 和 type
(todo) useDebounce
使用数组 reduce 方法实现 forEach、map、filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| function forEach(arr, cb) { arr.reduce((_, current, index) => { cb(current, index, arr); return null; }, arr[0]); }
function myForEach(arr, callback) { arr.reduce((_, current, index) => { callback(current, index, arr); return null; }, null); }
function map(arr, cb) { const newArr = []; arr.reduce((_, current, index) => { newArr.push(cb(current, index, arr)); return null; }, arr[0]); return arr; }
function myMap(arr, callback) { return arr.reduce((acc, current, index) => { acc.push(callback(current, index, arr)); return acc; }, []); }
function filter(arr, cb) { const newArr = []; arr.reduce((_, current, index) => { if (!cb(current, index, arr)) { newArr.push(current); } return null; }, arr[0]); return newArr; }
function myFilter(arr, callback) { return arr.reduce((acc, current, index) => { if (callback(current, index, arr)) { acc.push(current); } return acc; }, []); }
forEach([1, 2, 3], (n) => { console.log(n); }); console.log(map([1, 2, 3], (n) => n * 2)); console.log(filter([1, 2, 3], (n) => n === 2));
|
写一个 Emitter,需要完成事件的注册、监听和释放
1 2 3 4 5 6 7
|
const emitter = new Emitter(); const sub1 = emitter.subscribe("click", (...args) => console.log(args)); const sub2 = emitter.subscribe("click", (...args) => console.log(args)); emitter.emit("click", "1", "2"); sub1.release();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Emitter { constructor() { this.events = {}; }
subscribe(eventName, callback) { if (!this.events[eventName]) { this.events[eventName] = []; }
this.events[eventName].push(callback);
return { release: () => { this.events[eventName] = this.events[eventName].filter( (cb) => cb !== callback ); }, }; }
emit(eventName, ...args) { if (this.events[eventName]) { this.events[eventName].forEach((callback) => callback(...args)); } } }
|
(todo) React:不使用 useEffect 实现 useMemo
(todo) Vue: 从响应式数据创建到触发 watch 到重新渲染,中间发生了什么
(todo) 封装一个 JSONP 函数,并支持 thenable 属性
找出最接近的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| const arr2 = [1, 5, 9, 15, 28, 33, 55, 78, 99];
function findNext(n, arr) { let difference; let matched = 0; for (let i in arr) { const current = arr[i]; const absDiff = Math.abs(current - n); if (difference === undefined) difference = absDiff; if (difference === 0) { matched = current; break; } else if (difference > absDiff) { difference = absDiff; matched = current; } else if (difference === absDiff) { matched = matched > current ? matched : current; } } return matched; }
console.log(findNext(1, arr2)); console.log(findNext(44, arr2)); console.log(findNext(6, arr2)); console.log(findNext(7, arr2)); console.log(findNext(8, arr2));
|
分析
遍历一遍,比较绝对值,绝对值为 0 那就是命中
绝对值越小,就记录下来
绝对值相等,就比大小
(todo) 网站中图片的优化处理
如果网站里有非常多的图片,并且要求图片质量,应该怎么做优化
(todo) 函数柯里化
(todo) 实现一个高精度的倒计时,解决 setTimeout 和 setInterval 的精度问题
浏览器事件循环和 Node.js 的事件循环
(todo)TypeScript 的问题
写一个泛型,检查某个属性是否在对象中
检查两个 interface 是否有相同的属性
(todo) React 中 hook 机制是怎么实现的
垃圾回收机制,循环引用,内存泄漏
原型链,js class 的本质
(todo) 设计模式相关
面试中被问到让写设计模式的模板,一时间大脑空白
单例模式
单例模式的定义是一个构造函数或者类,最多只能有一个实例,即使多次构造,如 create 方法或者 new 或者其他… 均不会出现新的实例
实现思路,记录已创建的实例,并判断
使用场景是一些只能存在一个实例的地方,比如覆盖全屏的 loading,共享的资源,如配置文件,状态库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Singleton { private static _instance: Singleton | null = null; public name = ""; constructor(name) { if (Singleton._instance) throw new Error("error: 单例已构建,使用getInstance访问"); this.name = name; Singleton._instance = this; }
static getInstance(name?: string): Singleton { if (!Singleton._instance) { Singleton._instance = new Singleton(name); } return Singleton._instance; } }
const instance1 = new Singleton("First Instance"); console.log(instance1.name);
const instance2 = Singleton.getInstance(); console.log(instance2.name);
const instance3 = new Singleton("Second Instance");
|
也可以用闭包实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const Singleton = (function () { let instance; function createInstance(name) { return { name, }; } return { getInstance(name = "") { if (!instance) { instance = createInstance(name); } return instance; }, }; })();
|
适配器模式
通过一些方式,将一个接口转换为另一个接口所需要的形态
使用场景如版本迭代对旧数据的兼容,对通过对接口数据的处理,兼容到不同的应用
工厂模式和抽象工厂
共同点:隐藏了具体产品的细节(产品类),并隐藏产品创建的具体过程(工厂函数)。在使用的时候都通过工厂函数的接口来获取产品实例
不同点:
- 概念:工厂模式用于创建单一产品,抽象工厂通过提供一个工厂接口来创建一组相关联的产品
- 复杂度:抽象工厂模式是解决产品组合而拓展的工厂模式
工厂模式:将创建实例的逻辑封装在工厂方法中,简化实例的创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| class ProductA { constructor(name) { this.name = name + "A"; } }
class ProductB { constructor(name) { this.name = name + "B"; } }
function productFactory(type, name) { switch (type) { case "A": return new ProductA(name); case "B": return new ProductB(name); default: throw new Error("Invalid product type"); break; } }
const productA = productFactory("A", "product "); const productB = productFactory("B", "product ");
function factory(type) { if (type === "A") return new ProductA(); else return new ProductB(); }
const factorySymbol = Symbol("factory");
class ProductA { constructor(token: symbol) { if (token !== factorySymbol) throw new Error("Use factory to create instances."); } } function factory(type) { if (type === "A") return new ProductA(factorySymbol); }
|
抽象工厂模式:抽象工厂模式有产品族和多工厂的概念,工厂通过抽象化之后,多个工厂可以组合出不同的产品,并且有相同的接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| abstract class Button { render(): void; } abstract class TextBox { render(): void; }
class WindowsButton implements Button { render() { console.log("windows button"); } }
class MacButton implements Button { render() { console.log("mac button"); } }
class WindowsTextBox implements TextBox { render() { console.log("windows textbox"); } }
class MacTextBox implements TextBox { render() { console.log("mac textbox"); } }
abstract class UICreator { createButton(): Button; createTextBox(): TextBox; }
class WindowsUICreator { createButton() { return new WindowsButton(); } createTextBox() { return new WindowsTextBox(); } } class MacUICreator { createButton() { return new MacButton(); } createTextBox() { return new MacTextBox(); } }
function uiCreator(creator: UICreator) { const button = creator.createButton(); const textBox = creator.createTextBox();
button.render(); textBox.render(); }
uiCreator(new MacUICreator());
|
实际应用:
工厂模式:
- 简单的对象创建
- 减少重复代码
- 解耦抽象类和业务代码
- 隐藏复杂的对象构造
- 例如: 创建不同的用户
抽象工厂模式:
- 创建一族有关联的产品
- 保证产品一致性
- 多平台或多配置
- 复杂系统架构
- 例如:跨平台 UI 库
观察者模式
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface Observer { name: string; update(state: any): void; }
interface Subject { observer: Observer[]; state: any; attach(observer: Observer): void; detach(observer: Observer): void; notify(): void; setState(state: any): void; }
|
2022
Vue2 父子组件生命周期顺序
今天被问到的问题:
组件的生命周期相关 现在有一对父子组件 他们一定有四个生命周期需要执行
父组件的 beforeCreate 父组件的 created 子组件的 beforeCreate 子组件的 created
这四个的执行顺序是怎样的
我一下子没有迷糊过来…. 回答错误了
正确的答案是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| export default Vue.extend({ name: 'HomeView', components: { HelloWorld, }, beforeCreate() { console.log('父元素', 'beforeCreate') }, created() { console.log('父元素', 'created') }, beforeMount() { console.log('父元素', 'beforeMount') }, mounted() { console.log('父元素', 'mounted') }, });
export default Vue.extend({ name: 'HelloWorld', props: { msg: String, }, beforeCreate() { console.log('子元素', 'beforeCreate') }, created() { console.log('子元素', 'created') }, beforeMount() { console.log('子元素', 'beforeMount') }, mounted() { console.log('子元素', 'mounted') }, });
|
1 2 3 4 5 6 7 8
| 父元素 beforeCreate 父元素 created 父元素 beforeMount 子元素 beforeCreate 子元素 created 子元素 beforeMount 子元素 mounted 父元素 mounted
|
我觉得是因为在 mounted 之前,Vue 要完成组件树的构建,构建完成之后,从子组件开始依次向上渲染。所以 beforeMount 是一个分界线
2020
关于 this 指向的
1 2 3 4 5 6 7 8 9
| var user = { count: 1, getCount: function () { return this.count; }, }; console.log(user.getCount()); var func = user.getCount; console.log(func());
|
分析:
var func = user.getCount
这句,func
是一个返回this.count
的方法
可以理解为window.func()
可以复制到控制台里,输入window.count = 123
,执行func()
,将会输出 123
关于中括号语法取值的
1 2 3 4 5 6 7 8 9 10
| var a = {}; var b = { key: "b" }; var c = { key: "c" }; var d = [3, 4, 5]; a[b] = 123; a[c] = 345; a[d] = 333; console.log(a[b]); console.log(a[c]); console.log(a[d]);
|
分析:
中括号语法取值之前,会先toString()
一下
所以就变成了:
1 2 3 4 5
| a["[object Object]"] = 123; a["[object Object]"] = 345; a["3,4,5"] = 333;
|
关于函数 return function 的小问题
1 2 3 4 5 6 7 8 9 10 11 12 13
| var a = function (val, index) { console.log(index); return { fn: function (name) { return a(name, val); }, }; };
var b = a(0); b.fn(1); b.fn(2); b.fn(3);
|
分析:
执行b = a(0)
,b 被赋值为{fn: function (name) { return a(name, 0) }}
执行b.fn('val')
的时候,会返回一个对象{fn: function (name) { return a(name, 'val') }}
,这个对象没有赋值给任何变量(垃圾回收),b从未被改变
很简单的问题,但是很迷惑
可以试试这样
1 2 3
| var q = b.fn(1); var w = b.fn(2); var e = b.fn(3);
|
let 和 var 的区别
1 2 3 4 5 6 7 8 9 10 11 12
| for (var i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 0); } for (let i = 0; i < 5; i++) { setTimeout(() => { console.log(i); }, 0); }
|
不用 let, 用闭包试试
1 2 3 4 5
| for (var i = 0; i < 5; i++) { (function (j) { setTimeout(() => console.log(j), 1000); })(i); }
|
异步,事件循环,宏任务微任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| console.log("start"); setTimeout(() => { console.log("children2"); Promise.resolve().then(() => { console.log("children3"); }); }, 0);
new Promise(function (resolve, reject) { console.log("children4"); setTimeout(function () { console.log("children5"); resolve("children6"); }, 0); }).then((res) => { console.log("children7"); setTimeout(() => { console.log(res); }, 0); });
|
思路:
浏览器会先把代码看一遍,遇到直接执行的就执行(第一个宏任务)
遇到宏任务和微任务代码先按顺序扔一边
同步的执行完之后就执行微任务
微任务里面肯定有同步任务,微任务,宏任务
遇到同步任务直接解决,遇到微任务在微任务后面继续排队,遇到宏任务在宏任务后面继续排队
整体的执行顺序是:
有同步任务先解决,然后解决微任务,最后解决宏任务
以上输出结果:
1 2 3 4 5 6 7
| "start"; "children4"; "children2"; "children3"; "children5"; "children7"; "children6";
|
宏任务
- script(整体代码)
- setTimeout,setInterval
- xhr
- I/O
- UI 交互事件
微任务