裸辞8年前端的面试笔记——JavaScript篇(一)

裸辞后的第二个月开始准备找工作,今天是第三天目前还没有面试,现在的行情是一言难尽,都在疯狂的压价。

下边是今天复习的个人笔记

一、事件循环

JavaScript 的事件循环(Event Loop)是其实现异步编程的关键机制。

从原理上讲,JavaScript 是单线程语言,只有一个主线程来执行代码,这意味着同一时间只能做一件事。但为了实现异步操作(比如处理用户交互、网络请求等),引入了事件循环机制。
事件循环涉及到几个重要概念:

  1. 调用栈(Call Stack): 是一种数据结构,用于记录函数的调用关系。函数调用时入栈,执行完毕后出栈。
  2. 任务队列(Task Queue): 也叫消息队列,用于存放异步操作的回调函数。当异步操作完成时,对应的回调函数会被放入任务队列。
  3. 宏任务(Macrotask): 包括 script (整体代码)、setTimeout、setInterval、setImmediate(Node.js 环境)、requestAnimationFrame 等。
  4. 微任务(Microtask): 包括 Promise 的 then、catch、finally,MutationObserver 等。

事件循环的执行过程大致如下:

  1. 首先执行调用栈中的同步任务。
  2. 当遇到异步任务时,异步任务会被挂起,不会阻塞主线程,继续执行同步任务。
  3. 当同步任务执行完毕后,开始处理微任务队列,依次执行微任务队列中的任务。
  4. 微任务执行完毕后,开始执行宏任务队列中的任务,每执行一个宏任务,就会检查并执行微任务队列。
  5. 重复上述过程,不断循环,这就是事件循环。

例如:

console.log('start');setTimeout(() => {console.log('setTimeout');
}, 0);Promise.resolve().then(() => {console.log('Promise then');
});console.log('end');

在这段代码中,首先 console.log('start') console.log('end') 作为同步任务在调用栈中依次执行。setTimeout 是宏任务,会被放到宏任务队列。Promise.resolve().then() 是微任务,会被放到微任务队列。当同步任务执行完后,开始执行微任务队列中的 Promisethen 回调,打印 Promise then,最后执行宏任务队列中的 setTimeout 回调,打印 setTimeout

二、Promise.all 和 Promise.race

Promise.all 和 Promise.race ,它们都是 Promise 的静态方法,在处理多个 Promise 时非常有用,以下是它们的详细介绍:

Promise.all

  • 它接受一个包含多个 Promise 对象的可迭代对象(比如数组)作为参数。
  • 只有当传入的所有 Promise 都成功时,Promise.all 才会返回一个成功的 Promise,其结果是一个包含所有 Promise 结果的数组,顺序和传入的 Promise 顺序一致。
  • 只要有一个 Promise 失败,Promise.all 就会立即返回一个失败的 Promise,失败原因就是第一个失败的 Promise 的原因。

