当前位置: 首页 > news >正文

JavaScript手录06-函数

函数是 JavaScript 中核心的编程概念,它允许我们封装可重用的代码块,实现逻辑模块化和复用。以下是 JavaScript 函数的核心知识:

画板

一、函数的基本概念

函数是一段具有特定功能的代码集合,能够接收输入(参数)、执行操作并返回输出(返回值)。

核心作用

  1. 代码复用:避免重复编写相同逻辑
  2. 逻辑抽象:隐藏实现细节,只暴露调用接口
  3. 流程控制:将复杂任务分解为多个函数,使代码结构清晰

二、函数的定义方式

JavaScript 提供了多种定义函数的方式,各有特点:

1. 函数声明(Function Declaration)
// 语法:function 函数名(参数列表) { 函数体 }
function add(a, b) {return a + b; // return 语句返回结果并终止函数
}

特点

  • 存在函数提升:可以在声明之前调用(解析器会优先读取函数声明)
  • 必须指定函数名,通过函数名直接调用
2. 函数表达式(Function Expression)
// 语法:const 变量名 = function(参数列表) { 函数体 }
const multiply = function(a, b) {return a * b;
};

特点

  • 赋值给变量,通过变量名调用
  • 不存在函数提升:必须在声明后调用
  • 可以是匿名函数(省略函数名)
3. 箭头函数(Arrow Function,ES6+)
// 完整语法
const subtract = (a, b) => {return a - b;
};// 简化语法(单表达式可省略 {} 和 return)
const divide = (a, b) => a / b;

