news 2025/12/28 1:13:51

JavaScript学习笔记:5.函数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript学习笔记:5.函数

JavaScript学习笔记:5.函数

上一篇咱们解锁了JS的“重复干活技能”(循环与迭代),这一篇来攻克JS的核心组件——函数。如果说变量是JS的“砖瓦”,循环是“重复施工工具”,那函数就是“预制构件厂”:把常用逻辑封装起来,需要时直接调用,不用重复写一堆代码。

函数不仅能让代码更简洁,还藏着JS的核心特性——闭包、this绑定、箭头函数等,这些知识点既是面试高频考点,也是实际开发中“少踩坑”的关键。今天就用“生活化比喻+实战避坑”的方式,带你吃透函数的方方面面,从此写出高复用、高可读性的代码~

一、函数的本质:把“重复逻辑”装进“工具箱”

函数的核心作用就两件事:代码复用(写一次用多次)和逻辑封装(把复杂逻辑藏起来,只暴露简单接口)。比如“计算平方”这个逻辑,写个函数封装后,不管是计算3的平方还是10的平方,直接调用就行,不用重复写n*n

1. 函数的两种“出生方式”:声明vs表达式

JS里定义函数有两种核心方式,就像“正式员工”和“临时工”,各有不同的“入职规则”。

(1)函数声明:有“提升特权”的正式员工

函数声明是最传统的定义方式,用function关键字开头,自带“函数提升”特权——可以在声明之前调用(就像正式员工提前到岗干活)。语法:

// 函数声明:function + 函数名 + 参数 + 函数体function计算平方(数字){return数字*数字;}// 可以在声明前调用(提升特权)console.log(计算平方(5));// 25(正常执行,不报错)
(2)函数表达式:无“提升特权”的临时工

函数表达式是把函数赋值给变量,分“匿名”和“命名”两种,没有提升特权——必须先定义再调用(临时工得先入职才能干活)。语法:

// 匿名函数表达式(无函数名)const计算平方=function(数字){return数字*数字;};// 命名函数表达式(有函数名,方便调试)const计算阶乘=function阶乘(n){returnn<2?1:n*阶乘(n-1);};// 不能在声明前调用(会报错)console.log(计算平方(5));// 25(定义后调用,正常)console.log(计算阶乘(3));// 6(命名表达式的函数名只能在内部使用)
核心坑:函数提升的“差异陷阱”

新手最容易栽在“提升”上:函数声明会被完整提升到作用域顶部,而函数表达式(尤其是用let/const声明的)不会提升,提前调用会报错:

// 正面例子:函数声明可以提前调用console.log(加一(3));// 4(正常)function加一(n){returnn+1;}// 反面例子:函数表达式提前调用报错console.log(减一(3));// ReferenceError: 减一 is not definedconst减一=function(n){returnn-1;};
怎么选?
  • 优先用函数声明:如果函数逻辑独立,且需要在多处调用,用声明(可读性高,支持提前调用)。
  • 用函数表达式:如果函数是临时使用(比如作为参数传递给其他函数),或需要根据条件定义函数,用表达式。

2. 函数的“干活流程”:参数→执行→返回值

函数就像一个“加工机器”:接收输入(参数),经过内部处理(函数体),输出结果(返回值)。

(1)参数:函数的“原材料”

参数分“形参”(函数定义时的占位符)和“实参”(函数调用时的实际值)。JS的参数传递有个关键规则:

  • 基本类型(数字、字符串、布尔):按值传递,函数内修改不会影响外部。
  • 引用类型(对象、数组):按引用传递,函数内修改对象/数组的属性/元素,会影响外部。
// 基本类型:按值传递(不影响外部)function修改数字(n){n=10;// 只修改函数内的副本}leta=5;修改数字(a);console.log(a);// 5(外部变量没变化)// 引用类型:按引用传递(影响外部)function修改对象(obj){obj.姓名="李四";// 修改的是对象的引用地址}let张三={姓名:"张三"};修改对象(张三);console.log(张三.姓名);// 李四(外部对象被修改)
(2)返回值:函数的“加工成果”

return语句返回结果,return后面的代码不会执行(相当于“下班信号”)。如果没有return,函数默认返回undefined

