函数基础
把重复的代码装进"盒子",随时调用
🎯 学习目标
学完本章,你将能够:
- 能够定义和调用函数,正确使用参数和返回值
- 能够区分函数声明、函数表达式和箭头函数,并根据场景选择合适写法
- 能够使用默认参数和剩余参数(...args)处理可变参数
- 能够解释作用域链的概念,理解变量查找规则
- 能够使用闭包实现数据封装和高阶函数(如函数工厂)
📌 有编程基础?函数声明、参数、返回值你都会。但本章末尾的闭包是 JS 的核心特性——Java 没有闭包、C 的函数指针也不一样。如果你没接触过函数式语言,建议从 5.7 闭包部分开始精读,并衔接第 8 章的彻底解析。其余可跳过。
你有没有写过
同样的代码复制粘贴了五六遍,改一个地方就要改五六处?
比如,你写了一段计算折扣价的逻辑。购物车里每件商品都要用它,订单汇总也要用它,会员折扣还要用它。
如果你把这段逻辑复制了 10 次,有一天规则改了——你要改 10 个地方。漏掉一个就是 Bug。
函数就是解决这个问题的工具。你把逻辑写一次,给它起个名字,以后随时调用。
什么是函数
定义 → 白话 → 代码
定义:函数是一段可以被命名、接收参数、返回结果的代码块。
白话:函数就像一个自动售货机。你投币(输入参数),按按钮(调用函数),它吐出饮料(返回结果)。你不需要知道里面的机械怎么转,你只需要知道投什么币、出什么饮料。
这个比喻只是为了帮你建立直观感受。在计算机内部,函数实际上是一段被编译器记录了入口地址的指令序列,调用时 CPU 跳转到该地址执行,执行完再跳回来。
// 最简单的函数
function sayHi() {
console.log("你好!");
}
sayHi(); // 调用:输出 "你好!"
声明与调用
先"造机器",再"按按钮"
声明函数用 function 关键字,调用时写函数名加括号。
// 声明函数
function greet(name) {
console.log(`你好, ${name}!`);
}
// 调用函数
greet("张三"); // "你好, 张三!"
greet("李四"); // "你好, 李四!"
函数提升:用 function 声明的函数会被"提升"到作用域顶部。也就是说,你可以先调用,再声明。但表达式声明的函数不会提升,后面会讲到。
参数与返回值
输入原料,输出产品
参数是函数的输入,返回值是函数的输出。用 return 关键字把结果交还给调用者。
function add(a, b) {
return a + b; // 返回 a + b 的结果
}
let result = add(3, 5);
console.log(result); // 8
没有 return 的函数默认返回 undefined。return 一执行,函数立刻结束,后面的代码不会运行。
默认与剩余参数
让参数更灵活、更智能
默认参数:调用时不传值,就使用默认值。
function greet(name = "世界") {
console.log(`你好, ${name}!`);
}
greet(); // "你好, 世界!"
greet("张三"); // "你好, 张三!"
剩余参数:把多出来的参数收集成一个数组。必须放在最后。
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
回调函数
把函数当作参数传给别人
JavaScript 中,函数是一等公民——你可以把函数当作变量一样传来传去。当一个函数被当作参数传给另一个函数时,它就叫回调函数(Callback)。
// setTimeout 就是使用回调的经典例子
setTimeout(function() {
console.log("1秒后执行");
}, 1000);
// 用箭头函数更简洁
setTimeout(() => console.log("1秒后执行"), 1000);
// 自定义:让函数接收一个回调
function processArray(arr, callback) {
for (let item of arr) {
callback(item);
}
}
processArray([1, 2, 3], (n) => console.log(n * 2));
// 输出: 2, 4, 6
回调函数是 JavaScript 异步编程的基础。后面学习 Promise 和 async/await 时,你会频繁遇到它。现在只需要理解"函数可以当参数传"就行。
检查点练习
练习一
任务:写一个函数 multiply,接收两个参数,返回它们的乘积。如果只传一个参数,第二个参数默认为 1。
// 你的代码:
参考答案
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(3, 4)); // 12
console.log(multiply(5)); // 5
Part 2: 函数的多种写法
JavaScript 不止一种写函数的方式,掌握常见的几种看别人的代码才不会懵
你学会了用 function 声明函数,但打开别人的代码,怎么全是 => 和 const xxx = function()?别慌,它们做的事情一样,只是写法不同。这一节带你认识函数表达式、箭头函数和立即执行函数,搞清楚每种写法的细微差别——尤其是箭头函数里 this 的行为,这是面试高频题。
函数表达式
把函数存进变量,灵活但不会提升
// 函数表达式:把匿名函数赋给变量
const greet = function(name) {
return `你好, ${name}!`;
};
console.log(greet("张三")); // "你好, 张三!"
ReferenceError。而 function 声明的函数可以。
修复:养成先声明再调用的习惯,不管哪种写法都适用。
箭头函数
ES6 最受欢迎的语法糖
箭头函数用 => 代替 function,写法更短。
// 一个参数,一行返回
const double = x => x * 2;
console.log(double(5)); // 10
// 多个参数
const add = (a, b) => a + b;
// 多行函数体需要花括号 + return
const area = (w, h) => {
const result = w * h;
return result;
};
// 返回对象字面量要加括号
const user = (name, age) => ({ name, age });
箭头 vs 普通
this 指向不同,是最关键的区别
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
this | 取决于调用方式 | 继承外层 this |
arguments | 有 | 没有 |
| 构造函数 | 可以用 new | 不能用 new |
| 提升 | 声明会提升 | 不会提升 |
const obj = {
name: "张三",
sayHi: function() {
console.log(`我是${this.name}`);
},
sayHiArrow: () => {
console.log(`我是${this?.name}`);
}
};
obj.sayHi(); // "我是张三"
obj.sayHiArrow(); // "我是undefined"
立即执行函数
写完就跑,不留痕迹
IIFE(Immediately Invoked Function Expression):定义后立即调用的函数。常用于创建独立作用域,避免污染全局变量。
(function() {
let secret = "这段代码立刻执行";
console.log(secret);
})();
// 带返回值的 IIFE
const counter = (function() {
let count = 0;
return {
next: () => ++count,
reset: () => { count = 0; }
};
})();
console.log(counter.next()); // 1
console.log(counter.next()); // 2
Part 3: 作用域与闭包
变量在哪里能被访问?函数能不能"记住"它出生时的环境?
函数会写了,但有一个经典面试题你可能答不上来:循环里用 var 声明的变量,为什么循环结束后的值和预期不一样?答案藏在"作用域"里。这一节从作用域的三层结构讲起,一路讲到闭包——函数能"记住"自己被创建时的环境变量。搞懂它,你的 JS 功力就上了一个台阶。
作用域三兄弟
全局、函数、块级——变量能活多大范围
作用域决定了变量在哪些位置可以被访问。
let global = "全局"; // 哪里都能访问
function outer() {
let outerVar = "外层";
function inner() {
let innerVar = "内层";
console.log(global); // 可以
console.log(outerVar); // 可以
}
inner();
// console.log(innerVar); // ReferenceError!
}
outer();
let 和 const 有块级作用域({} 内有效),var 只有函数作用域。现在写代码基本用 let/const,忘掉 var。
作用域链
变量查找的"向上追溯"机制
当你在函数中访问一个变量时,JS 引擎先在当前作用域找,找不到就去外层找,一直找到全局。这条查找路径就叫作用域链。

