打造自己的工具盒——一文读懂JavaScript的函数特性

JavaScript 中的函数就像一个"工具盒",你可以把一段常用的代码逻辑装进去,需要的时候直接"调用"它来工作。函数让代码更整洁、可复用,是 包括JS在内的任意编程语言中非常核心的概念。下面我们逐个解析JS函数的关键知识点:

在这里插入图片描述

一、函数的定义:怎么创建一个函数?

创建函数主要有两种常用方式:函数声明函数表达式

1. 函数声明(最常用)

function 关键字开头,明确写出函数名,语法:

// 定义函数(形参:a, b 是"形式参数",只是占位符)
function 函数名(形参1, 形参2) {
  // 函数体:要执行的代码
  return 结果; // 可选,返回计算结果
}

// 例子:定义一个加法函数
function add(a, b) {
  return a + b;
}

// 调用函数(实参:1, 2 是"实际参数",真正参与计算的值)
let result = add(1, 2); 
console.log(result); // 输出 3

特点:函数声明会被"提升"(提前加载),可以在定义前调用:

// 可以在定义前调用
console.log(add(1,2)); // 3

function add(a, b) {
  return a + b;
}
2. 函数表达式

把函数赋值给一个变量,函数名可选(通常省略),语法:

// 把函数赋值给变量(函数名可省略)
let 变量名 = function(形参) {
  // 函数体
};

// 例子:用表达式定义加法函数
let add = function(a, b) {
  return a + b;
};

// 调用方式和声明式一样
console.log(add(1,2)); // 3

特点:不会被提升,必须在定义后才能调用

console.log(add(1,2)); // 报错:add is not defined(因为还没定义)

let add = function(a, b) {
  return a + b;
};

二、函数的参数:函数的"输入"

参数是函数的"原材料",分为形参(定义时写的)和实参(调用时传的)。JS 对参数的限制很灵活,比如参数数量可以不匹配。

1. 参数数量不匹配时
  • 实参多了:多余的实参会被忽略
  • 实参少了:缺少的形参会被赋值为 undefined
function add(a, b) {
  return a + b;
}

console.log(add(1)); // NaN(因为 b 是 undefined,1 + undefined = NaN)
console.log(add(1, 2, 3)); // 3(多余的 3 被忽略)
2. 默认参数(ES6 特性)

可以给形参设置默认值,当实参没传时自动使用默认值:

// 给 b 设置默认值 0
function add(a, b = 0) {
  return a + b;
}

console.log(add(1)); // 1(b 没传,用默认值 0,1+0=1)
console.log(add(1, 2)); // 3(传了实参,用实参 2)
3. 剩余参数(… 语法,ES6 特性)

当不确定要传多少个参数时,用 ...变量名 接收所有实参(返回一个数组):

// 计算任意多个数字的和
function sum(...nums) { 
  // nums 是一个数组,包含所有传入的实参
  let total = 0;
  for (let num of nums) {
    total += num;
  }
  return total;
}

console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4)); // 10
4.arguments

连剩余参数都省略了。

// 普通函数:定义时没指定参数,但可以通过 arguments 获取所有实参
function sum() {
  let total = 0;
  // arguments 包含所有传入的实参,遍历求和
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(sum(1, 2)); // 3(arguments 是 [1,2])
console.log(sum(1, 2, 3, 4)); // 10(arguments 是 [1,2,3,4])

三、匿名函数:没有名字的函数

匿名函数就是没有名字的函数,通常作为"临时工具"使用,比如赋值给变量、作为参数传递。

// 1. 赋值给变量(就是上面讲的"函数表达式")
let greet = function() {
  console.log("Hello!");
};
greet(); // 输出 Hello!

// 2. 作为参数传递(比如定时器)
setTimeout(function() {
  console.log("1秒后执行");
}, 1000); // 1秒后打印 "1秒后执行"

匿名函数的好处:用完即弃,不需要为临时逻辑单独起名字。

四、箭头函数(=>,ES6 特性)

箭头函数是匿名函数的"简化写法",用 => 代替 function,语法更简洁。

基本语法
// 普通匿名函数
let add = function(a, b) {
  return a + b;
};

// 箭头函数简化(去掉 function,加 =>)
let add = (a, b) => {
  return a + b;
};
更简洁的写法
  • 如果只有一个形参,可省略括号 ()
  • 如果函数体只有一句 return,可省略 {}return
// 1. 单个参数
let double = function(num) {
  return num * 2;
};
// 简化为
let double = num => num * 2;

// 2. 多个参数(必须保留括号)
let add = (a, b) => a + b;

// 3. 无参数(必须保留空括号)
let greet = () => console.log("Hello");
箭头函数 vs 普通函数(关键区别)
  1. this 指向不同
    普通函数的 this 指向调用它的对象;
    箭头函数的 this 继承自外层作用域(不会变)。

    // 普通函数
    let person = {
      name: "小明",
      sayHi: function() {
        console.log(this.name); // this 指向 person,输出 "小明"
      }
    };
    
    // 箭头函数(这里会有问题)
    let person2 = {
      name: "小红",
      sayHi: () => {
        console.log(this.name); // this 继承外层(全局),全局没有 name,输出 undefined
      }
    };
    

关于this指向问题,可参考这篇博文

  1. 箭头函数不能作为构造函数(不能用 new 调用)。
  2. 箭头函数没有 arguments 对象(可用剩余参数代替)。

五、函数作为参数:函数也能当"数据"传递

在 JS 中,函数和数字、字符串一样,可以作为参数传给另一个函数(这种函数叫"高阶函数")。

例子1:自定义高阶函数
// 高阶函数:接收一个函数作为参数
function calculate(a, b, operation) {
  // 调用传入的函数,传入 a 和 b
  return operation(a, b);
}

// 定义两个普通函数
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }

