手写发布订阅(Event Emitter):实现 `on`、`emit`、`off` 和 `once` 方法

手写发布订阅模式:从零实现 Event Emitter 的完整指南 大家好,欢迎来到今天的讲座。今天我们不聊框架、不谈算法,而是深入到 JavaScript 底层机制中,一起手写一个经典的 Event Emitter(事件发射器) 实现。它虽然看似简单,但却是 Node.js 核心模块、前端状态管理库(如 Redux、Vuex)、以及各类异步通信系统的基础。 你可能在项目中用过 eventEmitter.on(‘click’, handler) 这样的代码,但你知道它是怎么工作的吗?我们今天的目标就是——亲手实现一套完整的 on、emit、off 和 once 方法,让你真正理解“发布-订阅”模式的本质。 一、什么是发布订阅模式? 发布订阅是一种行为设计模式,允许对象之间解耦地通信。它有两个角色: 角色 职责 发布者(Publisher) 发送事件(emit),通知所有监听者 订阅者(Subscriber) 监听特定事件(on),执行回调函数 这种模式的好处是: 解耦:发布者不需要知道谁在监听 灵活扩展:可以动态添加或移除监听器 支持多对多通信:一个事件可被多个监听者处理 Node.j …

手写 `Array.prototype.map`:如何支持 callback 中的 `this` 绑定?