图:作用域链的变量查找过程
【AI 生图提示词】请生成一张中文技术示意图,风格简洁、配色柔和、适合教学: - 画面主体:展示 inner 函数如何通过作用域链向上查找变量的过程 - 图示结构(从下到上的嵌套层次): 1. 最内层椭圆"inner 函数",标有两条查找路径的箭头 2. 箭头1:从 inner 指向外层的"outer 作用域"矩形,标注"查找 name",outer 作用域中显示"name = 外层",用绿色勾标注"找到" 3. 箭头2:从 inner 指向外层的"全局作用域"矩形,标注"查找 global",全局作用域中显示"global = 全局",用绿色勾标注"找到" - 关键要点:用箭头和层次关系展示"内层可以访问外层变量"的机制 - 配色建议:浅灰背景,inner 用浅蓝色,outer 用浅紫色,全局用浅绿色,查找箭头用橙色 - 画面比例:16:9 - 避免:过度装饰、3D 效果、文字过小 - 标注文字用中文,变量名用等宽字体
let global = "全局";
function outer() {
let name = "外层";
function inner() {
let age = 20;
console.log(name); // "外层"——从外层找到的
console.log(global); // "全局"——从全局找到的
}
inner();
// console.log(age); // ReferenceError! 外层访问不到内层
}
outer();
作用域链是单向的:内层可以访问外层,外层不能访问内层。就像你能看到窗外的风景,但窗外的人看不到你屋内。
闭包初体验
函数"记住"了它出生时的环境
闭包:函数能够记住并访问它定义时的作用域,即使在外部执行。
用人话说:就像你带着家乡的口音去了外地。虽然人已经不在家乡了,但口音(变量)还在。
这个比喻只是为了帮你建立直观感受。在计算机内部,闭包实际上是函数对象额外保存了一个指向其词法环境的引用,引擎通过这个引用找到外层变量。
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
// count 变量"活着",不会被回收
闭包实战用法
工厂函数、私有变量——闭包的经典场景
// 工厂函数:批量生成"定制版"函数
function createMultiplier(factor) {
return (number) => number * factor;
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// 私有变量:外部无法直接访问 age
function createPerson(name) {
let age = 0; // 外部访问不到
return {
getName: () => name,
getAge: () => age,
setAge: (n) => { if (n > 0) age = n; }
};
}
const p = createPerson("张三");
p.setAge(25);
console.log(p.getAge()); // 25
高阶函数
接收函数或返回函数的函数
高阶函数是指接收函数作为参数,或者返回一个函数的函数。你其实已经在用它们了——createCounter 返回函数,processArray 接收函数。
// 接收函数作为参数
function operate(a, b, fn) {
return fn(a, b);
}
console.log(operate(3, 5, (x, y) => x + y)); // 8
console.log(operate(3, 5, (x, y) => x * y)); // 15
// 返回函数——前面学的闭包就是典型的高阶函数
function createValidator(min, max) {
return (value) => value >= min && value <= max;
}
const isAge = createValidator(0, 150);
console.log(isAge(25)); // true
console.log(isAge(-1)); // false
// 数组的高阶方法(后面章节会详细学)
[1, 2, 3].map(n => n * 2); // [2, 4, 6]
[1, 2, 3].filter(n => n > 1); // [2, 3]
高阶函数是函数式编程的核心概念。掌握它,你会发现代码可以写得更灵活、更简洁。
检查点练习
练习二
任务:用闭包写一个 createGreeter 函数。它接收一个问候语(如"你好"),返回一个新函数。新函数接收名字,输出"你好, 张三!"这样的结果。
// 你的代码:
参考答案
function createGreeter(greeting) {
return (name) => `${greeting}, ${name}!`;
}
const sayHello = createGreeter("你好");
console.log(sayHello("张三")); // "你好, 张三!"
综合练习
练习三:配置生成器
任务:用闭包写一个 createConfig 函数。它接收一个默认配置对象,返回一个新函数。新函数接收用户配置,合并后返回最终配置(用户配置覆盖默认值)。
const getConfig = createConfig({ theme: "light", lang: "zh" });
console.log(getConfig({ theme: "dark" }));
// { theme: "dark", lang: "zh" }
console.log(getConfig({ lang: "en", debug: true }));
// { theme: "light", lang: "en", debug: true }
提示:用展开运算符 { ...a, ...b } 合并对象,b 的属性会覆盖 a 的同名属性。
参考答案
function createConfig(defaults) {
return (userConfig) => ({ ...defaults, ...userConfig });
}
const getConfig = createConfig({ theme: "light", lang: "zh" });
console.log(getConfig({ theme: "dark" }));
// { theme: "dark", lang: "zh" }
练习四:函数管道
任务:写一个 pipe 函数,接收多个函数作为参数,返回一个新函数。新函数会依次调用所有传入的函数,前一个的输出作为后一个的输入。
const transform = pipe(
(s) => s.trim(),
(s) => s.toLowerCase(),
(s) => s.replace(/\s+/g, "-")
);
console.log(transform(" Hello World "));
// "hello-world"
提示:用 for...of 循环依次调用每个函数,把上一次的结果传给下一次。
参考答案
function pipe(...fns) {
return function(input) {
let result = input;
for (let fn of fns) {
result = fn(result);
}
return result;
};
}
const transform = pipe(
(s) => s.trim(),
(s) => s.toLowerCase(),
(s) => s.replace(/\s+/g, "-")
);
console.log(transform(" Hello World ")); // "hello-world"
常见错误诊断
undefined,但代码里明明有逻辑。
原因:函数没有
return,或者 return 写在了 if 分支里但没覆盖所有情况。
修复:检查每个分支是否都有
return,或在函数末尾加一个兜底的 return。
TypeError: xxx is not a function
原因:用函数表达式声明,但在声明之前调用了它。
修复:把调用移到声明之后,或者改用
function 声明。
this 是 undefined
原因:箭头函数没有自己的
this,它继承外层的 this。如果外层是模块或全局,this 就是 undefined。
修复:需要用
this 的地方,改用普通函数。
📝 要点总结
本章核心知识点
函数定义
声明、表达式、箭头函数三种方式
参数处理
默认参数、剩余参数、解构参数
作用域链
变量从内向外查找,形成作用域链
闭包
函数记住并访问外部变量的能力
高阶函数
函数作为参数或返回值,实现抽象和复用
下一章
函数学会了,接下来该学对象——用键值对组织你的数据,让信息不再散乱。