// 把函数作为参数传给 calculate
console.log(calculate(2, 3, add)); // 5(执行 2+3)
console.log(calculate(2, 3, multiply)); // 6(执行 2*3)
例子2:数组方法中的回调函数

数组的 mapfilter 等方法,本质就是接收一个函数作为参数:

let numbers = [1, 2, 3, 4];

// map 接收一个函数,对每个元素处理
let doubled = numbers.map(function(num) {
  return num * 2;
});
// 用箭头函数更简洁
let doubled = numbers.map(num => num * 2);

console.log(doubled); // [2, 4, 6, 8]

六、扩展阅读:编程语言中的“一等公民”

在这里,我们说JavaScript是一种支持函数作为一等公民的编程语言。
“一等公民”是计算机科学中的术语,核心指某种实体(比如函数、数字、字符串)在编程语言中拥有与其他基本数据类型完全同等的权利和操作能力,没有使用上的限制。

简单说,只要一个东西能像数字(比如 5)或字符串(比如 "hello")一样用,它就是“一等公民”。

函数作为“一等公民”的核心特征

判断函数是否是“一等公民”,主要看它是否满足以下4个关键能力,这些能力完全对标基本数据类型(如数字):

  1. 可赋值给变量
    函数能像数字一样直接赋值给变量,变量可以直接代表这个函数。
    示例(Python):

    # 定义函数
    def add(a, b):
        return a + b
    # 把函数赋值给变量func,此时func和add完全等价
    func = add
    # 通过变量调用函数,结果为3
    print(func(1, 2))
    
  2. 可作为参数传递给其他函数
    函数能像数字、字符串一样,作为参数传入另一个函数(这就是之前讨论的“高阶函数”的基础)。
    示例(JavaScript):

    // 定义一个“处理函数”,接收另一个函数作为参数
    function process(num, func) {
        return func(num); // 调用传入的函数
    }
    // 把匿名函数作为参数传入,结果为10
    console.log(process(5, x => x * 2));
    
  3. 可作为函数的返回值
    函数能像数字一样,被另一个函数“生产”并返回,形成“函数嵌套”的灵活结构。
    示例(Python):

    # 定义一个函数,返回另一个函数
    def make_adder(n):
        # 内部函数,使用外部函数的参数n
        def adder(x):
            return x + n
        return adder # 返回内部函数
    # 生成一个“加3”的函数
    add3 = make_adder(3)
    # 调用生成的函数,结果为5
    print(add3(2))
    
  4. 可存储在数据结构中
    函数能像数字、字符串一样,存入列表、字典等数据结构中,方便统一管理和调用。
    示例(JavaScript):

    // 把多个函数存入数组
    const operations = [
        x => x + 1,   // 加1
        x => x * 2,   // 乘2
        x => x - 3    // 减3
    ];
    // 遍历数组调用函数,依次输出2、2、-2
    operations.forEach(func => console.log(func(1)));
    

为什么“一等公民”很重要?

它是函数式编程的核心基础,能让代码更灵活、更简洁:

  • 支持“回调函数”:比如异步操作(如网络请求)完成后,自动调用传入的函数。
  • 支持“高阶函数”:像 map()filter() 这类通用工具函数,本质就是接收函数作为参数,实现代码复用。
  • 减少冗余代码:通过“动态生成函数”(如上述 make_adder),避免重复写相似逻辑。

对比:非一等公民的局限

有些语言早期不支持函数作为“一等公民”,比如 Java 8 之前

  • 函数不能直接赋值给变量,必须包裹在类(如匿名内部类)中。
  • 不能直接作为参数传递,需要先定义接口,再通过实现类传递逻辑。
  • 代码会变得冗长,直到 Java 8 引入 Lambda 表达式,才让函数具备了“一等公民”的核心能力。

以下是不同编程语言中“函数作为一等公民”的支持情况对比表,涵盖核心特性、支持版本及语法示例:

