
深入理解 JavaScript 函数调用方式、高阶函数与模块模式【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum本文基于 curriculum 仓库中旧版 JavaScript 课程 deep_dive_functions.md 一课展开是函数主题的进阶篇。全篇以JavaScript 热爱函数为线索系统拆解函数的两种调用方式方法式与函数式、apply/call/bind的作用、构造函数的适用时机、参数的必需性判定、以函数为参数与返回值的高阶函数、以及用模块模式实现代码命名空间这六大核心议题。学完本文你将能够在真实项目中熟练组织、调用并复用函数写出结构清晰、可维护的 JavaScript 代码。为什么函数值得深挖在本仓库的旧版 JavaScript 课程体系中函数并不是一个新话题——js_basics.md 已经覆盖了函数声明的两种方式、匿名函数、IIFE 与闭包scope_closures.md 则专门探讨了作用域、this与闭包。而本文要回答的问题更贴近实战函数在被调用时如何被对待以及我们如何利用函数天然的特性把代码组织得井井有条。你会在真实项目中反复遇到一个被称为模块模式module pattern的代码组织范式它把相关的函数与变量逻辑性地分组在一起从而让代码保持组织性organized与可维护性maintainable。理解这一模式的前提是先把函数本身的调用机制、参数机制与传递机制彻底弄清楚。函数的两种调用方式方法式与函数式同一个函数用不同的方式调用其内部this的值会截然不同——这正是 scope_closures.md 中反复强调的神秘this变量。函数式调用function-style直接以函数名调用此时this指向全局对象浏览器中为windowfunction sayHi() { console.log(this); // window非严格模式下 } sayHi(); // 函数式调用方法式调用method-style把函数作为某个对象的属性再通过对象.属性的方式调用此时this指向该对象本身const greeter { name: Odin, greet: function () { console.log(this.name); }, }; greeter.greet(); // 方法式调用this greeter输出 Odin关键结论this的取值不是由函数在哪里定义决定而是由函数如何被调用决定。这正是 js_basics.md 学习目标中this与 Ruby 的self有何异同这一问题的答案核心——Ruby 的self随定义语境变化而 JS 的this随调用语境变化。方法式调用为何重要方法式调用让函数能够依附于对象运行这直接支撑了下一节的apply/call如果你手中只有一个孤立的函数却希望它以某个对象的方法的身份被调用就需要显式指定this。apply与call显式指定调用语境apply与call都是函数对象自带的方法作用完全相同——立即调用该函数并显式指定其内部this指向谁区别只在于传递参数的方式方法参数传递方式典型场景fn.call(thisArg, arg1, arg2, ...)参数逐个列出参数数量固定、已知fn.apply(thisArg, [arg1, arg2, ...])参数以数组或类数组传入参数数量动态、来自数组function introduce(city, age) { console.log(${this.name} lives in ${city}, age ${age}); } const person { name: Alice }; // call参数逐个传入 introduce.call(person, Shanghai, 28); // apply参数以数组传入 introduce.apply(person, [Shanghai, 28]);而 scope_closures.md 学习目标中提到的bind与二者不同bind不立即执行而是返回一个this被永久绑定的新函数常用于事件回调等函数被延迟调用的场景。三者的分工可以概括为call/apply立即调用临时指定thisbind预绑定this生成可反复使用的新函数。何时使用构造函数本仓库的 objects_prototypes.md 指出JavaScript 没有 Ruby 意义上的类而是用原型prototype与构造函数来完成代码复用与 DRY。构造函数是用new调用、专门用来批量创建同类对象的函数function Dog(name) { this.name name; } const rex new Dog(Rex); console.log(rex.name); // Rex使用构造函数的时机可以归纳为两条判断当你需要创建多个结构相同、行为共享的对象多个玩家、多个格子、多个任务条目……时当你需要利用原型链让这些实例共享方法、避免为每个实例重复定义函数时。与之相对如果只是需要一个临时、一次性的数据容器字面量方式const obj {...}更轻量——js_basics.md 明确对比了构造函数方式与字面量方式两种创建对象的手段。一个实用建议构造函数首字母大写以此区分普通函数这也是业界约定俗成的命名规范。参数必需、可选与溢出JavaScript 函数对参数的态度非常宽松这既是便利也是陷阱理解它需要抓住三条规则。规则一参数缺失不报错调用时少传参数缺失的参数值为undefined。判断一个参数是否必需就看函数体内是否依赖该参数进行计算function multiply(a, b) { return a * b; } multiply(3); // NaNb 为 undefined规则二用默认值表示可选参数如果一个参数在调用方未提供时应有合理默认值就应在函数体内补上兜底逻辑。在 ES6 之前通常用||或显式判断现代写法是默认参数function greet(name, greeting Hello) { console.log(${greeting}, ${name}); } greet(Odin); // Hello, Odin greet(Odin, Hi); // Hi, Odin如何表示一个参数不是必需的两种主流做法要么给它默认值要么在文档注释中明确标注并在函数体内用条件判断处理它的缺失。规则三用arguments访问溢出参数调用时多传的参数会被静默忽略——但它们并非消失而是被保存在函数体内自动可用的类数组对象arguments中。借助它函数可以接收不定数量的参数function sum() { let total 0; for (let i 0; i arguments.length; i) { total arguments[i]; } return total; } sum(1, 2, 3); // 6 sum(1, 2, 3, 4); // 10额外参数通过 arguments 访问注意arguments是类数组而非真正的数组不能直接调用Array.prototype上的方法。更现代、可读性更好的做法是使用 ES6 的剩余参数rest parametersfunction sum(...numbers)它返回一个真正的数组。从仓库中 js_basics.md 反复强调的用#forEach遍历数组等习惯来看现代代码中优先使用剩余参数能让遍历更顺畅。高阶函数函数即数据JavaScript 的函数是一等公民可以像字符串、数字一样被传递。这正是本课标题JavaScript 热爱函数的体现也是接下来两种模式的基础。函数作为参数回调callbackevent_driven.md 给出了最精炼的定义回调就是一个作为参数传给另一个函数的函数。判断一个函数能否接收另一个函数作为参数的标准只有一个该函数是否在参数位置上期望一个函数类型的值并在合适的时机调用它。// 第一个函数期望接收一个回调并在内部调用它 function firstFunction(callback) { callback(hello); } // 第二个函数作为回调被传入 function secondFunction(string) { console.log(string); } firstFunction(secondFunction); // 控制台输出 helloevent_driven.md 特别提醒了两点实战要点回调的格式必须与调用方预期一致——调用方传几个参数、传什么类型回调就要按此签名编写参数数量不匹配是常见 bug 来源不要指望回调return数据——回调的本质是把信息向前抛留待将来使用而非像普通函数那样输入→返回。函数返回函数闭包与工厂一个函数可以返回另一个函数而返回的函数可以记住外层函数的作用域——这就是 js_basics.md 与 scope_closures.md 中闭包机制的典型应用function makeCounter() { let count 0; // 被闭包捕获的私有变量 return function () { count 1; return count; }; } const counter makeCounter(); counter(); // 1 counter(); // 2 counter(); // 3如何运行一个被返回的函数两种方式// 方式一先把返回的函数存入变量再调用 const increment makeCounter(); increment(); // 方式二链式直接调用注意括号的嵌套顺序 makeCounter()();闭包的价值在于外层函数的局部变量本应在调用结束后被销毁但因为内部函数仍然引用着它这份状态被保存了下来且外部无法直接访问——这正是下一节模块模式实现私有变量的底层原理。模块模式与命名空间当项目变大大量全局函数与全局变量会互相污染、难以追踪。两个被反复使用的解法是命名空间namespacing与模块模式module pattern。什么是命名空间命名空间指的是把你代码中相关的变量和函数放进一个独一无二的对象或作用域之下避免与全局作用域中的其他代码冲突。// 不使用命名空间两个全局函数可能互相覆盖 function render() { /* ... */ } function render() { /* 另一个模块也想叫这个名字 */ } // 冲突 // 使用命名空间把功能收拢到对象下 const Game {}; Game.render function () { /* ... */ };从 objects_prototypes.md 的表述看对象既可用于按逻辑分组变量也可以用于在某个命名空间内分组函数二者一脉相承。用模块模式实现命名空间模块模式是命名空间的标准落地方式核心三件套IIFE立即调用函数表达式 闭包 返回的对象。外层函数创建私有作用域内部函数通过闭包访问私有变量最后把要对外暴露的接口通过返回对象导出const ChatModule (function () { // 私有变量与私有函数外部无法直接访问 let messageCount 0; function log(message) { console.log([${messageCount}] ${message}); } // 公开接口只有这里返回的内容对外可见 return { send: function (text) { log(text); }, count: function () { return messageCount; }, }; })(); ChatModule.send(Hello); // 输出 [1] Hello ChatModule.count(); // 1 ChatModule.log; // undefined —— 私有函数未暴露这段代码同时演示了本课的所有核心概念外层(function(){...})()是 IIFE在 js_basics.md 中有定义messageCount与log依赖闭包保持私密状态最终把对象字面量返回给ChatModule完成命名空间化。关于surrogates代理/替身原文档的学习目标中包含什么是 surrogates 以及为何使用它们。从模块模式与闭包的语境推断该术语在本课程其他文件中未再展开故以下为基于语境的解释在闭包实现中内部函数并不能直接拥有外层变量而是通过作用域链持有对外层变量环境的引用闭包在某种意义上充当了访问这些变量的代理/替身surrogate——外层函数执行结束后变量本应消亡却因闭包这个代理的引用而存活并被持续访问。理解这一点就理解了为什么模块模式能长期保存私有状态不是状态本身被复制而是闭包作为其代理持续存在。实战练习与仓库延伸阅读本仓库旧版 JavaScript 课程为函数主题配套了两个实战项目非常适合巩固本课所学project_functions.md在浏览器中实现扫雷Minesweeper要求你先规划需要哪些对象和函数正是模块化思维的落地训练project_scopes_closures.md用 JavaScript 实现井字棋Tic Tac Toe涉及构造函数创建棋盘对象、事件回调与游戏状态管理。更进一步的原理补全建议按顺序阅读仓库内这几篇姊妹课程主题仓库文档作用域、this、bind与闭包scope_closures.md回调函数与事件驱动event_driven.md对象、构造函数与原型链objects_prototypes.mdIIFE、匿名函数、闭包与bind速览js_basics.md代码组织与风格规范organizing_code.md小结一张图记住函数进阶要点问题答案函数式调用直接fn()调用this指向全局方法式调用通过obj.fn()调用this指向对象apply与call的作用立即调用函数并显式指定this区别仅在传参方式何时使用构造函数需要批量创建结构相同、方法共享的对象时参数何时必需函数体依赖该参数进行计算时缺失为undefined如何表示参数非必需提供默认值或在函数内做缺失处理如何访问溢出参数通过arguments类数组对象现代写法用剩余参数函数作为参数回调模式调用方决定回调的签名与调用时机函数返回函数闭包工厂返回的函数可保存并访问外层私有状态如何运行返回的函数存入变量后调用或链式fn()()什么是命名空间将相关变量/函数收拢到唯一对象或作用域下避免冲突如何用模块命名空间IIFE 闭包 返回对象即模块模式函数是 JavaScript 最强大的语言特性之一。掌握调用语境、参数机制与高阶函数之后模块模式这一组织范式就不再是神秘的黑盒而会成为你每个项目中最顺手的基础设施——本仓库后续的扫雷与井字棋项目正是检验这些能力的第一个试炼场。【免费下载链接】curriculumThe open curriculum for learning web development项目地址: https://gitcode.com/GitHub_Trending/cu/curriculum创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考