04函数基础

函数就是一段可重复调用的代码——给它一组输入,它按固定流程算出一个结果。 JavaScript 把函数当成"一等公民":能赋值、能传参、能返回、还能作为别人的输入或输出。 本课把三种写法、参数、返回值、作用域与闭包、this、高阶函数、IIFE 一次性讲清,让你从此能写出结构清晰、能复用的代码。

约 18 分钟 8 个动手实验 function / arrow / 作用域

一、函数三种写法:声明 / 表达式 / 箭头

JS 函数有三种写法:function 声明(最经典,自动提升)、const 表达式 (要等赋值以后才能用)、const = () => { } 箭头函数(ES6 新增,语法最短, 不绑定自己的 this 和 arguments)。日常开发以箭头为主, 写法最干净;涉及 this 绑定或需要构造函数时才退回 function。

function 声明function add(a,b){return a+b} · 提升到作用域顶端,可以先调用后定义
const 表达式const add = function(a,b){...} · 不提升,必须先赋值再调用
箭头函数const add = (a,b) => a+b · 不绑 this / arguments,单行可省略 return
三种写法对照:提升、this、arguments
写法是否提升this 怎么定有 arguments 吗典型场景
function 声明✓ 提升到顶端看调用方式✓ 有主流程、构造函数、需要 this
const 表达式✗ 必须先赋值看调用方式✓ 有需要把函数当值传来传去
箭头函数✗ 必须先赋值看外层(词法)✗ 没有回调、数组方法、闭包
同一段加法逻辑用三种写法各写一次,打开 console.log 看输出。试着把三种 add 各调用两次,体会「提升 vs 不提升」的差别
工程心法:回调、数组方法、临时工具函数——能箭头就箭头; 一旦要 this、要做构造函数、要用 arguments——退回 function。 团队里风格统一就行,不必每个都争论。

二、参数:默认值 / 剩余 / 解构

JS 函数参数非常灵活:可以设默认值(ES6), 可以用 ...args 把剩下的参数打包成数组(剩余参数), 还可以直接在形参里解构对象——像点菜一样精确告诉函数你需要哪些字段。

三种参数增强写法一览
写法语法典型用途
默认参数function f(a, b = 10) { }参数不传时给一个合理兜底
剩余参数function f(first, ...rest) { }参数个数不确定(替代 arguments)
解构参数function f({ name, age }) { }配置对象、用户信息等结构化输入
用剩余参数写一个 sum 函数:sum(1,2,3,4) = 10。把传入的数字改成 5 个、10 个,看 nums 怎么"全收"
记住一句口诀:默认参数管"少给",剩余参数管"多给",解构参数管"乱给"—— 三种写法经常一起出现,比如 function fn({ page = 1, size = 10 } = {}, ...rest) 也不稀奇。

三、返回值与提前结束

return 把结果交还给调用方,并立刻结束函数执行—— return 后面的代码全部跳过。函数没写 return 等价于返回 undefined。 箭头函数如果函数体只有一条表达式,可以省略 return 和大括号,单行写完。

写三个函数:max 返回两数最大值;isEven 返回布尔;noReturn 不写 return。三个返回值分别打到页面上对比
常见坑:箭头函数体只有一条语句时,单行可省 return; 一旦写成多行必须加 { } 和 return,否则返回 undefined—— const add = (a, b) => { a + b } 实际什么都不返回。

四、作用域与闭包入门

作用域就是变量的"生存地盘":全局作用域(任何地方都能访问)、 函数作用域(函数体内)、块作用域({ } 内部,仅 let/const)。 闭包 = 函数能"记住"它定义时的变量环境——哪怕那个外层函数已经执行完毕, 内部函数依然能读到那些变量。这是 JS 最强大的特性之一。

三种作用域对照
作用域由谁创建能用什么变量经典示例
全局作用域页面加载页面任意位置var a = 1 写在最外层
函数作用域每次函数调用参数 + 函数内声明的变量function f() { var x }
块作用域{ } 块仅 let / constif / for { let y }
经典闭包计数器:每次点按钮,n 在外层函数执行完后依然"活着"——拆掉返回的 addOne 会怎样?
闭包不是玄学,而是函数 + 它定义时的变量环境。 它是模块化、防抖、节流、柯里化的底层机制。看完下面 IIFE 的经典坑, 你会发现:JS 里几乎所有"能记住状态"的写法,本质都是闭包。