编程语言 支持版本/起始版本 核心语法/特性 关键能力示例(赋值/传参/返回/存储)
Python 全版本(原生支持) 函数直接赋值给变量、lambda 匿名函数 python<br># 1. 赋值给变量<br>def add(a, b): return a + b<br>func = add<br><br># 2. 作为参数传递<br>list(map(lambda x: x*2, [1,2,3])) # [2,4,6]<br><br># 3. 作为返回值<br>def make_func(n):<br> return lambda x: x + n<br>add3 = make_func(3)<br><br># 4. 存储在列表<br>funcs = [add, lambda a,b: a*b]<br>
JavaScript 全版本(原生支持) 函数表达式、() => {} 箭头函数 javascript<br>// 1. 赋值给变量<br>const add = (a, b) => a + b;<br><br>// 2. 作为参数传递<br>[1,2,3].map(x => x*2); // [2,4,6]<br><br>// 3. 作为返回值<br>const makeFunc = n => x => x + n;<br>const add3 = makeFunc(3);<br><br>// 4. 存储在数组<br>const funcs = [add, (a,b) => a*b];<br>
Java Java 8+(引入Lambda) 函数式接口(Function/Predicate)、Lambda表达式 java<br>import java.util.function.Function;<br><br>// 1. 赋值给变量(通过函数式接口)<br>Function<Integer, Integer> add = x -> x + 1;<br><br>// 2. 作为参数传递<br>int result = process(5, x -> x * 2);<br>static int process(int num, Function<Integer, Integer> func) {<br> return func.apply(num);<br>}<br>
C# C# 2.0+(委托);C# 3.0+(Lambda增强) 委托(delegate)、Lambda表达式 csharp<br>// 1. 委托类型定义<br>delegate int Op(int x);<br><br>// 2. 赋值与传参<br>Op square = x => x * x;<br>int result = Calculate(3, square);<br>static int Calculate(int num, Op op) => op(num);<br><br>// 3. 存储在列表<br>var funcs = new List<Op> { x => x+1, x => x*2 };<br>
Haskell 全版本(纯函数式语言) 函数类型(如 a -> b)、柯里化默认支持 haskell<br>// 1. 函数赋值(变量即函数)<br>add :: Int -> Int -> Int<br>add a b = a + b<br>func = add<br><br>// 2. 作为参数传递<br>applyTwice :: (a -> a) -> a -> a<br>applyTwice f x = f (f x)<br>result = applyTwice (+1) 5 -- 7<br>
C++ C++11+(Lambda与函数对象) 函数指针、Lambda表达式、std::function cpp<br>#include <functional><br><br>// 1. 存储函数(std::function)<br>std::function<int(int)> func = [](int x) { return x*2; };<br><br>// 2. 作为参数传递<br>void process(int num, std::function<int(int)> f) {<br> cout << f(num) << endl;<br>}<br>process(3, [](int x) { return x+5; }); // 8<br>
Ruby 全版本(一切皆对象) lambdaproc、方法对象(method ```ruby
// 1. 赋值给变量
add = lambda {
Kotlin 全版本(原生支持) 函数类型(如 (Int) -> Int)、Lambda表达式 kotlin<br>// 1. 函数类型变量<br>val add: (Int, Int) -> Int = { a, b -> a + b }<br><br>// 2. 作为参数传递<br>fun process(num: Int, func: (Int) -> Int) = func(num)<br>process(6) { it / 2 } // 3(it为默认参数)<br><br>// 3. 作为返回值<br>fun makeFunc(n: Int): (Int) -> Int = { it + n }<br>
Go Go 1.0+(原生支持) 函数类型直接声明、匿名函数 go<br>// 1. 函数变量<br>var add func(int, int) int<br>add = func(a, b int) int { return a + b }<br><br>// 2. 作为参数传递<br>func compute(x int, f func(int) int) int {<br> return f(x)<br>}<br>compute(10, func(n int) int { return n - 3 }) // 7<br>

关键说明:

  1. 原生支持 vs 增强支持

    • Python、JavaScript、Ruby、Haskell、Go 从设计之初就将函数视为一等公民,语法简洁直接。
    • Java、C#、C++ 是通过后期版本(如 Java 8、C++11)引入 Lambda 或函数对象机制,实现了对“函数作为一等公民”的支持。
  2. 核心差异

    • 纯函数式语言(如 Haskell)中,函数是默认的一等公民,且天然支持柯里化(函数自动返回新函数)。
    • 面向对象语言(如 Java、C#)中,函数通常需要通过“接口/委托”包装,但语法逐渐向简洁化发展(如 Lambda 表达式)。
  3. 实用性
    无论实现方式如何,支持“函数作为一等公民”的语言都能更方便地实现回调、高阶函数(如 map/filter)、策略模式等,提升代码灵活性。

总结

  • 函数是"可复用的代码块",用 function 定义,通过 函数名() 调用。
  • 参数灵活:支持默认值、剩余参数,数量可不匹配。
  • 匿名函数没有名字,适合临时使用。
  • 箭头函数是简化写法,this 指向和普通函数不同。
  • 函数可以作为参数传递,让代码更灵活(高阶函数)。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