首页 > web前端 > js教程 > 正文

JS函数如何定义和调用

小老鼠
发布: 2025-08-25 09:32:01
原创
1005人浏览过
JavaScript中定义函数有函数声明、函数表达式和箭头函数三种主要方式,分别具有提升特性、按需赋值和词法this绑定的特点;函数通过函数名加括号调用。参数传递支持位置参数、默认参数、剩余参数和解构参数,提升灵活性。this指向由调用方式决定,常见于全局调用、对象方法、构造函数和事件处理中,可通过箭头函数、bind、call、apply或保存this变量来控制。闭包指函数访问并记住外层作用域变量的能力,常用于数据私有化、维持状态、函数工厂和柯里化,增强代码封装性和复用性,但需注意内存管理。

js函数如何定义和调用

JavaScript中定义函数,说白了,就是告诉程序你有一段可重复使用的代码逻辑,给它起个名字。调用函数则是让这段代码跑起来。最常见的定义方式有函数声明、函数表达式和箭头函数,而调用则直接通过函数名加上括号来执行。

解决方案

定义JS函数,我们有几种主流的方式,每种都有其独特的适用场景和行为特性,理解这些差异是写出健壮JS代码的基础。

函数声明 (Function Declaration) 这是最传统、也是最直观的一种方式。它看起来就像这样:

function greet(name) {
  console.log(`你好,${name}!`);
}
登录后复制

它的一个显著特点是“变量提升”(hoisting)。这意味着你可以在函数声明之前就调用它,代码依然能正常运行。对我来说,这在编写一些工具函数或者需要提前定义的辅助函数时很方便,不用太纠结声明的顺序。

函数表达式 (Function Expression) 这种方式是将一个函数赋值给一个变量。函数可以是匿名的,也可以是具名的(虽然具名在实际中不常用,除非是为了在函数内部递归调用自身或者调试方便)。

const sayHello = function(name) {
  console.log(`Hello, ${name}!`);
};

// 也可以是具名函数表达式,但外部调用仍需通过变量名
const factorial = function calculateFactorial(n) {
  if (n <= 1) return 1;
  return n * calculateFactorial(n - 1);
};
登录后复制

函数表达式不会被提升,你必须在定义之后才能调用它。我个人觉得,这种方式更符合我对“变量”的理解——先声明再使用。它在回调函数、立即执行函数表达式(IIFE)中非常常见。

箭头函数 (Arrow Function) 这是ES6引入的一种更简洁的函数定义方式,尤其适合短小的回调函数。

const add = (a, b) => a + b;
const logMessage = message => console.log(message); // 单个参数可以省略括号
const doSomething = () => console.log("执行了!"); // 没有参数需要空括号
登录后复制

箭头函数最大的特点是它的语法简洁,以及它没有自己的

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
arguments
登录后复制
登录后复制
登录后复制
super
登录后复制
new.target
登录后复制
。它的
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
是词法作用域的,直接继承自外层作用域的
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。这一点,说实话,解决了JS中
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
指向的很多“坑”,让异步编程和事件处理变得更舒服。我个人在写React组件或者其他需要保持
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
上下文一致性的地方,几乎首选箭头函数。

函数调用 无论你用哪种方式定义了函数,调用它都非常直接:使用函数名,后面跟着一对括号

()
登录后复制
。如果函数需要参数,就在括号里传入。

greet("张三"); // 输出:你好,张三!
sayHello("李四"); // 输出:Hello, 李四!
console.log(add(5, 3)); // 输出:8
doSomething(); // 输出:执行了!
登录后复制

函数调用时,括号是必须的,即使函数不需要任何参数。

JS函数定义时有哪些常见的参数传递方式?

函数参数的传递,在JavaScript里其实挺灵活的,它不仅仅是简单地把值扔进去,还有一些更高级的玩法,能让你的函数签名更具表现力。

1. 位置参数 (Positional Parameters) 这是最基本的,也是我们最常用的方式。你按照函数定义时参数的顺序,依次传入对应的值。