例如:

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);Promise.all([promise1, promise2, promise3]).then((values) => {console.log(values); //输出: [1, 2, 3]}).catch((error) => {console.error(error);});

有失败的Promise:

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject(new Error('Promise 2 failed'));
const promise3 = Promise.resolve(3);Promise.all([promise1, promise2, promise3]).then((values) => {console.log(values); // 不会输出}).catch((error) => {console.error(error.message); // 输出: Promise 2 failed});

Promise.race

  • 同样接受一个包含多个 Promise 对象的可迭代对象作为参数。
  • 只要其中一个 Promise 率先改变状态(无论是成功还是失败),Promise.race 就会返回这个 Promise 的结果或原因。

例如:

const promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('Promise 1 resolved');}, 2000);
});const promise2 = new Promise((resolve, reject) => {setTimeout(() => {reject(new Error('Promise 2 failed'));}, 1000);
});Promise.race([promise1, promise2]).then((value) => {console.log(value);}).catch((error) => {console.error(error.message); // Promise 2 failed});

简单来说,Promise.all 强调所有 Promise 都成功,Promise.race 则关注谁先改变状态。

纯JS实现Promise,并集成all和race

下面是一个简单实现 Promise 并添加 all 和 race 方法的代码示例,解释了其基本原理和实现思路:

// 自定义Promise类
function MyPromise(executor) {this.status = 'pending';this.value = null;this.reason = null;this.onResolvedCallbacks = [];this.onRejectedCallbacks = [];const resolve = (val) => {if (this.status === 'pending') {this.status = 'fulfilled';this.value = val;this.onResolvedCallbacks.forEach(callback => callback(this.value));}};const reject = (err) => {if (this.status === 'pending') {this.status ='rejected';this.reason = err;this.onRejectedCallbacks.forEach(callback => callback(this.reason));}};try {executor(resolve, reject);} catch (error) {reject(error);}
}// Promise.prototype.then方法实现
MyPromise.prototype.then = function (onFulfilled, onRejected) {onFulfilled = typeof onFulfilled === 'function'? onFulfilled : value => value;onRejected = typeof onRejected === 'function'? onRejected : reason => { throw reason };let nextPromise;if (this.status === 'fulfilled') {nextPromise = new MyPromise((resolve, reject) => {try {const x = onFulfilled(this.value);resolvePromise(nextPromise, x, resolve, reject);} catch (error) {reject(error);}});}if (this.status ==='rejected') {nextPromise = new MyPromise((resolve, reject) => {try {const x = onRejected(this.reason);resolvePromise(nextPromise, x, resolve, reject);} catch (error) {reject(error);}});}if (this.status === 'pending') {nextPromise = new MyPromise((resolve, reject) => {this.onResolvedCallbacks.push((value) => {try {const x = onFulfilled(value);resolvePromise(nextPromise, x, resolve, reject);} catch (error) {reject(error);}});this.onRejectedCallbacks.push((reason) => {try {const x = onRejected(reason);resolvePromise(nextPromise, x, resolve, reject);} catch (error) {reject(error);}});});}return nextPromise;
};// 辅助函数,处理then方法中返回值的逻辑
function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {return reject(new TypeError('Chaining cycle detected for promise'));}if (x instanceof MyPromise) {x.then(resolve, reject);} else if (typeof x === 'object' || typeof x === 'function') {if (x === null) {return resolve(x);}let called = false;try {const then = x.then;if (typeof then === 'function') {then.call(x,(y) => {if (called) return;called = true;resolvePromise(promise2, y, resolve, reject);},(r) => {if (called) return;called = true;reject(r);});} else {resolve(x);}} catch (error) {if (called) return;called = true;reject(error);}} else {resolve(x);}
}// 实现Promise.all方法
MyPromise.all = function (promises) {return new MyPromise((resolve, reject) => {const result = [];let count = 0;if (promises.length === 0) {resolve(result);} else {promises.forEach((p, index) => {MyPromise.resolve(p).then((value) => {result[index] = value;count++;if (count === promises.length) {resolve(result);}}).catch((error) => {reject(error);});});}});
};// 实现Promise.race方法
MyPromise.race = function (promises) {return new MyPromise((resolve, reject) => {promises.forEach((p) => {MyPromise.resolve(p).then((value) => {resolve(value);}).catch((error) => {reject(error);});});});
};

使用自定义的 MyPromise

// 使用示例
const promise1 = new MyPromise((resolve) => {setTimeout(() => {resolve(1);}, 1000);
});const promise2 = new MyPromise((resolve, reject) => {setTimeout(() => {reject(new Error('Promise 2 failed'));}, 500);
});// 使用then方法
promise1.then((value) => {console.log(value);}).catch((error) => {console.error(error);});// 使用all方法
MyPromise.all([promise1, promise2]).then((values) => {console.log(values);}).catch((error) => {console.error(error);});// 使用race方法
MyPromise.race([promise1, promise2]).then((value) => {console.log(value);}).catch((error) => {console.error(error);});

在上述代码中,首先定义了一个 MyPromise 类,实现了基本的 Promise 功能,包括 then 方法。然后添加了 all 和 race 静态方法,分别用于按顺序处理多个 Promise(all)和谁先有结果就返回谁(race)。resolvePromise 函数则处理了 then 方法中返回值的复杂逻辑,确保遵循 Promise/A+ 规范。

三、闭包

闭包是面试中常见的一个考点,复习也是很有必要的,在工作中使用闭包的场景很多比如在Vue和React组件就是个大闭包,还有防抖节流等函数的封装等等。

1. 什么是闭包?

闭包是指函数和与其相关的词法环境的组合。当一个内部函数在其外部函数返回后仍然能访问外部函数的变量时,就创建了闭包。

function outer() {let count = 0;function inner() {count++;console.log(count);}return inner;
}const closureFn = outer();
closureFn(); // 1
closureFn(); // 2

在这个例子中,inner函数形成了闭包,即使 outer 函数已经执行完毕,inner 函数依然可以访问 outer 函数作用域内的 count 变量。

2. 闭包有什么作用?

  • 数据私有性: 可以隐藏变量,通过闭包,外部代码无法直接访问函数内部的变量,只能通过闭包返回的函数来操作这些变量,实现数据的封装。
  • 状态保存: 闭包能记住创建时的状态,比如在计数器的例子中,每次调用闭包函数,都能记住上次 count 的值并进行操作。
  • 柯里化: 闭包是实现函数柯里化的基础,柯里化可以将多参数函数转化为一系列单参数函数,提高函数的复用性和灵活性。柯里化后边会延伸描述

3. 闭包可能会带来什么问题?

  • 内存泄漏: 如果闭包函数一直存在,并且引用了一些大的对象或不再需要的变量,这些变量不会被垃圾回收机制回收,可能会导致内存占用过高,出现内存泄漏。例如:
    function createBigObject() {const bigArray = new Array(1000000).fill(0);return function () {// 闭包函数一直存在,bigArray无法被回收console.log('closure');};
    }const leakyClosure = createBigObject();
    
  • 变量的值不是预期的: 在循环中使用闭包时,如果不注意,可能会得到意外的结果。比如:
    const functions = [];
    for (var i = 0; i < 5; i++) {functions.push(() => {console.log(i);});
    }
    functions.forEach(fn => fn()); 
    // 输出 5 5 5 5 5,因为这里的i是最后循环结束时的值
    
    可以通过立即执行函数或使用 let 关键字来解决这个问题。如下:
    const functions = [];
    for (let i = 0; i < 5; i++) {functions.push(() => {console.log(i);});
    }
    functions.forEach(fn => fn()); 
    // 输出 0 1 2 3 4
    

4. 如何避免闭包导致的内存泄漏?

当闭包不再使用时,手动将闭包函数赋值为 null,这样相关的变量就可以被垃圾回收机制回收。例如:

function createClosure() {let data = { a: 1 };return function () {console.log(data.a);};
}let closureFn = createClosure();
// 使用闭包函数
closureFn();
// 不再使用闭包时,将其赋值为null
closureFn = null;

5. 实际开发中,不专门手动将闭包引用的变量置为null

实际开发中,闭包导致的内存泄漏本质是 “外部引用未正确释放”,而非闭包语法本身的问题。现代 GC 机制和框架已能处理大部分场景,手动置空闭包变量既不现实也无必要。开发者的核心任务是:

  • 正确管理外部依赖(移除事件监听、清除定时器、避免不合理的全局引用);
  • 依赖框架的生命周期钩子处理副作用,让闭包随上下文自然释放。

只有在极端或不规范的场景下,才需针对性地手动清理,但这也应优先通过切断外部引用来实现,而非直接操作闭包内部的变量。

四、柯里化

**柯里化(Currying)**是一种在函数式编程中广泛使用的技术,它允许你将一个多参数函数转换为一系列单参数函数。以下从定义、原理、用途、示例等方面详细介绍柯里化。

定义与原理

  • 定义: 柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。简单来说,就是将一个多参数函数拆分成多个单参数函数。
  • 原理: 利用闭包的特性,让函数记住之前传入的参数,当参数数量达到原函数所需的参数数量时,执行原函数逻辑。

用途

参数复用: 当多次调用同一个函数,并且传递的参数大部分相同时,使用柯里化可以复用这些相同的参数。
延迟计算: 可以在需要的时候再传入剩余的参数进行计算,而不是一次性传入所有参数。
动态创建函数: 根据不同的参数动态生成不同的函数。

示例

以下是一个简单的 JavaScript 示例,展示如何实现柯里化:

// 定义一个普通的加法函数
function add(a, b) {return a + b;
}// 实现柯里化函数
function curry(func) {return function curried(...args) {if (args.length >= func.length) {return func.apply(this, args);} else {return function (...nextArgs) {return curried.apply(this, args.concat(nextArgs));};}};
}// 将 add 函数进行柯里化
const curriedAdd = curry(add);// 使用柯里化函数
const add5 = curriedAdd(5);
console.log(add5(3)); // 输出 8    

代码解释

  • curry 函数: 接受一个函数 func 作为参数,返回一个新的函数 curried。
  • curried 函数: 接受任意数量的参数 args,如果 args 的长度大于或等于原函数 func 的参数长度,则直接调用 func 并返回结果;否则,返回一个新的函数,该函数会将之前的参数和新传入的参数合并后再次调用 curried 函数。
  • curriedAdd 函数: 是 add 函数柯里化后的结果。通过 curriedAdd(5) 得到一个新的函数 add5,这个函数记住了之前传入的参数 5,当调用 add5(3) 时,将 5 和 3 相加并返回结果。

实际应用场景

  • **日志记录:**在日志记录时,通常会有一些固定的参数(如日志级别、日志来源等),可以使用柯里化来复用这些参数。
    function log(level, source, message) {console.log(`[${level}] [${source}] ${message}`);
    }const curriedLog = curry(log);
    const errorLog = curriedLog('ERROR');
    const appErrorLog = errorLog('App');
    appErrorLog('Something went wrong!'); // 输出 [ERROR] [App] Something went wrong!
    
  • 事件处理: 在处理事件时,可能需要传递一些额外的参数,可以使用柯里化来动态创建事件处理函数。
    <!DOCTYPE html>
    <html lang="en"><head><meta charset="UTF-8">
    </head><body><button id="myButton">Click me</button><script>function handleClick(message, event) {console.log(`${message}: ${event.type}`);}const curriedHandleClick = curry(handleClick);const clickWithMessage = curriedHandleClick('Button clicked');const button = document.getElementById('myButton');button.addEventListener('click', clickWithMessage);</script>
    </body></html>
    
    在这个示例中,通过柯里化创建了一个带有固定消息的事件处理函数 clickWithMessage,当按钮被点击时,会输出相应的日志信息。

不推荐使用柯里化的场景

  • 简单一次性调用: 若函数仅调用一次,且参数无复用可能,直接调用普通函数更高效(如 sum(1, 2, 3) 无需柯里化)。
  • 参数顺序依赖强或易混淆: 柯里化要求严格按参数顺序传参,若参数含义不明确(如log('ERROR', 'App', '消息')中 ‘App’ 可能是来源或消息),可能导致调用时参数错位。
  • 追求极致性能的场景: 虽然现代引擎优化较好,但柯里化涉及闭包和多层函数嵌套,在极端高频调用(如循环内)时可能存在微小性能损耗(需实测验证)

总结

  • 用: 当需要参数复用、延迟计算、函数组合或动态生成定制函数,且代码风格兼容函数式思维时。
  • 慎: 当参数逻辑复杂、可读性可能受损,或团队对 FP 不熟悉时,优先考虑更直观的参数传递方式(如对象、默认参数)。

柯里化的核心价值在于将 “不变的部分” 与 “变化的部分” 分离,通过函数的 “预配置” 提高代码的灵活性和复用性。实际开发中,可从小规模场景(如工具函数、配置类函数)开始尝试,逐步判断是否符合项目需求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/82257.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么是死信队列?死信队列是如何导致的?

死信交换机&#xff08;Dead Letter Exchange&#xff0c;DLX&#xff09; 定义&#xff1a;死信交换机是一种特殊的交换机&#xff0c;专门用于**接收从其他队列中因特定原因变成死信的消息**。它的本质还是交换机&#xff0c;遵循RabbitMQ中交换机的基本工作原理&#xff0c…

9. 从《蜀道难》学CSS基础:三种选择器的实战解析

引言&#xff1a;当古诗遇上现代网页设计 今天我们通过李白的经典诗作《蜀道难》来学习CSS的三种核心选择器。这种古今结合的学习方式&#xff0c;既能感受中华诗词的魅力&#xff0c;又能掌握实用的网页设计技能。让我们开始这场穿越时空的技术之旅吧&#xff01; 一、HTML骨架…

三角网格减面算法及其代表的算法库都有哪些?

以下是三角网格减面算法及其代表库/工具的详细分类&#xff0c;涵盖经典算法和现代实现&#xff1a; ​​1. 顶点聚类&#xff08;Vertex Clustering&#xff09;​​ ​​原理​​&#xff1a;将网格空间划分为体素栅格&#xff0c;合并每个栅格内的顶点。​​特点​​&#…

URP - 屏幕图像(_CameraOpaqueTexture)

首先需要在unity中开启屏幕图像开关才可以使用该纹理 同样只有不透明对象才能被渲染到屏幕图像中 若想要该对象不被渲染到屏幕图像中&#xff0c;可以将其Shader的渲染队列改为 "Queue" "Transparent" 如何在Shader中使用_CameraOpaqueTexture&#xf…

vue 和 html 的区别

使用 Vue.js 和原生 HTML 开发 Web 应用有显著的区别&#xff0c;主要体现在开发模式、功能扩展、性能优化和维护性等方面。以下是两者的对比分析&#xff1a; &#x1f9f1; 原生 HTML&#xff08;HTML CSS JavaScript&#xff09; 特点&#xff1a; 静态结构&#xff1a;H…

LeetCode[226] 翻转二叉树

思路&#xff1a; 使用递归&#xff0c;归根结底还是左右节点互相倒&#xff0c;那么肯定需要一个temp节点在中间传递&#xff0c;最后就是递归&#xff0c;没什么说的 代码&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int …

幂等的几种解决方案以及实践

目录 什么是幂等&#xff1f; 解决幂等的常见解决方案&#xff1a; 唯一标识符案例 数据库唯一约束 案例 乐观锁案例 分布式锁&#xff08;Distributed Locking&#xff09; 实践精选方案 首先 为什么不直接使用分布式锁呢&#xff1f; 自定义实现幂等组件&#xff01…

PowerShell中的Json处理

1.定义JSON字符串变量 PS C:\WINDOWS\system32> $body {"Method": "POST","Body": {"model": "deepseek-r1","messages": [{"content": "why is the sky blue?","role"…

奥威BI:AI+BI深度融合,重塑智能AI数据分析新标杆

在数字化浪潮席卷全球的今天&#xff0c;企业正面临着前所未有的数据挑战与机遇。如何高效、精准地挖掘数据价值&#xff0c;已成为推动业务增长、提升竞争力的核心议题。奥威BI&#xff0c;作为智能AI数据分析领域的领军者&#xff0c;凭借其创新的AIBI融合模式&#xff0c;正…

【Linux网络】网络协议基础

网络基础 计算机网络背景 独立模式:计算机之间相互独立 网络互联:多台计算机连接在一起,完成数据共享 局域网LAN:计算机数量更多了,通过交换机和路由器连接在一起 广域网WAN:将远隔千里的计算机都连在一起 所谓"局域网"和"广域网"只是一个相对的概念.比…

LabVIEW表面粗糙度测量及算法解析

在制造业和科研领域&#xff0c;表面粗糙度测量对保障产品质量、推动材料研究意义重大。表面粗糙度作为衡量工件表面加工质量的关键指标&#xff0c;直接影响着工件诸如磨损、密封、疲劳等机械性能。随着技术的发展&#xff0c;LabVIEW 在表面粗糙度测量及数据处理中发挥着不可…

深入探索 JavaScript 中的模块对象

引言 在现代 JavaScript 开发中&#xff0c;模块化编程是一项至关重要的技术。它允许开发者将代码拆分成多个独立的模块&#xff0c;每个模块专注于单一功能&#xff0c;从而提高代码的可维护性、可测试性和复用性。而模块对象则是模块化编程中的核心概念之一&#xff0c;它为…

Linux——Mysql数据库

目录 一&#xff0c;数据库简介 二&#xff0c;数据库的基本概念 1&#xff0c;数据 2&#xff0c;数据库和数据库表 3&#xff0c;数据库管理系统和数据库系统 三&#xff0c;主流数据库介绍 四&#xff0c;数据库的两大类型 1&#xff0c;关系型数据库 主键 外键 2…

73页最佳实践PPT《DeepSeek自学手册-从理论模型训练到实践模型应用》

这份文档是一份关于 DeepSeek 自学手册的详细指南&#xff0c;涵盖了 DeepSeek V3 和 R1 模型的架构、训练方法、性能表现以及使用技巧等内容。它介绍了 DeepSeek V3 作为强大的 MoE 语言模型在数学、代码等任务上的出色表现以及其训练过程中的创新架构如多头潜在注意力和多 To…

LabVIEW 2019 与 NI VISA 20.0 安装及报错处理

在使用 Windows 11 操作系统的电脑上&#xff0c;同时安装了 LabVIEW 2019 32 位和 64 位版本的软件。此前安装的 NI VISA 2024 Q1 版&#xff0c;该版本与 LabVIEW 2019 32 位和 64 位不兼容&#xff0c;之后重新安装了 NI VISA 20.0。从说明书来看&#xff0c;NI VISA 20.0 …

基于Centos7的DHCP服务器搭建

一、准备实验环境&#xff1a; 克隆两台虚拟机 一台作服务器&#xff1a;DHCP Server 一台作客户端&#xff1a;DHCP Clinet 二、部署服务器 在网络模式为NAT下使用yum下载DHCP 需要管理员用户权限才能下载&#xff0c;下载好后关闭客户端&#xff0c;改NAT模式为仅主机模式…

最全盘点,赶紧收藏:2025 年全网最全的 Java 技术栈内容梳理(持续更新中)

大家好&#xff0c;我是栗筝i&#xff0c;是一个拥有 5 年经验的 Java 开发工程师和技术博主&#xff0c;曾有多年在国内某大厂工作的经历。从 2022 年 10 月份开始&#xff0c;我将持续梳理出全面的 Java 技术栈内容&#xff0c;一方面是对自己学习内容进行整合梳理&#xff0…

【项目实践】boost 搜索引擎

1. 项目展示 boost搜索引擎具体讲解视频 2. 项目背景 对于boost库&#xff0c;官方是没有提供搜索功能的&#xff0c;我们这个项目就是来为它添加一个站内搜索的功能。 3. 项目环境与技术栈 • 项目环境&#xff1a; ubuntu22.04、vscode • 技术栈&#xff1a; C/C、C11、S…

一个简单的MCP测试与debug

最近MCP挺火&#xff0c;我也跟着网上教程试试&#xff0c;参考如下&#xff0c;感谢原博主分享&#xff1a; https://zhuanlan.zhihu.com/p/1891227835722606201https://zhuanlan.zhihu.com/p/1891227835722606201 MCP是啥&#xff1f;技术原理是什么&#xff1f;一个视频搞…

深度学习系统学习系列【7】之卷积神经网络(CNN)

文章目录 说明卷积神经网络概述(Convolutional Neural Network,CNN)卷积神经网络的应用图像分类与识别图像着色自然语言处理NLP卷积神经网络的结构卷积神经网络中的数据流动 CNN与ANN的关系 卷积操作Padding 操作滑动窗口卷积操作网络卷积层操作矩阵快速卷积Im2col算法GEMM算法…