五、this 绑定(入门)

this 是函数内部的一个特殊变量,指向"调用这个函数的那个对象"。 普通函数的 this 取决于调用方式——不是定义位置; 箭头函数的 this 取决于定义时的外层,永远不变(也叫"词法 this")。

this 四种绑定场景速查
调用方式this 指向示例
对象方法调用它的那个对象obj.fn() → this = obj
独立调用非严格模式 → window;严格模式 → undefinedfn()
箭头函数定义时的外层 this() => this 取外层
new 调用新创建的实例new Fn() → this = {}
三种 this 场景:对象方法、独立调用、箭头函数。把 obj.tag 改成 window.tag 看独立调用 this 跑到哪
记住两条铁律:① 箭头函数没有自己的 this, 它"继承"外层(因此不能做构造函数、不能用 call/apply/bind 改 this); ② 普通函数的 this 看调用方式,不看定义位置—— var f = obj.fn; f(); 的 this 已经不再指向 obj,这是新手最常踩的坑。

六、高阶函数:参数或返回值是函数

高阶函数 = 接收函数作为参数、或把函数作为返回值的函数。 数组三剑客 map / filter / reduce 全是这种思路:把"对每个元素做什么"这件事 交给调用方写,数组方法只负责遍历。这种"行为参数化"是 JS 写法优雅的关键。

数组三剑客签名速查
方法签名作用不修改原数组?
maparr.map((item, i) => newItem)每个元素"变形",返回新数组✓
filterarr.filter((item) => bool)留下满足条件的,返回新数组✓
reducearr.reduce((acc, item) => next, init)把数组"折叠"成一个值✓
forEacharr.forEach((item) => { })只遍历,不返回(返回 undefined)—
一组数字 [1,2,3,4,5] 用 map 平方、filter 偶数、reduce 求和,三种结果同框对比
链式调用是数组方法的杀手锏:先把数据用 filter 筛干净, 再用 map 变形,最后用 reduce 折叠——三步像流水线一样清爽。 一旦发现自己在写 for 里 push 数组,几乎都能换成 map/filter/reduce。

七、IIFE 与函数表达式应用

IIFE(Immediately Invoked Function Expression)= 定义后立刻执行的函数表达式。 写法:(function() { /* 代码 */ })();。最常见的用途是 创建一个独立作用域,避免变量污染全局。 ES6 之后有了 { } 块作用域,IIFE 的"造作用域"作用被削弱, 但解决 setTimeout 闭包陷阱依然是经典教学案例。

IIFE 双演示:①IIFE 隔离作用域不污染全局;②setTimeout 闭包保留 i,把经典的 5 个 5 修成 0/1/2/3/4
现代项目里,直接用 let 替代 var 就能避开 setTimeout 闭包陷阱—— for (let i = 0; i < 5; i++) 的 let 每次循环都创建独立绑定, 等价于 IIFE 的效果。但 IIFE 这个案例值得记住,因为面试常考、 也是理解闭包最直观的场景。

八、小结

  • JS 函数三种写法:function 声明(提升)/ const 表达式(不提升)/ 箭头(不绑 this),回调与数组方法优先用箭头。
  • 参数三大增强:默认参数 b = 10 / 剩余参数 ...args / 解构参数 { name, age },常一起出现。
  • return 立刻结束函数并返回值;不写 return 等价于 return undefined;箭头单行可省 return。
  • 作用域分全局 / 函数 / 块(let/const);闭包 = 函数 + 定义时的变量环境,是防抖节流、模块化的基础。
  • 普通函数 this 看调用方式(对象方法 → 对象,独立调用 → window);箭头函数 this 永远看外层——不能做构造函数。
  • 高阶函数(map / filter / reduce)把行为参数化,链式调用是数组方法的杀手锏。
  • IIFE = 立即执行的函数表达式,用于隔离作用域;经典用途是修 var + setTimeout 的闭包陷阱(用 let 也能修)。
动手练习:①写一个阶乘箭头函数 factorial(5) = 120(提示:用 reduce 或递归); ②写一个闭包累加器 const acc = makeAdder(10),调 acc(5) 返回 15; ③写一个 map 平方函数 const squareAll = arr => arr.map(n => n * n); ④用 IIFE 写一个独立作用域,往里面塞个 var pass = 'abc',外面访问不到。 全部用 console.log 验证。