function calculateArea(width, height) {
  return width * height;
}
let area = calculateArea(10, 5); // 10 对应 width, 5 对应 height
console.log(area); // 输出:50
登录后复制

这种方式简单直接,但如果参数很多或者参数的意义不明确,很容易混淆。

2. 默认参数 (Default Parameters) ES6引入的特性,允许你在函数定义时为参数设置默认值。如果调用函数时没有为该参数提供值,或者传入了

undefined
登录后复制
登录后复制
,那么就会使用默认值。这在我看来,极大地提升了函数的健壮性和易用性,避免了在函数体内部写大量的
if (param === undefined) param = defaultValue;
登录后复制
这样的逻辑。

function sendMessage(message, sender = "匿名用户") {
  console.log(`${sender} 说: ${message}`);
}
sendMessage("你好!"); // 输出:匿名用户 说: 你好!
sendMessage("你好!", "小明"); // 输出:小明 说: 你好!
sendMessage("这是个 undefined 的例子", undefined); // 依然使用默认值
登录后复制

3. 剩余参数 (Rest Parameters) 同样是ES6的特性,使用

...
登录后复制
语法,它允许你将不定数量的参数收集到一个数组中。这对于处理参数数量不确定的函数非常有用,比如一个求和函数,你可能想传入任意数量的数字。