手写 Array.prototype.map:深入理解 this 绑定机制与实现细节 大家好,欢迎来到今天的编程技术讲座。今天我们不聊框架、不谈架构,而是聚焦于一个看似基础却极具深度的 JavaScript 内置方法——Array.prototype.map。你可能每天都在用它,但你真的了解它是如何工作的吗?特别是当我们传递一个回调函数时,这个回调中的 this 到底指向哪里?为什么有时候会变成 undefined?我们今天就来手写一个完整的 map 方法,并重点讲解其中的 this 绑定逻辑。 一、为什么要手写 map? 在现代前端开发中,我们习惯于直接使用原生数组方法如 map、filter、forEach 等。它们简洁、高效、语义清晰。然而,理解这些方法背后的实现原理,不仅能帮助我们在面试中脱颖而出,更能让我们在遇到复杂场景(比如跨环境执行、异步操作、自定义上下文)时游刃有余。 更重要的是:手写不是为了替代原生方法,而是为了掌握其本质。 让我们从最简单的例子开始: const numbers = [1, 2, 3]; const doubled = numbers.map(x = …

手写数组扁平化 `flat`:给定深度(Depth),如何用递归或 Reduce 实现?

手写数组扁平化 flat:深入理解递归与 Reduce 的实现原理 在 JavaScript 中,数组的扁平化(Flattening)是一个非常常见的操作。当我们处理嵌套结构的数据时,比如从 API 接口获取的多层 JSON 数据,或者用户输入的复杂对象,常常需要将它们转换为一维数组以便后续处理。ES2019 引入了原生方法 Array.prototype.flat(),但它并不适用于所有场景——特别是当你的运行环境不支持该特性时,或者你需要自定义行为(如控制深度、过滤元素等),手动实现一个高效的扁平化函数就显得尤为重要。 本文将以“讲座”形式,带你从基础概念出发,逐步剖析如何使用递归和 Reduce 两种方式来手写一个可配置深度的 flat 函数,并通过性能对比、边界情况分析、实际应用场景等多个维度,让你不仅学会怎么做,更明白为什么这么做。 一、什么是数组扁平化? 定义 数组扁平化是指将一个多维数组(嵌套数组)展开成一维数组的过程。例如: const nested = [1, [2, 3], [4, [5, 6]], 7]; // 扁平化后应为: [1, 2, 3, 4, 5, 6, …

手写防抖(Debounce)与节流(Throttle):实现支持立即执行(Leading)的版本

手写防抖(Debounce)与节流(Throttle):实现支持立即执行(Leading)的版本 大家好,欢迎来到今天的编程技术讲座。我是你们的讲师,今天我们要深入探讨两个在前端开发中极其重要的性能优化技巧:防抖(Debounce) 和 节流(Throttle)。它们常用于处理高频触发事件(如输入框搜索、窗口滚动、按钮点击等),避免不必要的重复计算或网络请求。 但今天我们不只是讲基础用法,而是要手写一个支持立即执行(leading) 的完整版本——这正是很多开发者在实际项目中容易忽略的关键点。 一、什么是防抖和节流? ✅ 防抖(Debounce) 定义:在一段时间内,如果某个函数被多次调用,只会在最后一次调用之后延迟执行一次。 适用场景:用户输入搜索关键词时,防止每打一个字就发一次请求;或者表单验证频繁触发。 ✅ 节流(Throttle) 定义:规定一个时间段内最多执行一次函数,不管在这段时间里调用了多少次。 适用场景:页面滚动监听、鼠标移动事件、resize事件处理。 📝 小贴士:两者本质都是通过控制函数执行频率来提升性能,但策略不同: 特性 防抖(Debounce) 节流(Thro …

手写 `call`、`apply`、`bind`:不用原生方法,如何改变 `this` 上下文?

手写 call、apply、bind:彻底理解 JavaScript 中的 this 上下文控制机制 大家好,欢迎来到今天的讲座。今天我们要深入探讨一个看似简单但极其重要的 JavaScript 主题——如何手动实现 call、apply 和 bind 方法。这不是为了让你在面试中背诵代码,而是帮助你真正理解 JavaScript 的 this 机制是如何工作的,以及我们为什么需要这些方法来“改变”函数执行时的上下文。 一、为什么要学习手写 call / apply / bind? ✅ 1. 理解 this 的本质 在 JavaScript 中,this 不是静态绑定的,它的值取决于函数被调用的方式(调用位置)。 这常常让开发者困惑:“为什么我写了 obj.func(),this 却不是 obj?” 而 call、apply、bind 正是用来显式指定 this 的工具。 ✅ 2. 深入掌握原型链与对象属性访问机制 手写这三个方法的过程,其实就是在模拟 JS 引擎内部如何处理函数调用和 this 绑定逻辑。你会接触到: 对象属性查找(原型链) 函数作为对象的特性(可添加属性) argu …

手写深拷贝(Deep Clone):如何优雅处理 RegExp、Date 和循环引用(Circular Ref)?

手写深拷贝(Deep Clone):优雅处理 RegExp、Date 和循环引用(Circular Ref) 大家好,欢迎来到今天的编程技术讲座。今天我们要深入探讨一个看似简单但实则非常复杂的主题——手写深拷贝(Deep Clone)。 你可能已经用过 JSON.parse(JSON.stringify(obj)) 来做深拷贝,但它有明显的局限性:无法处理 Date、RegExp、函数、undefined、Symbol 等类型,更别说循环引用了。而我们今天的目标是写出一个真正健壮、优雅且能处理边界情况的深拷贝函数。 一、为什么需要深拷贝? 在 JavaScript 中,对象和数组都是引用类型。如果你直接赋值: const obj1 = { a: 1, b: [2, 3] }; const obj2 = obj1; obj2.b.push(4); console.log(obj1.b); // [2, 3, 4] 你会发现 obj1 也被改变了。这就是浅拷贝的问题。 深拷贝的核心目标是:创建一个新的对象或数组,其内部结构完全独立于原对象,修改新对象不会影响原对象。 二、常见深拷贝方法对比 …

手写 `Object.create`:如何创建一个没有原型(null prototype)的对象?

手写 Object.create:如何创建一个没有原型(null prototype)的对象? 各位开发者朋友,大家好!今天我们来深入探讨一个看似简单却极具深度的话题——如何手写 Object.create 方法,尤其是创建一个没有原型(即 prototype 为 null)的对象。 这不仅是一个面试常问的问题,更是理解 JavaScript 原型链机制、对象构造原理和语言设计哲学的关键一步。如果你只是知道 Object.create(null) 能创建无原型对象,但不清楚背后发生了什么,那今天的讲解将帮你彻底打通这个知识点。 一、什么是 Object.create?它的作用是什么? 在 JavaScript 中,Object.create(proto, propertiesObject) 是一个内置方法,用于基于指定的原型对象创建一个新的对象。它的语法如下: const newObj = Object.create(proto, descriptors); proto:新对象的原型(即 newObj.__proto__ 的值) descriptors:可选参数,用于定义新对象的属性( …

手写 `new` 操作符:模拟创建一个实例对象的完整四个步骤

手写 new 操作符:深入理解 JavaScript 中对象创建的底层机制 大家好,欢迎来到今天的编程技术讲座。我是你们的技术讲师,今天我们要探讨一个看似简单但极其重要的主题——手写 new 操作符。在 JavaScript 中,new 是我们最常用的构造函数调用方式之一,比如: const person = new Person(‘Alice’, 25); 然而,很多人并不清楚这个操作背后到底发生了什么。其实,new 并不是魔法,它是一系列明确步骤的组合。今天我们就来一步步拆解这些步骤,并通过手写一个模拟版本来加深理解。 一、什么是 new?它的作用是什么? 在 JavaScript 中,new 是一种用于调用构造函数并创建实例对象的关键字。当我们使用 new 调用一个函数时,会发生以下四件事情(这是标准行为): 步骤 描述 1️⃣ 创建新对象 创建一个空对象 {} 2️⃣ 设置原型链 将新对象的内部属性 [[Prototype]] 指向构造函数的 prototype 属性 3️⃣ 绑定 this 将构造函数中的 this 指向新创建的对象 4️⃣ 自动返回对象 如果构造函数没有显式 …

手写 `instanceof`:如何通过遍历原型链判断构造函数

手写 instanceof:如何通过遍历原型链判断构造函数 大家好,欢迎来到今天的讲座。今天我们来深入探讨一个看似简单但非常重要的 JavaScript 概念——instanceof 运算符的底层实现原理。 你可能每天都在用 instanceof,比如: const obj = new Person(); console.log(obj instanceof Person); // true 但你有没有想过:它是怎么知道 obj 是不是 Person 的实例? 它背后是不是有一个“查找过程”?这个过程是否可以被我们手动模拟? 今天我们就从零开始,手写一个类似 instanceof 的功能,理解它的本质,并掌握原型链在其中扮演的关键角色。 一、什么是 instanceof? 首先明确一下定义: instanceof 是 JavaScript 中用于检测一个对象是否属于某个构造函数创建的实例的运算符。 语法如下: left instanceof right left 是要检测的对象; right 是构造函数(或类); 返回布尔值:如果 left 是 right 的实例,则返回 true,否 …

手写 `Promise.all`:如果其中一个 reject 了,如何处理剩余的请求?

手写 Promise.all:当其中一个 reject 时,如何优雅处理剩余请求? 大家好,欢迎来到今天的专题讲座。今天我们不讲“Hello World”,也不讲“闭包”或“原型链”。我们来聊聊一个看似简单、实则暗藏玄机的 JavaScript 高阶特性 —— Promise.all。 你可能已经用过 Promise.all([…]),比如并发发起多个 API 请求,等它们都成功后再统一处理结果。但如果你只停留在“它能跑起来”的层面,那今天的内容将彻底改变你的认知。 一、什么是 Promise.all?它的默认行为是什么? 先从基础开始。Promise.all 是 ES6 引入的一个静态方法,用于并行执行一组 Promise,并在所有 Promise 成功完成时返回一个包含所有结果的数组;如果其中任意一个 Promise 失败(reject),整个 Promise.all 就会立即失败(reject),不会等待其他 Promise 完成。 const p1 = Promise.resolve(1); const p2 = Promise.resolve(2); const p3 = …