function加乘(a,b){returna+b;// 返回结果,后面的代码不执行console.log("这句话永远不会执行");}console.log(加乘(2,3));// 5(返回结果)console.log(加乘(2));// NaN(b默认是undefined,2+undefined=NaN)

二、函数的“生存空间”:作用域与闭包

作用域决定了变量的“访问权限”,而闭包是JS的“黑魔法”——让函数能“记住”自己的出生环境,即使离开也能访问外部变量。这部分是JS的核心难点,也是面试必问。

1. 函数作用域:变量的“专属领地”

函数内定义的变量是“局部变量”,只能在函数内访问;函数外定义的变量是“全局变量”,函数内可以访问。嵌套函数还能访问外层函数的变量(作用域链)。

// 全局变量:整个脚本都能访问const全局变量="我是全局的";function外层函数(){// 外层局部变量:外层和内层都能访问const外层变量="我是外层的";function内层函数(){// 内层局部变量:只有内层能访问const内层变量="我是内层的";console.log(全局变量);// 可以访问(作用域链向上查找)console.log(外层变量);// 可以访问console.log(内层变量);// 可以访问}内层函数();console.log(内层变量);// ReferenceError: 内层变量 is not defined(外层不能访问内层)}外层函数();
关键规则:作用域链

变量访问遵循“就近原则”:先找自己的作用域,找不到就向上找外层作用域,直到全局作用域。如果全局也没有,就报错ReferenceError

2. 闭包:带“记忆功能”的函数

闭包的本质是“嵌套函数+外层函数的作用域”——内层函数被返回到外层函数之外调用时,依然能访问外层函数的变量。就像你离开家时,把家门钥匙带在了身上,即使不在家,也能打开家门。

闭包的经典用法:保存状态(计数器例子)
// 外层函数:创建计数器的“环境”function创建计数器(){let计数=0;// 外层变量,被闭包记住// 内层函数:操作计数,形成闭包returnfunction(){计数++;return计数;};}// 创建两个独立的计数器(各自记住自己的计数)const计数器1=创建计数器();const计数器2=创建计数器();console.log(计数器1());// 1console.log(计数器1());// 2(记住了上一次的计数)console.log(计数器2());// 1(独立计数,不影响)
闭包的另一个用法:封装私有变量

JS没有原生的“私有变量”,但可以用闭包模拟——让变量只能通过特定方法访问,不能直接修改,保证数据安全。

function创建用户(姓名){let密码="123456";// 私有变量,外部无法直接访问return{getName(){return姓名;// 暴露“读姓名”的方法},修改密码(旧密码,新密码){// 暴露“改密码”的方法,带验证逻辑if(旧密码===密码){密码=新密码;return"密码修改成功";}return"旧密码错误";},};}const用户=创建用户("张三");console.log(用户.getName());// 张三(可以访问)console.log(用户.密码);// undefined(无法直接访问私有变量)console.log(用户.修改密码("123456","654321"));// 密码修改成功
闭包的坑:内存泄漏

闭包会让外层函数的变量一直存在于内存中(不会被垃圾回收),如果滥用闭包(比如大量创建闭包且不释放),会导致内存泄漏,让页面卡顿。

避坑指南

  • 只在需要“保存状态”或“封装私有变量”时使用闭包。
  • 不需要时,手动解除闭包引用(比如计数器1 = null),让垃圾回收机制回收变量。

三、函数的“高级参数玩法”:默认参数、剩余参数与arguments

参数是函数的“原材料入口”,JS提供了多种灵活的参数处理方式,让函数能应对不同的输入场景。

1. 默认参数:给“原材料”设个默认值

以前如果函数参数没传,默认是undefined,需要手动判断赋值。ES6的默认参数可以直接在定义时给参数设默认值,简洁又优雅。

// 以前的写法:手动判断undefinedfunction乘法(a,b){b=typeofb!=="undefined"?b:1;// 没传b就默认1returna*b;}// ES6默认参数:直接设默认值function乘法(a,b=1){returna*b;}console.log(乘法(5));// 5(b默认是1)console.log(乘法(5,3));// 15(传了b就用传入的值)
避坑点:默认参数的“暂时性死区”

默认参数的作用域是独立的,不能访问后面的参数,否则会报错:

// 反面例子:默认参数访问后面的参数,报错function错误例子(a=b,b=1){returna+b;}console.log(错误例子());// ReferenceError: Cannot access 'b' before initialization// 正面例子:后面的参数可以访问前面的参数function正确例子(a=1,b=a){returna+b;}console.log(正确例子());// 2(a=1,b=a=1)

2. 剩余参数:接收“不确定数量”的原材料

如果函数的参数数量不确定,以前要用arguments对象处理,现在用剩余参数(...变量名)更简洁,还能直接当成数组使用。

// 剩余参数:接收所有传入的参数,变成数组function求和(...数字们){return数字们.reduce((总和,数字)=>总和+数字,0);}console.log(求和(1,2));// 3console.log(求和(1,2,3,4));// 10(不管传多少个参数都能处理)
剩余参数vs arguments

arguments是函数内的内置对象,也能获取所有参数,但有两个缺点:

  • 是“类数组”,不是真正的数组,需要Array.from(arguments)转换才能用数组方法。
  • 箭头函数没有arguments对象。

剩余参数直接是数组,支持所有数组方法,且箭头函数也能使用,推荐优先用剩余参数。

3. arguments对象:老派的“参数容器”

arguments是函数内的内置对象,存储了所有传入的实参,适合老项目兼容或需要动态处理参数的场景。

function连接字符串(分隔符){let结果="";// arguments[0]是分隔符,从arguments[1]开始是要连接的字符串for(leti=1;i<arguments.length;i++){结果+=arguments[i]+分隔符;}return结果;}console.log(连接字符串("、","红","橙","黄"));// 红、橙、黄、

注意:箭头函数没有arguments对象,如果需要获取所有参数,只能用剩余参数。

四、箭头函数:ES6的“简化版函数”

ES6新增的箭头函数(() => {})是函数表达式的“简化语法”,写法更简洁,还解决了传统函数的this绑定问题,是开发中的“高频工具”。

1. 箭头函数的“简化语法”

箭头函数的语法可以根据场景简化,越简单的逻辑写起来越爽:

// 1. 单参数+单语句返回:省略括号和returnconst加一=n=>n+1;// 等价于 function(n) { return n + 1; }// 2. 多参数+单语句返回:参数加括号const求和=(a,b)=>a+b;// 3. 多语句+返回值:需要大括号和returnconst计算平方和=(a,b)=>{const平方A=a*a;const平方B=b*b;return平方A+平方B;};// 4. 无参数:括号不能省const说Hello=()=>console.log("Hello");

2. 箭头函数的核心优势:无独立this

传统函数的this绑定很“混乱”——谁调用它,this就指向谁(全局调用指向全局,对象调用指向对象)。而箭头函数没有自己的this,它的this继承自外层执行上下文的this,解决了“this绑定丢失”的经典问题。

经典场景:定时器中的this
// 传统函数:this绑定丢失(指向全局)function传统用户(){this.姓名="张三";this.年龄=20;setInterval(function(){this.年龄++;// this指向window,不是用户对象console.log(this.年龄);// NaN(window.年龄不存在)},1000);}// 箭头函数:this继承外层(指向用户对象)function箭头用户(){this.姓名="张三";this.年龄=20;setInterval(()=>{this.年龄++;// this指向外层的用户对象console.log(this.年龄);// 21、22、23...(正确)},1000);}const用户=new箭头用户();

3. 箭头函数的“禁忌场景”

箭头函数虽好,但不是万能的,以下场景不能用:

  • 不能作为构造函数(不能用new关键字调用):箭头函数没有prototype,用new会报错。
  • 不能作为对象的方法:对象方法中的this需要指向对象本身,而箭头函数的this继承自外层,会导致错误。
  • 需要arguments对象的场景:箭头函数没有arguments,只能用剩余参数替代。
// 反面例子1:箭头函数作为构造函数(报错)constPerson=()=>{};constp=newPerson();// TypeError: Person is not a constructor// 反面例子2:箭头函数作为对象方法(this指向错误)const对象={姓名:"张三",说姓名:()=>console.log(this.姓名)// this指向全局,不是对象};对象.说姓名();// undefined

五、函数的“其他高级玩法”:递归与预定义函数

除了上面的核心知识点,函数还有两个实用玩法:递归(自己调用自己)和预定义函数(JS内置的现成函数)。

1. 递归:函数的“自我调用”

递归是函数调用自身的写法,适合解决“分治问题”(比如遍历树结构、计算阶乘、斐波那契数列),逻辑比循环更简洁。

经典例子:计算阶乘(n! = n × (n-1) × … × 1)
function阶乘(n){// 终止条件:n=0或1时返回1(避免无限递归)if(n===0||n===1){return1;}// 递归调用:n × 阶乘(n-1)returnn*阶乘(n-1);}console.log(阶乘(5));// 120(5×4×3×2×1)
递归的坑:无限递归与栈溢出

递归必须有“终止条件”,否则会陷入无限递归,导致栈溢出(浏览器报错Maximum call stack size exceeded)。

避坑指南

  • 每次递归调用时,参数必须“靠近”终止条件(比如n-1)。
  • 复杂递归可以用“尾递归优化”(函数最后一句是递归调用,无其他计算),但JS对尾递归优化支持有限,大额递归建议用循环替代。

2. 预定义函数:JS的“现成工具”

JS内置了很多预定义函数(全局函数),不用自己写,直接调用就能实现常见功能:

  • parseInt(str, 进制):字符串转整数(必须指定进制,避免坑)。
  • parseFloat(str):字符串转浮点数。
  • isNaN(value):判断是否是NaN(注意:NaN !== NaN,不能直接用===判断)。
  • encodeURI(url)/decodeURI(url):编码/解码URL(不编码特殊字符如&)。
  • encodeURIComponent(url)/decodeURIComponent(url):编码/解码URL组件(编码所有特殊字符)。
// 常用预定义函数示例console.log(parseInt("101",2));// 5(二进制转十进制)console.log(parseFloat("3.14abc"));// 3.14(忽略后面的非数字字符)console.log(isNaN(NaN));// true(判断NaN的正确方式)console.log(encodeURIComponent("https://www.baidu.com?name=张三"));// 编码后:https%3A%2F%2Fwww.baidu.com%3Fname%3D%E5%BC%A0%E4%B8%89

六、函数实战避坑总结

  1. 函数定义:需要提前调用用“函数声明”,临时使用用“函数表达式”。
  2. 作用域:变量访问遵循“就近原则”,嵌套函数能访问外层变量。
  3. 闭包:只在需要“保存状态”或“封装私有变量”时使用,避免内存泄漏。
  4. 参数:优先用“默认参数+剩余参数”,替代arguments和手动判断undefined
  5. 箭头函数:适合回调函数(如定时器、数组方法),不适合构造函数和对象方法。
  6. 递归:必须有终止条件,复杂递归优先用循环替代。

七、最后:函数的“效率秘籍”

  • 复用逻辑优先封装成函数:避免重复代码,提高可读性和维护性。
  • 函数职责单一:一个函数只做一件事(比如“求和”就只求和,不做排序、过滤等其他操作)。
  • 函数名要“见名知意”:比如计算平方而不是fn1验证密码而不是check

函数是JS的核心,掌握了函数的定义、作用域、闭包、箭头函数等知识点,就能从“会写JS”升级到“写好JS”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/12 20:55:20

Android权限管理深度解析:特殊权限处理的实战指南

Android权限管理深度解析&#xff1a;特殊权限处理的实战指南 【免费下载链接】PermissionsDispatcher 项目地址: https://gitcode.com/gh_mirrors/pe/PermissionsDispatcher 在Android开发中&#xff0c;权限管理一直是开发者面临的痛点之一。特别是像SYSTEM_ALERT_WI…

作者头像 李华
网站建设 2025/12/12 20:55:18

获客成本降不下来?技术路径可能选错了

2025年&#xff0c;很多企业老板发现获客成本越来越高。搜索竞价点一下几十块&#xff0c;信息流广告投了没效果&#xff0c;传统SEO等半年才见效。问题可能不在预算&#xff0c;而在技术路径选错了。这篇文章聊聊为什么会出现这种情况&#xff0c;以及有什么新的思路。 最近跟…

作者头像 李华
网站建设 2025/12/12 20:52:58

3步搞定Stable Diffusion v2-base:从零开始创作惊艳AI图像

3步搞定Stable Diffusion v2-base&#xff1a;从零开始创作惊艳AI图像 【免费下载链接】stable-diffusion-2-base 项目地址: https://ai.gitcode.com/hf_mirrors/ai-gitcode/stable-diffusion-2-base 想象一下&#xff0c;你只需要输入一段文字描述&#xff0c;就能让A…

作者头像 李华
网站建设 2025/12/28 4:29:53

不只是学AI,更是思维的进化:我的CAIE认证上海站报考与成长全记录

去年秋天&#xff0c;我在上海参加了CAIE人工智能工程师认证的学习与考试。这段经历让我对AI有了不一样的体会——它不仅仅是技术的堆砌&#xff0c;更像是一次对思维方式的梳理和提升。如果你也在上海&#xff0c;正在观望是否要系统学习AI&#xff0c;或许我的这段历程能给你…

作者头像 李华
网站建设 2025/12/19 19:53:17

最近在帮朋友公司折腾指纹考勤系统,发现用Matlab实现库内指纹比对还挺有意思。今天咱们就手把手拆解这个从预处理到比对的完整流程,顺便聊聊实际开发中遇到的坑

基于matlab的指纹识别库内对比系统 【指纹识别】基于计算机视觉&#xff0c;含GUI界面 步骤&#xff1a;归一化&#xff0c;灰度化&#xff0c;二值化&#xff0c;细化&#xff0c;定位指纹中心点&#xff0c;提取特征&#xff0c;库内比对&#xff0c;结果识别。 功能&#xf…

作者头像 李华
网站建设 2025/12/12 20:50:42

基于Anolis OS的国产CPU性能优化实践,共推多芯混部时代操作系统新范式

2025 年 11 月&#xff0c;备受瞩目的龙蜥大会在北京隆重举行。作为中国开源操作系统生态的重要里程碑&#xff0c;本届大会汇聚了来自芯片、硬件、软件及云服务等领域的顶尖专家与行业代表。会上&#xff0c;阿里云智能集团高级技术专家沈培以“国产 CPU 平台上操作系统和云产…

作者头像 李华