function sum(...numbers) {
  // numbers 现在是一个数组,包含了所有传入的参数
  return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 输出:6
console.log(sum(10, 20, 30, 40)); // 输出:100
登录后复制

这比ES5时代使用

arguments
登录后复制
登录后复制
登录后复制
对象要清晰和灵活得多,因为
arguments
登录后复制
登录后复制
登录后复制
不是真正的数组,而且它包含了所有参数,包括那些明确定义的参数。

4. 解构参数 (Destructuring Parameters) 当你传入一个对象作为参数,并且想直接使用对象中的某些属性作为函数的局部变量时,解构赋值就能派上用场了。这让函数签名更清晰,一眼就能看出函数需要哪些属性。

function displayUser({ name, age, city = "未知" }) {
  // name, age, city 直接从传入的对象中解构出来
  console.log(`姓名: ${name}, 年龄: ${age}, 城市: ${city}`);
}

const user1 = { name: "张三", age: 30 };
displayUser(user1); // 输出:姓名: 张三, 年龄: 30, 城市: 未知

const user2 = { name: "李四", age: 25, city: "北京" };
displayUser(user2); // 输出:姓名: 李四, 年龄: 25, 城市: 北京
登录后复制

我个人觉得,对于配置对象或者参数较多的函数,解构参数配合默认值,能让函数接口变得非常友好和易读。

JS函数内部的
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
指向问题该如何理解和处理?

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
在JavaScript里,绝对是个让人又爱又恨的“磨人精”。它的指向不是在函数定义时确定的,而是在函数被调用时根据调用方式动态决定的。这跟很多其他语言的
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
(或等价物)行为大相径庭,也是初学者最容易掉坑的地方。

简单来说,

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
通常指向调用该函数的对象。但这个“对象”具体是谁,就得看上下文了。

1. 全局上下文中的

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
在非严格模式下,如果函数是在全局作用域中直接调用的,或者函数内部的
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
没有明确绑定,那么
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
会指向全局对象(浏览器里是
window
登录后复制
,Node.js里是
global
登录后复制
)。严格模式下,它会是
undefined
登录后复制
登录后复制

function showThis() {
  console.log(this);
}
showThis(); // 浏览器中通常是 Window 对象,严格模式下是 undefined
登录后复制

2. 作为对象方法调用时的

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
当函数作为一个对象的方法被调用时,
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
指向这个对象本身。这是最常见的场景,也是我们最期望的
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
行为。

const person = {
  name: "Alice",
  greet: function() {
    console.log(`你好,我是 ${this.name}`);
  }
};
person.greet(); // this 指向 person 对象
登录后复制

3. 构造函数中的

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
当函数作为构造函数(配合
new
登录后复制
关键字)被调用时,
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
指向新创建的实例对象。

function Car(make, model) {
  this.make = make;
  this.model = model;
}
const myCar = new Car("Honda", "Civic");
console.log(myCar.make); // this 指向 myCar
登录后复制

4. 事件处理函数中的

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
在DOM事件处理中,
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
通常指向触发事件的那个DOM元素。

document.getElementById('myButton').addEventListener('click', function() {
  console.log(this.id); // this 指向 myButton 元素
});
登录后复制

5. 箭头函数中的

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
这是个“异类”,也是我个人最喜欢它的地方。箭头函数没有自己的
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
绑定。它会捕获其所在(定义时)的上下文的
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
值,并始终保持这个
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。这解决了传统函数在回调中
this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
丢失的问题。

const user = {
  name: "Bob",
  tasks: ["吃饭", "睡觉", "打豆豆"],
  logTasks: function() {
    this.tasks.forEach(function(task) {
      // 这里的 this 默认指向全局对象(或 undefined),不是 user
      // console.log(`${this.name} 的任务: ${task}`); // 会出错
    });

    this.tasks.forEach((task) => {
      // 箭头函数中的 this 继承自外层的 logTasks 方法,即 user 对象
      console.log(`${this.name} 的任务: ${task}`);
    });
  }
};
user.logTasks();
登录后复制

如何处理

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
指向问题?

  • 使用箭头函数: 这是最现代、最简洁的解决方案,尤其适用于回调函数和异步操作。
  • 使用
    bind()
    登录后复制
    登录后复制
    方法:
    bind()
    登录后复制
    登录后复制
    会创建一个新函数,这个新函数的
    this
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    永远被绑定到你传入的第一个参数。
    const unboundGreet = person.greet;
    const boundGreet = unboundGreet.bind(person);
    boundGreet(); // 依然能正确输出
    登录后复制
  • 使用
    call()
    登录后复制
    登录后复制
    apply()
    登录后复制
    登录后复制
    方法:
    这两个方法允许你立即调用函数,并显式指定
    this
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的值。
    call()
    登录后复制
    登录后复制
    接受单独的参数,
    apply()
    登录后复制
    登录后复制
    接受一个参数数组。它们常用于借用方法或在特定上下文执行函数。
    const anotherPerson = { name: "Charlie" };
    person.greet.call(anotherPerson); // this 指向 anotherPerson
    登录后复制
  • 保存
    this
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    到变量:
    在ES6之前,常见的做法是在外层作用域中将
    this
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    赋值给一个变量(如
    const self = this;
    登录后复制
    const that = this;
    登录后复制
    ),然后在内部函数中使用这个变量。现在有了箭头函数,这种做法已经不那么常见了。

理解

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的规则,并灵活运用箭头函数、
bind
登录后复制
等工具,是掌握JavaScript异步和面向对象编程的关键一步。

JS函数何时会用到闭包(Closure)以及它有什么用?

闭包,在我看来,是JavaScript里一个非常强大也略显“魔幻”的概念。它不像

this
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
那样容易让人困惑,但它能做到的事情,却能让你的代码变得极其灵活和富有表现力。

什么是闭包? 简单来说,当一个函数能够记住并访问其外部作用域(词法环境)的变量时,即使这个外部函数已经执行完毕,这个内部函数和它“记住”的那些变量的组合,就形成了一个闭包。

想象一下,一个函数就像一个“背包客”,它在出发(被定义)的时候,会把周围环境里的一些“必需品”(变量)装进自己的背包里。即使它走得很远,离开了原来的地方,它依然能从背包里拿出那些东西来用。

一个经典的闭包例子:计数器

function createCounter() {
  let count = 0; // 这个 count 是 createCounter 的局部变量

  return function() { // 返回的这个匿名函数就是闭包
    count++;
    console.log(count);
  };
}

const counter1 = createCounter(); // counter1 是一个闭包
counter1(); // 输出:1
counter1(); // 输出:2

const counter2 = createCounter(); // counter2 是另一个独立的闭包
counter2(); // 输出:1
counter2(); // 输出:2
登录后复制

在这个例子里,

createCounter
登录后复制
函数执行完毕后,它的局部变量
count
登录后复制
登录后复制
登录后复制
并没有被销毁,因为返回的那个匿名函数依然引用着它。每次调用
counter1()
登录后复制
,它都能访问并修改属于它自己的那个
count
登录后复制
登录后复制
登录后复制
变量。
counter2
登录后复制
也同样,但它有自己独立的
count
登录后复制
登录后复制
登录后复制

闭包有什么用?

1. 数据私有化/封装 (Data Privacy/Encapsulation) 这是闭包最常见的用途之一。通过闭包,你可以创建私有变量和方法,外部无法直接访问,只能通过暴露出来的特权方法来操作。这在实现模块模式、单例模式时非常有用,可以有效防止全局命名空间污染,并保护内部状态。

function createPerson(name) {
  let _age = 0; // 私有变量

  return {
    getName: function() {
      return name;
    },
    getAge: function() {
      return _age;
    },
    celebrateBirthday: function() {
      _age++;
      console.log(`${name} 现在 ${_age} 岁了!`);
    }
  };
}

const john = createPerson("John");
console.log(john.getName()); // John
console.log(john.getAge()); // 0
john.celebrateBirthday(); // John 现在 1 岁了!
// console.log(john._age); // 无法直接访问 _age
登录后复制

2. 维持状态 (Maintaining State) 在异步操作或事件处理中,闭包可以帮助你记住函数执行时的上下文或特定状态。比如在循环中创建事件监听器,如果不用闭包,很容易出现变量捕获问题。

// 传统 for 循环中的常见陷阱(不使用 let 或闭包)
// const buttons = document.querySelectorAll('button');
// for (var i = 0; i < buttons.length; i++) {
//   buttons[i].onclick = function() {
//     console.log('点击了按钮 ' + i); // 永远输出 "点击了按钮 3" (如果3个按钮)
//   };
// }

// 使用闭包解决
// for (var i = 0; i < buttons.length; i++) {
//   (function(index) { // 立即执行函数创建闭包
//     buttons[index].onclick = function() {
//       console.log('点击了按钮 ' + index); // 正确输出 0, 1, 2
//     };
//   })(i);
// }

// 现代JS更推荐使用 let 或 forEach
// for (let i = 0; i < buttons.length; i++) {
//   buttons[i].onclick = function() {
//     console.log('点击了按钮 ' + i); // let 自身就创建了块级作用域,行为类似闭包
//   };
// }
登录后复制

3. 函数工厂 (Function Factories) 闭包可以用来创建一系列行为相似但参数不同的函数。

function makeMultiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

const multiplyBy5 = makeMultiplier(5);
const multiplyBy10 = makeMultiplier(10);

console.log(multiplyBy5(2)); // 输出:10
console.log(multiplyBy10(2)); // 输出:20
登录后复制

multiplyBy5
登录后复制
multiplyBy10
登录后复制
都是闭包,它们各自记住了自己被创建时
factor
登录后复制
的值。

4. 柯里化 (Currying) 柯里化是函数式编程中的一个概念,它将一个接受多个参数的函数转换成一系列只接受一个参数的函数。闭包是实现柯里化的核心。

function add(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

const add10 = add(10);
const add10and20 = add10(20);
console.log(add10and20(30)); // 输出:60
登录后复制

闭包虽然强大,但也要注意潜在的问题,比如如果闭包引用了大量外部变量,可能会导致内存占用增加,甚至引发内存泄漏(尤其是在DOM操作中,如果闭包引用了不再需要的DOM元素)。所以,在使用闭包时,理解其机制并适当地管理内存是很重要的。

以上就是JS函数如何定义和调用的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 //m.sbmmt.com/ All Rights Reserved | php.cn | 湘ICP备2023035733号