特点

  • 语法简洁,适合短逻辑函数
  • 没有自己的 this(继承外层作用域的 this
  • 不能用作构造函数(不能用 new 调用)
  • 没有 arguments 对象
4. 构造函数方式(不推荐)

通过 Function 构造函数创建函数(可读性差,几乎不用):

const power = new Function('base', 'exponent', 'return base ** exponent');

三、函数的参数

函数参数是函数与外部交互的入口,分为形参(定义时声明)和实参(调用时传入)。

1. 基本用法
// 形参:a 和 b
function sum(a, b) {return a + b;
}// 实参:2 和 3
console.log(sum(2, 3)); // 输出:5
2. 参数特性

参数数量不匹配时

  • 实参少于形参:多余形参值为 undefined
  • 实参多于形参:多余实参可通过 arguments 或剩余参数获取

默认参数(ES6+):为形参设置默认值,当实参未传入时使用

function greet(name = 'Guest') {console.log(`Hello, ${name}`);
}
greet(); // 输出:Hello, Guest

剩余参数(ES6+):用 ... 收集多余实参为数组

function sumAll(...numbers) { // numbers 是数组return numbers.reduce((total, num) => total + num, 0);
}
console.log(sumAll(1, 2, 3)); // 输出:6

四、函数的调用方式

函数定义后需调用才能执行,不同调用方式会影响 this 指向:

1. 普通调用
function hello() {console.log('Hello');
}
hello(); // 直接调用,this 指向全局对象(浏览器中是 window)
2. 作为对象方法调用
const obj = {name: '张三',sayName() {console.log(this.name); // this 指向当前对象 obj}
};
obj.sayName(); // 输出:张三
3. 构造函数调用(new 关键字)
function Person(name) {this.name = name; // this 指向新创建的实例
}
const person = new Person('李四'); // 创建实例
4. 间接调用(改变 this 指向)

通过 call()apply()bind() 手动指定 this

function introduce() {console.log(`我是${this.name}`);
}
const user = { name: '王五' };introduce.call(user); // 输出:我是王五(this 指向 user)

五、函数的返回值

  • 通过 return 语句返回结果,return 后函数立即终止
  • 若没有 returnreturn 后无值,函数默认返回 undefined
function isEven(num) {if (num % 2 === 0) {return true; // 返回布尔值}return false; // 函数执行到此处终止
}

六、作用域

6.1 作用域(Scope)

在JavaScript中,作用域指的是**变量和函数可访问的范围**,它决定了代码中变量的可见性和生命周期。

JavaScript 的作用域是**静态作用域**(词法作用域),即变量的作用域在代码编写时就已确定,而非运行时。

1. 作用域的类型

JavaScript 中有三种主要的作用域类型:

(1)全局作用域(Global Scope)
  • 定义:在所有函数外部声明的变量,或未声明直接赋值的变量(不推荐)。
  • 特点:在整个脚本中都可访问,生命周期与页面一致(页面关闭前一直存在)。
// 全局变量
const globalVar = "我是全局变量";function test() {console.log(globalVar); // 可访问全局变量
}test(); // 输出:我是全局变量
(2)函数作用域(Function Scope)
  • 定义:在函数内部声明的变量(包括函数参数)。
  • 特点:仅在当前函数内部可访问,函数执行结束后变量会被销毁(闭包除外)。
function test() {// 函数内变量(函数作用域)const funcVar = "我是函数内变量";console.log(funcVar); // 可访问
}test();
console.log(funcVar); // 报错:funcVar 未定义(外部不可访问)
(3)块级作用域(Block Scope,ES6+)
  • 定义:在代码块({})中通过 letconst 声明的变量(如 ifforwhile 的代码块)。
  • 特点:仅在当前代码块内可访问,块执行结束后变量销毁。
if (true) {// 块级变量let blockVar = "我是块级变量";console.log(blockVar); // 可访问
}console.log(blockVar); // 报错:blockVar 未定义(块外部不可访问)

注意var 声明的变量没有块级作用域,仅受函数作用域限制。

补充1:函数(方法)、语句、表达式
类别定义示例区别
表达式等效于值的代码片段,一定有返回值。
反过来说,没有返回值的代码片段一定不是表达式。
1+2 //返回3
user.name //返回属性值
fn(1,2,3) // 返回函数调用结果
核心目的:产生值
有返回值
等效于值使用
语句执行操作的指令,控制程序流程,没有返回值或者返回值为undefined。if语句
循环语句for while
var a = 3 变量声明语句
执行操作
没有返回值或者返回值为undefined
独立存在(控制流程)
函数(方法)用于封装可复用的代码块,可以接收参数并返回结果,本质上也可以算是一种特殊的值。function add(a,b){
return a+b}
封装逻辑,可以调用
调用时可能有返回值
作为工具被调用
补充2:变量提升

变量提升是 JavaScript 解析器的一种特性:在代码执行前,解析器会将变量和函数的声明 “提升” 到其所在作用域的顶部,但赋值操作仍保留在原地。这意味着可以在声明前使用变量或函数。

  1. 变量提升的表现
  • var 声明的变量:声明会被提升,赋值留在原地(未赋值时为 undefined)。在声明前使用不会报错,且值为undefined。
console.log(a); // 输出 undefined(声明被提升,赋值未执行)
var a = 10;
console.log(a); // 输出 10
  • let/const 声明的变量:声明也会被提升,但存在 “暂时性死区”(TDZ),在声明前使用会报错。
console.log(b); // 报错:Cannot access 'b' before initialization
let b = 20;
  • 函数声明:整个函数体都会被提升(可以在声明前调用)。
foo(); // 输出 "执行foo"(函数声明被完全提升)
function foo() {console.log("执行foo");
}
  1. 变量提升的本质

JavaScript 代码执行分为解析阶段执行阶段

  • 解析阶段:扫描代码,将变量和函数声明添加到作用域中(提升)。
  • 执行阶段:按顺序执行代码,处理赋值和函数调用。

提升仅提升 “声明”,不提升 “赋值”,这是理解变量提升的关键。

补充3:函数声明与函数表达式
特征函数声明函数表达式
语法function 函数名字(参数)
{ ... }
const 变量名 = function(参数){ ... }; // 函数匿名或具名
提升行为整个函数体被提升,可以在声明前调用,不会报错。相当于将函数赋值给变量,因此仅变量声明被提升。此时如果在声明前调用会报错。
函数名通过function声明的函数必须具名,通过函数名调用。可以匿名,通过变量名调用。
语境作为独立语句存在,不能进行赋值、参数传递等操作。作为表达式的一部分存在,可以进行赋值、参数传递等操作。
2. 作用域链(Scope Chain)

当访问一个变量时,JavaScript 会按以下规则查找:

  1. 先在当前作用域中查找,找到则使用。
  2. 若未找到,向上查找父级作用域
  3. 依次向上,直到全局作用域。若仍未找到,则认为该变量未定义(报错)。

这种层级查找关系称为作用域链

// 全局作用域
const globalVar = "全局";function outer() {// 外层函数作用域const outerVar = "外层";function inner() {// 内层函数作用域const innerVar = "内层";// 查找变量:当前作用域 → 外层 → 全局console.log(innerVar); // 内层(当前作用域)console.log(outerVar); // 外层(父级作用域)console.log(globalVar); // 全局(顶级作用域)}inner();
}outer();
6.2 闭包(Closure)

闭包是 JavaScript 中最强大也最易混淆的特性之一,它与作用域紧密相关。

1. 闭包的定义

闭包指的是有权访问另一个函数作用域中变量的函数。通常是在一个函数内部定义另一个函数,内层函数引用外层函数的变量,即使外层函数执行完毕,这些变量仍会被保留。

function outer() {const outerVar = "外层变量"; // 外层函数的变量// 内层函数(闭包)function inner() {console.log(outerVar); // 引用外层函数的变量}return inner; // 返回内层函数
}// 外层函数执行后,返回内层函数
const closureFunc = outer();
closureFunc(); // 输出:外层变量(外层函数已执行,但变量仍被访问)

核心表现:外层函数执行完毕后,其作用域并未被销毁,因为内层函数(闭包)仍在引用其中的变量。

2. 闭包的形成条件
  1. 嵌套函数:存在函数内部定义的内层函数。
  2. 变量引用:内层函数引用外层函数的变量或参数。
  3. 外部访问:内层函数被返回到外层函数外部并被调用。
3. 闭包的实际应用场景
  1. 封装私有变量

JavaScript 没有原生的私有变量机制,但可通过闭包模拟:

function createCounter() {let count = 0; // 私有变量(外部无法直接访问)return {increment() { count++; },decrement() { count--; },getCount() { return count; }};
}const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 输出:2(只能通过暴露的方法访问 count)
console.log(counter.count); // 输出:undefined(直接访问私有变量失败)
  1. 延迟执行与保存状态

闭包可保存函数执行时的状态,常用于定时器、事件处理等场景:

function delayLog(message, delay) {setTimeout(function() {// 闭包引用了外层函数的 message 变量console.log(message);}, delay);
}delayLog("3秒后执行", 3000); // 3秒后输出:3秒后执行
  1. 模块化开发

通过闭包隔离不同模块的作用域,避免全局变量污染:

const module = (function() {// 模块内部私有变量const privateVar = "私有数据";// 暴露公共方法return {getPrivate() { return privateVar; },setPrivate(val) { /* 验证逻辑 */ }};
})();console.log(module.getPrivate()); // 输出:私有数据(通过公共接口访问)
4. 闭包的注意事项
  1. 内存占用问题

闭包会保留外层函数的作用域,导致变量不会被垃圾回收机制回收,过度使用可能造成内存泄漏。

解决:不再使用闭包时,手动解除引用(如赋值为 null)。

let closureFunc = outer();
closureFunc(); 
closureFunc = null; // 解除引用,释放内存
  1. 循环中的闭包陷阱

经典问题:循环中创建的闭包可能共享同一变量,导致结果不符合预期。

// 问题代码
for (var i = 0; i < 3; i++) {setTimeout(function() {console.log(i); // 输出:3 3 3(所有闭包共享同一个 i)}, 1000);
}// 解决:使用 let 块级作用域,或立即执行函数
for (let i = 0; i < 3; i++) { // let 使每次循环的 i 是独立的setTimeout(function() {console.log(i); // 输出:0 1 2}, 1000);
}
6.3 作用域与闭包的关系
  • 作用域是闭包的基础:闭包依赖于作用域链实现对外部变量的访问。
  • 闭包是作用域的延伸:即使外层作用域已结束,闭包仍能维持对其变量的引用。
  • 通俗理解:作用域规定了“变量在哪里可用”,闭包则实现了“即使作用域结束,变量仍可被访问”。
补充1 JavaScript中的垃圾回收机制(Garbage Collection)

JavaScript 具有自动垃圾回收机制,其核心目的是自动释放不再被使用的内存空间,避免内存泄漏(内存溢出)。开发者无需手动管理内存,由 JavaScript 引擎(如 V8)自动完成。

1. 垃圾回收的核心原理

垃圾回收的本质是:找出不再被引用的变量/对象,释放其占用的内存

判断一个值是否“不再被使用”的核心标准是:该值是否还存在有效的引用

  • 如果一个值没有任何变量或对象引用它,它就是“垃圾”,会被回收。
  • 如果仍有引用指向它,则会被保留在内存中。
2. 常见的垃圾回收算法

JavaScript 引擎主要使用两种算法:

(1)标记-清除算法(Mark-and-Sweep,最常用)

这是现代 JavaScript 引擎最主流的算法,分为两个阶段:

  1. 标记阶段:从全局对象(如 windowglobal)开始,遍历所有可访问的变量/对象,为它们打上“被引用”的标记。
  2. 清除阶段:遍历内存中所有值,清除没有被标记的对象(即无法访问的对象),释放其内存。

优点:能处理循环引用(如两个对象互相引用但均无外部引用的情况)。

画板

(2)引用计数算法(Reference Counting,较少使用)

通过记录每个值被引用的次数来判断是否回收:

  • 当一个值被引用时,引用计数 +1。
  • 当引用消失时,引用计数 -1。
  • 当引用计数为 0 时,释放内存。

缺点:无法解决循环引用问题(如 a.b = b; b.a = a; 会导致两者引用计数永远不为 0,内存无法释放),现代引擎已基本淘汰。

3. 垃圾回收的触发时机

垃圾回收是自动且周期性的:

  • 引擎会在内存占用达到一定阈值时自动触发。
  • 开发者无法直接调用垃圾回收,但可以通过解除引用(如赋值 <font style="background-color:#FBDE28;">null</font>)帮助引擎识别垃圾。
补充2 为什么闭包中引用的变量不会被垃圾回收机制回收?

闭包中引用的变量之所以不会被回收,核心原因是**这些变量仍存在有效的引用**,不符合垃圾回收的“无引用”标准。

1. 闭包中变量的引用链分析

当内层函数(闭包)引用外层函数的变量时,会形成一条持久的引用链:

  • 外层函数执行时,创建其作用域及内部变量。
  • 内层函数(闭包)引用外层变量,并被返回到外层函数外部。
  • 外部变量(如接收闭包的变量)引用该闭包,闭包又引用外层变量,形成“外部变量 → 闭包 → 外层变量”的引用链。

示例

function outer() {const outerVar = "我是外层变量"; // 外层变量function inner() {console.log(outerVar); // 闭包引用外层变量}return inner; // 闭包被返回到外部
}const closureFunc = outer(); // 外部变量引用闭包
closureFunc(); // 执行闭包时仍能访问 outerVar

此时,outerVarinner(闭包)引用,innerclosureFunc 引用,closureFunc 是全局变量——整个引用链完整,outerVar 仍被有效引用,因此不会被回收。

2. 何时闭包中的变量会被回收?

只有当闭包本身不再被引用时,其引用的外层变量才会失去所有引用,进而被回收:

// 接上面的例子
closureFunc = null; // 解除对闭包的引用// 此时:closureFunc → inner(闭包)的引用消失
// inner → outerVar 的引用也随之失效
// outerVar 不再被任何对象引用,会被垃圾回收

七、函数的其他特性

1. 函数是一等公民

函数可以作为值赋值给变量、作为参数传递、作为返回值返回:

// 函数作为参数
function execute(func, a, b) {return func(a, b);
}
execute(add, 2, 3); // 输出:5(传递 add 函数)
2. 匿名函数

没有名称的函数,常用于临时场景(如回调函数):

// 匿名函数作为定时器回调
setTimeout(function() {console.log('1秒后执行');
}, 1000);
3. 立即执行函数(IIFE)

定义后立即执行,用于创建独立作用域:

(function() {const privateVar = '私有变量'; // 外部无法访问console.log(privateVar);
})();

拓展、闭包的实际应用场景

闭包在实际开发中应用非常广泛,其核心价值在于保留作用域和私有变量,以下列举一些典型的实际应用场景:

模块化与私有变量封装

JavaScript 没有原生私有变量机制,闭包可以模拟“私有成员”,实现变量的封装和保护,避免全局污染。

// 计数器模块(闭包实现私有变量)
const createCounter = () => {let count = 0; // 私有变量(外部无法直接访问)return {increment: () => count++,  // 闭包:访问私有变量decrement: () => count--,getCount: () => count};
};// 使用模块
const counter1 = createCounter();
counter1.increment();
counter1.increment();
console.log(counter1.getCount()); // 2
console.log(counter1.count); // undefined(无法直接访问私有变量)// 多个实例互不干扰(各自的闭包保存独立状态)
const counter2 = createCounter();
counter2.increment();
console.log(counter2.getCount()); // 1

核心价值:通过闭包隔离不同实例的状态,仅暴露有限接口,保证数据安全性。

防抖与节流(性能优化)

在处理高频事件(如滚动、输入、窗口resize)时,闭包可保存定时器状态,避免函数频繁执行。

防抖(Debounce)

触发事件后延迟n秒执行,如果n秒内再次触发则重新计时(适用于搜索输入、表单提交)。

const debounce = (fn, delay = 300) => {let timer = null; // 闭包保存定时器IDreturn (...args) => {clearTimeout(timer); // 每次触发清除之前的定时器timer = setTimeout(() => {fn.apply(this, args); // 延迟执行原函数}, delay);};
};// 应用:搜索输入联想
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce((e) => {console.log('搜索:', e.target.value); // 输入停止300ms后执行
}, 500));
节流(Throttle)

每隔n秒最多执行一次函数(适用于滚动加载、拖拽)。

const throttle = (fn, interval = 1000) => {let lastTime = 0; // 闭包保存上次执行时间return (...args) => {const now = Date.now();if (now - lastTime >= interval) { // 间隔达标才执行fn.apply(this, args);lastTime = now;}};
};// 应用:滚动监听
window.addEventListener('scroll', throttle(() => {console.log('滚动位置:', window.scrollY); // 每1秒最多执行一次
}, 1000));
函数柯里化(参数复用)

将多参数函数转化为单参数函数的链式调用,通过闭包保存已传入的参数。

// 柯里化函数:拼接API地址
const curryApi = (baseUrl) => {return (path) => {return (params) => {return `${baseUrl}${path}?${new URLSearchParams(params)}`;};};
};// 复用基础地址
const api = curryApi('https://api.example.com');
const userApi = api('/user'); // 闭包保存 baseUrl// 后续调用只需传入变化的参数
console.log(userApi({ id: 1 })); 
// 输出: https://api.example.com/user?id=1
console.log(userApi({ name: 'test' })); 
// 输出: https://api.example.com/user?name=test

核心价值:固定部分参数,简化重复调用,提高代码复用性。

延迟执行与状态保存

闭包可以“记住”函数创建时的环境状态,常用于定时器、事件回调等场景。

// 延迟打印消息(保存当前消息状态)
const delayLog = (message, delay) => {setTimeout(() => {console.log(message); // 闭包引用外部 message}, delay);
};delayLog('3秒后执行', 3000); // 3秒后输出:3秒后执行

核心价值:即使外部函数已执行完毕,闭包仍能保留对原始变量的引用。

React Hooks 底层原理

React 的 useStateuseEffect 等 Hooks 本质上依赖闭包保存组件状态。

// 简易模拟 useState(闭包保存状态)
let state; // 闭包变量保存状态
const useState = (initialValue) => {state = state ?? initialValue; // 首次初始化const setState = (newValue) => {state = newValue;// 模拟重新渲染render();};return [state, setState];
};// 组件中使用
const render = () => {const [count, setCount] = useState(0);console.log('当前计数:', count);
};render(); // 输出: 当前计数: 0
setCount(1); // 触发重新渲染,输出: 当前计数: 1

核心价值:在无类组件的情况下,通过闭包跨渲染周期保存状态。

练习-防抖

练习1:基础防抖实现(点击按钮)

目标:实现一个按钮点击防抖,多次点击后只执行最后一次(延迟1秒)。

<button id="btn">点击我(防抖)</button>
<script>// 1. 定义要执行的函数function handleClick() {console.log('按钮点击生效');}// 2. 实现防抖函数function debounce(fn, delay) {// 补充代码:实现防抖逻辑}// 3. 绑定事件(使用防抖)const btn = document.getElementById('btn');const debouncedClick = debounce(handleClick, 1000);btn.addEventListener('click', debouncedClick);
</script>

提示

  • 用闭包保存定时器ID
  • 每次触发时清除之前的定时器,重新计时
练习2:输入框实时搜索防抖

目标:输入框输入时,停止输入1秒后才执行搜索(避免输入过程中频繁触发)。

<input type="text" id="searchInput" placeholder="输入搜索内容">
<script>// 1. 搜索函数function search(keyword) {console.log('搜索:', keyword);}// 2. 防抖函数(可复用练习1的实现)function debounce(fn, delay) {// 复用练习1的代码}// 3. 绑定输入事件const input = document.getElementById('searchInput');// 补充代码:使用防抖处理input事件
</script>

提示

  • 输入事件的回调需要获取输入框的值(e.target.value
  • 防抖函数中通过 apply 传递事件对象
练习3:带立即执行选项的防抖

目标:扩展防抖函数,增加 immediate 参数(默认 false),当为 true 时,第一次触发立即执行,后续触发才防抖。

<button id="btn1">普通防抖(延迟执行)</button>
<button id="btn2">立即执行+防抖</button>
<script>function log(message) {console.log(message);}// 扩展防抖函数:增加immediate参数function debounce(fn, delay, immediate) {// 补充代码:实现带立即执行的防抖}// 测试const btn1 = document.getElementById('btn1');const btn2 = document.getElementById('btn2');btn1.addEventListener('click', debounce(() => log('延迟执行'), 1000));btn2.addEventListener('click', debounce(() => log('立即执行'), 1000, true));
</script>

提示

  • 用一个变量判断是否是第一次触发
  • 立即执行时,先执行函数再设置定时器(期间触发会清除定时器)
练习4:防抖取消功能

目标:给防抖函数增加取消功能,允许手动取消延迟执行。

<button id="submit">提交(防抖3秒)</button>
<button id="cancel">取消提交</button>
<script>function submit() {console.log('提交成功');}function debounce(fn, delay) {let timer;// 防抖处理函数const debounced = function(...args) {// 补充基础防抖逻辑};// 增加取消方法debounced.cancel = function() {// 补充代码:清除定时器,取消执行};return debounced;}// 测试const submitBtn = document.getElementById('submit');const cancelBtn = document.getElementById('cancel');const debouncedSubmit = debounce(submit, 3000);submitBtn.addEventListener('click', debouncedSubmit);cancelBtn.addEventListener('click', () => {debouncedSubmit.cancel();console.log('已取消提交');});
</script>

提示

  • 防抖函数返回的对象中增加 cancel 方法
  • cancel 方法内部清除定时器(clearTimeout(timer)
练习5:实战场景 - 窗口 resize 防抖

目标:监听窗口大小变化,当停止调整1秒后,才执行尺寸计算(避免调整过程中频繁触发)。

<script>// 计算窗口尺寸的函数function handleResize() {console.log('窗口尺寸:', window.innerWidth, 'x', window.innerHeight);}// 复用防抖函数function debounce(fn, delay) {// 复用之前的实现}// 绑定resize事件(带防抖)window.addEventListener('resize', debounce(handleResize, 1000));
</script>

提示

  • 窗口 resize 事件触发频率极高,防抖是必做优化
  • 无需额外操作,直接将防抖后的函数绑定到事件即可

参考答案-防抖

练习1:基础防抖实现(点击按钮)

关键思路

  • 用闭包变量 timer 保存定时器ID,确保每次触发都能访问同一个定时器
  • 每次点击先清除之前的定时器,再设置新的,实现“多次点击重新计时”
<!DOCTYPE html>
<html>
<body><button id="btn">点击我(防抖)</button><script>// 1. 定义要执行的函数function handleClick() {console.log('按钮点击生效');}// 2. 实现防抖函数function debounce(fn, delay) {let timer = null; // 闭包保存定时器IDreturn function(...args) {// 每次触发时清除之前的定时器(重新计时)clearTimeout(timer);// 设置新的定时器,延迟执行原函数timer = setTimeout(() => {// 确保this指向正确,参数正常传递fn.apply(this, args);}, delay);};}// 3. 绑定事件(使用防抖)const btn = document.getElementById('btn');const debouncedClick = debounce(handleClick, 1000); // 延迟1秒btn.addEventListener('click', debouncedClick);</script>
</body>
</html>
练习2:输入框实时搜索防抖

关键思路

  • 输入事件的回调通过 e.target.value 获取输入内容
  • 防抖延迟设为500ms(比按钮场景短,提升用户体验)
<!DOCTYPE html>
<html>
<body><input type="text" id="searchInput" placeholder="输入搜索内容"><script>// 1. 搜索函数function search(keyword) {console.log('搜索:', keyword);}// 2. 防抖函数(复用练习1的实现)function debounce(fn, delay) {let timer = null;return function(...args) {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};}// 3. 绑定输入事件const input = document.getElementById('searchInput');// 防抖处理输入事件,延迟500msinput.addEventListener('input', debounce(function(e) {search(e.target.value); // 传递输入框的值}, 500));</script>
</body>
</html>
练习3:带立即执行选项的防抖

关键思路

  • 增加 immediate 参数控制执行时机,默认 false
  • 立即执行模式下,第一次触发直接执行函数,后续触发仅重置定时器
  • 延迟结束后重置状态,确保下次触发能正常执行
<!DOCTYPE html>
<html>
<body><button id="btn1">普通防抖(延迟执行)</button><button id="btn2">立即执行+防抖</button><script>function log(message) {console.log(message);}// 扩展防抖函数:增加immediate参数function debounce(fn, delay, immediate = false) {let timer = null;let isExecuted = false; // 标记是否已立即执行return function(...args) {// 清除之前的定时器clearTimeout(timer);// 立即执行模式:第一次触发时立即执行if (immediate && !isExecuted) {fn.apply(this, args);isExecuted = true; // 标记为已执行}// 设置定时器:延迟后重置状态(或执行延迟逻辑)timer = setTimeout(() => {if (!immediate) {// 非立即模式:延迟后执行fn.apply(this, args);}isExecuted = false; // 重置状态,允许下次触发}, delay);};}// 测试const btn1 = document.getElementById('btn1');const btn2 = document.getElementById('btn2');btn1.addEventListener('click', debounce(() => log('延迟执行'), 1000));btn2.addEventListener('click', debounce(() => log('立即执行'), 1000, true));</script>
</body>
</html>
练习4:防抖取消功能

关键思路

  • 在防抖函数返回的对象上增加 cancel 方法
  • cancel 内部通过 clearTimeout 清除定时器,阻止原函数执行
<!DOCTYPE html>
<html>
<body><button id="submit">提交(防抖3秒)</button><button id="cancel">取消提交</button><script>function submit() {console.log('提交成功');}function debounce(fn, delay) {let timer;// 防抖处理函数const debounced = function(...args) {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};// 增加取消方法debounced.cancel = function() {clearTimeout(timer); // 清除定时器,取消执行timer = null;};return debounced;}// 测试const submitBtn = document.getElementById('submit');const cancelBtn = document.getElementById('cancel');const debouncedSubmit = debounce(submit, 3000); // 延迟3秒提交submitBtn.addEventListener('click', debouncedSubmit);cancelBtn.addEventListener('click', () => {debouncedSubmit.cancel();console.log('已取消提交');});</script>
</body>
</html>
练习5:窗口 resize 防抖

关键思路

  • 窗口 resize 事件触发频率极高(拖动窗口边缘时每秒触发数十次)
  • 用防抖限制为“停止调整1秒后执行一次”,大幅减少函数执行次数
<!DOCTYPE html>
<html>
<body><script>// 计算窗口尺寸的函数function handleResize() {console.log('窗口尺寸:', window.innerWidth, 'x', window.innerHeight);}// 复用防抖函数function debounce(fn, delay) {let timer = null;return function(...args) {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};}// 绑定resize事件(带防抖)window.addEventListener('resize', debounce(handleResize, 1000));</script>
</body>
</html>
http://www.dtcms.com/a/300354.html

相关文章:

  • Linux——线程同步
  • KubeKey安装KubeSphere、部署应用实践问题总结
  • 立式加工中心X-Y轴传动机械结构设“cad【6张】三维图+设计说明书
  • 计算机中的单位(详细易懂)
  • 计算机结构-逻辑门、存储器、内存、加法器、锁存器、程序计数器
  • 斐波那契数列加强版 快速矩阵幂
  • 53. 最大子数组和
  • 组合问题(回溯算法)
  • Windows Server容器化应用的资源限制设置
  • 图书管理系统:一个功能完善的图书馆管理解决方案
  • 【C++篇】STL的关联容器:map和set(下篇):用一颗红黑树同时封装出map和set
  • CCFRec-人大高瓴-KDD2025-序列推荐中充分融合协同信息与语义信息
  • Item13:以对象管理资源
  • 人工智能论文辅导:Prompt Engineering(特征工程)
  • 倍思鹿数值仿真-实现各类提示、快捷键功能,提高工作效率
  • Android Jetpack 组件库 ->Jetpack Navigation (下)
  • 通过不同坐标系下的同一向量,求解旋转矩阵
  • 深度学习入门(2)
  • 实验-OSPF多区域
  • 告别Vite脚手架局限!MixOne Beta测试招募:你的需求,我们来实现
  • 【Java】基础概念-构造函数详解
  • [Python] -进阶理解7- Python中的内存管理机制简析
  • 基于springboot的在线数码商城/在线电子产品商品销售系统的设计与实现
  • (二)使用 LangChain 从零开始构建 RAG 系统 RAG From Scratch
  • 7月26号打卡
  • Unity GenericMenu 类详解
  • 技术 — 资本双螺旋:AI 时代的投资浪潮与技术突破
  • 模型训练部署流程
  • 电磁兼容三:电磁干扰三要素详解
  • 【大模型框架】LangChain入门:从核心组件到构建高级RAG与Agent应用