函数是JavaScript编程的基石,它扮演着组织代码、实现复用和构建抽象的核心角色。在JavaScript中,函数不仅是执行特定任务的代码块,更是一种特殊的对象,这赋予了它极大的灵活性。 你可以将函数存储在变量中、作为参数传递给其他函数,甚至可以作为另一个函数的返回值。这种“一等公民”的特性,使得JavaScript能够支持强大的函数式编程范式。

在JavaScript中,函数的定义方式主要有函数声明和函数表达式两种。函数声明是通过function关键字直接定义一个具名函数,而函数表达式则是将一个函数(可以是匿名的,也可以具名)赋值给变量。两者在语法结构上有所不同,更重要的是它们在JavaScript引擎中的处理方式也存在差异。函数声明会在代码执行前被整体提升(Hoisting),这意味着你可以在函数声明之前就调用该函数。而函数表达式则不会被提升,只有在表达式执行之后才能使用对应的函数。这种差异在实际编程中会影响函数的调用时机和代码的组织方式,因此理解它们的本质区别非常重要。
函数声明是JavaScript中最常见的定义函数的方式。你可以通过function关键字来创建一个具名函数,
语法格式为:function 函数名(参数1, 参数2, ...) { /* 函数体 */ }。
在这种方式下,整个函数会在代码解析阶段被提前加载到作用域中,这个过程称为“提升”(Hoisting)。
因此,即使你在函数声明之前调用该函数,JavaScript也能够正确识别并执行它。
这种特性有助于你在代码结构上更加灵活地组织函数调用和定义的位置,无需担心函数声明的先后顺序。
|// 函数声明 function calculateArea(width, height) { return width * height; } const area = calculateArea(10, 5); console.log("面积是:", area);
|面积是: 50
函数表达式是指将一个函数赋值给变量,这个函数可以是匿名的,也可以有名字。与函数声明相比,函数表达式的最大区别在于它不会在代码解析阶段被提前加载,也就是说, 只有当 JavaScript 执行到这一行代码时,函数才会被创建。因此,你只能在函数表达式定义之后才能调用它,否则会出现错误。这种特性使得函数表达式在需要根据条件动态创建函数,或者将函数作为参数传递时非常有用。
|const getGreeting = function(name) { return "你好, " + name; }; const greeting = getGreeting("世界"); console.log(greeting);
|你好, 世界
在JavaScript中,你可以在一个函数的内部再定义另一个函数,这种做法被称为嵌套函数。 嵌套函数的最大特点是它能够直接访问其外部函数作用域中的所有变量和参数,包括外部函数的形参和在外部函数内部声明的局部变量。 这种作用域的嵌套关系,使得嵌套函数不仅可以完成内部逻辑的封装,还能与外部函数的数据进行交互。 嵌套函数通常用于将某些辅助性的操作封装在主函数内部,避免对外暴露,实现更好的模块化和私有化。
|function processName(name) { // 嵌套函数,用于获取首字母 function getInitial() { return name.charAt(0); } console.log(`名字 "${name}" 的首字母是: ${getInitial()}`); } processName("张三");
|名字 "张三" 的首字母是: Z
函数的定义只是为后续的执行做准备,只有在实际调用时,函数内部的代码才会被运行。这个“调用”过程不仅仅是让函数执行一段逻辑,
更重要的是它决定了函数内部this关键字的具体指向。在JavaScript中,this的含义会随着调用方式的不同而发生变化:
如果直接调用一个普通函数,this通常指向全局对象(在浏览器环境下是window,在严格模式下则为undefined);
如果函数作为某个对象的方法被调用,this就会指向那个对象本身;而当使用new关键字以构造函数的方式调用时,
this则会指向新创建的实例对象。正因为如此,理解函数的调用方式对于掌握JavaScript的面向对象编程和高级用法至关重要。
这是最直接的调用方式。当函数不作为任何对象的属性被调用时,它就是普通函数调用。在非严格模式下,this会指向全局对象(在浏览器中是window);在严格模式("use strict";)下,this则为undefined。
|function showGlobalMessage() { // 在浏览器非严格模式下,this 指向 window 对象 this.message = "全局消息"; console.log(this.message); } showGlobalMessage();
|全局消息
当你把一个函数赋值为对象的属性,然后通过“对象.方法()”的方式来调用它时,这个函数就被称为该对象的方法。在这种调用场景下,JavaScript 会自动将 this 绑定到调用方法的那个对象本身。也就是说,方法内部通过 this 访问到的属性和方法,实际上都是属于这个对象的。这种机制让我们能够在方法内部灵活地操作和引用对象自身的数据,是实现面向对象编程的核心所在。比如你可以在方法里读取或修改对象的属性,或者调用同一个对象上的其他方法,这一切都依赖于 this 的正确指向。
|const user = { name: "小明", sayHi: function() { console.log("你好, 我是" + this.name); } }; user.sayHi();
|你好, 我是小明
当你在函数前加上new关键字进行调用时,这个函数就会作为构造函数来执行。此时,JavaScript会自动创建一个全新的空对象,并把这个新对象绑定到函数内部的this上。函数体内可以通过this为新对象添加属性和方法。执行完构造函数后,这个新对象会被自动返回。通过这种方式,你可以用同一个构造函数批量创建出结构和行为类似的多个对象实例,每个实例都拥有独立的属性和方法。
|function Circle(radius) { this.radius = radius; this.getArea = function() { return Math.PI * this.radius * this.radius; }; } const circle1 = new Circle(10); console.log("半径为10的圆面积:", circle1.getArea
|半径为10的圆面积: 314.16
JavaScript函数作为对象,自身也拥有方法。其中call()和apply()方法允许我们间接调用函数,并且可以显式地指定函数执行时的this值。这为函数的动态调用和上下文切换提供了极大的灵活性。call()方法接受一个参数列表,而apply()方法接受一个参数数组。
|const person = { name: "李华" }; function introduce(city, country) { console.log(`我是${this.name},我来自${country}的${city}。`); } // 使用 call introduce.call(person, "上海", "中国"); // 使用 apply introduce.apply
|我是李华,我来自中国的上海。 我是李华,我来自中国的北京。
在JavaScript中,函数参数的处理方式极为灵活。无论你在调用函数时传递了多少个参数,或者这些参数是什么类型,JavaScript都不会进行强制检查。即使你传入的参数数量与函数定义时的参数个数不一致,JavaScript也不会报错。多余的参数会被忽略,缺失的参数则会被自动赋值为undefined。这种宽松的机制让开发者可以根据实际需求灵活地传递参数,但同时也带来了潜在的风险,比如参数类型不符合预期或者参数数量不足。因此,在编写函数时,通常需要在函数内部对参数进行合理的判断和处理,以确保代码的健壮性和可维护性。
当你调用函数时,如果传入的参数少于函数定义的参数数量,那么多余的参数值会自动设为undefined。在ES6及更高版本中,我们可以直接为参数设置默认值,使代码更加简洁。
|function registerUser(username, role = "访客") { console.log(`用户: ${username}, 角色: ${role}`); } registerUser("Admin", "管理员"); registerUser("Guest");
|用户: Admin, 角色: 管理员 用户: Guest, 角色: 访客
有时我们需要编写能处理任意数量参数的函数。在函数内部,可以通过arguments对象来访问所有传入的参数。arguments是一个类数组对象,包含了函数调用时传递的所有参数。
|function sumAll() { let total = 0; for (let i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; } const result = sumAll(1, 2, 3,
|总和是: 15
在JavaScript中,函数被视为“一等公民”(First-Class Citizens),这意味着它们可以像其他任何值(如数字、字符串、对象)一样被使用。你可以将函数赋值给变量,存储在数组或对象中,作为参数传递给其他函数,甚至可以作为函数的返回值。
|// 1. 赋值给变量 const add = function(a, b) { return a + b; }; // 2. 存储在对象中 const mathUtils = { add: add, subtract: function(a, b) { return a - b; } }; // 3. 作为参数传递 (回调函数) function calculate(a, b
|加法结果: 15 减法结果: 5
在JavaScript中,作用域(Scope)指的是变量和函数在代码中的可见范围。每当你声明一个变量时,它只能在特定的作用域内被访问,这样可以防止变量名冲突并保护数据不被外部随意修改。而闭包(Closure)则是指当一个函数能够“记住”并访问其外部函数作用域中的变量时,即使外部函数已经执行结束,这种现象就叫做闭包。正是因为有了作用域和闭包,JavaScript程序员可以灵活地管理变量的生命周期,实现数据的封装和私有化,从而编写出更加稳定和安全的代码。

在一个函数内部声明的变量,在函数外部是无法访问的,这为我们创建“私有”变量提供了可能。通过立即调用函数表达式(IIFE),我们可以创建一个独立的作用域来封装代码,避免污染全局命名空间。
|const counterModule = (function() { // 这个变量是私有的,外部无法访问 let count = 0; // 返回一个对象,暴露公共接口 return { increment: function() { count++; }, getValue: function() { return count; } }; })(); counterModule.increment();
|计数器的值: 2
闭包是JavaScript中非常重要且强大的一个特性。它的本质在于:当一个函数在其外部函数作用域之外被调用时,依然能够访问并操作该外部函数中的变量。也就是说,即使外部函数已经执行结束并从调用栈中移除,内部嵌套的函数依然“记住”了它创建时所处的作用域环境。这种现象使得我们可以在函数外部间接访问到函数内部的变量,实现数据的封装和持久化,从而编写出更灵活、健壮的代码。
|function createGreeter(greeting) { // 返回的这个函数就是一个闭包,它“记住”了greeting return function(name) { console.log(`${greeting}, ${name}!`); }; } const sayHello = createGreeter("你好"); const sayWelcome = createGreeter("欢迎"); sayHello("张三"
|你好, 张三! 欢迎, 李四!
ES6为JavaScript带来了箭头函数(Arrow Functions)这一新特性。箭头函数的语法比传统函数表达式更加简洁,省略了function关键字,并且在只有一个参数时可以省略圆括号,只有一条语句时还可以省略花括号和return。除了语法上的简化,箭头函数还有一个重要特性:它不会创建自己的this,而是继承自外层作用域的this。这意味着在箭头函数内部访问this时,得到的始终是定义该函数时所在上下文的this,而不是调用时的对象。这一特性在处理回调函数或定时器等场景时,能够避免传统函数中常见的this指向问题,使代码更加直观和易于维护。
|const numbers = [1, 2, 3, 4, 5]; // 使用传统函数 const traditionalEvens = numbers.filter(function(n) { return n % 2 === 0; }); // 使用箭头函数 const arrowEvens = numbers.filter(n
|传统函数筛选的偶数: [ 2, 4 ] 箭头函数筛选的偶数: [ 2, 4 ]
现在让我们通过一些练习题来巩固本章学到的知识。每道题都涵盖了JavaScript函数的核心概念,请先尝试独立完成,然后再查看答案。
this指向什么?|function show() { console.log(this.name); } const obj = { name: "对象" }; // 1. 作为方法调用 obj.show = show; obj._______(); // 2. 间接调用 show._______(obj);
|function show() { console.log(this.name); } const obj = { name: "对象" }; // 1. 作为方法调用 obj.show = show; obj.show(); // 输出:"对象" // 2. 间接调用 show.call(obj); // 输出:"对象"
函数调用方式影响this的指向:
this指向调用该方法的对象call()或apply()间接调用:可以显式指定this的值|const values = [10, 20, 30]; // 使用map方法,将数组中每个值乘以2 const doubled = values.map(_______ => _______ * 2); console.log(doubled);
|const values = [10, 20, 30]; // 使用map方法,将数组中每个值乘以2 const doubled = values.map(value => value * 2); console.log(doubled); // [20, 40, 60]
箭头函数的语法:参数 => 表达式。当只有一个参数时,可以省略圆括号。箭头函数会自动返回表达式的值。
createMultiplier闭包函数:|function createMultiplier(factor) { // 返回一个新的函数 return function(number) { return number * _______; }; } const double = createMultiplier(2); console.log(double(5)); // 应输出 10
|function createMultiplier(factor) { // 返回一个新的函数 return function(number) { return number * factor; // factor来自外部作用域 }; } const double = createMultiplier(2); console.log(double(5)); // 输出:10 const triple = createMultiplier(3
buildSentence("你好", "欢迎来到", "JavaScript的世界") 输出:"你好 欢迎来到 JavaScript的世界"|// 使用剩余参数语法 function buildSentence(...words) { return words.join(" "); } // 或者使用arguments对象 function buildSentence2() { return Array.from(arguments).join(" "); } // 测试 console.log(buildSentence("你好", "欢迎来到",
Cache模块,使用闭包来存储私有缓存数据。要求提供add、get、has方法,外部代码不能直接访问缓存对象。|const Cache = (function() { // 私有缓存对象,外部无法直接访问 const cache = {}; return { add: function(key, value) { cache[key] = value; }, get: function(key) { return cache[key]; }, has: function
withLogging,它接受一个函数作为参数,并返回一个新函数。新函数在执行原始函数之前打印"开始执行...",在执行之后打印"执行完毕"。|function withLogging(fn) { return function(...args) { console.log("开始执行..."); const result = fn(...args); console.log("结果是:", result); console.log("执行完毕"); return result; }; } // 使用示例
这是一个典型的闭包例子。返回的函数"记住"了外部函数的factor参数,即使createMultiplier已经执行完毕,返回的函数仍然可以访问factor。
剩余参数语法...words会将所有传入的参数收集到一个数组中,比使用arguments对象更现代、更灵活。
使用立即执行函数表达式(IIFE)创建闭包,将cache对象封装在私有作用域中,只通过返回的公共接口访问。这是模块模式的典型应用。
高阶函数是指接受函数作为参数或返回函数的函数。withLogging是一个装饰器函数,它增强了原函数的功能而不修改原函数本身。这是函数式编程中常见的模式。