门户网站建设的背景,谷歌商店paypal官网下载,虚拟货币网站建设,2008系统怎么做网站JavaScript学习笔记#xff1a;5.函数
上一篇咱们解锁了JS的“重复干活技能”#xff08;循环与迭代#xff09;#xff0c;这一篇来攻克JS的核心组件——函数。如果说变量是JS的“砖瓦”#xff0c;循环是“重复施工工具”#xff0c;那函数就是“预制构件厂”#xff…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){returnn2?1:n*阶乘(n-1);};// 不能在声明前调用会报错console.log(计算平方(5));// 25定义后调用正常console.log(计算阶乘(3));// 6命名表达式的函数名只能在内部使用核心坑函数提升的“差异陷阱”新手最容易栽在“提升”上函数声明会被完整提升到作用域顶部而函数表达式尤其是用let/const声明的不会提升提前调用会报错// 正面例子函数声明可以提前调用console.log(加一(3));// 4正常function加一(n){returnn1;}// 反面例子函数表达式提前调用报错console.log(减一(3));// ReferenceError: 减一 is not definedconst减一function(n){returnn-1;};怎么选优先用函数声明如果函数逻辑独立且需要在多处调用用声明可读性高支持提前调用。用函数表达式如果函数是临时使用比如作为参数传递给其他函数或需要根据条件定义函数用表达式。2. 函数的“干活流程”参数→执行→返回值函数就像一个“加工机器”接收输入参数经过内部处理函数体输出结果返回值。1参数函数的“原材料”参数分“形参”函数定义时的占位符和“实参”函数调用时的实际值。JS的参数传递有个关键规则基本类型数字、字符串、布尔按值传递函数内修改不会影响外部。引用类型对象、数组按引用传递函数内修改对象/数组的属性/元素会影响外部。// 基本类型按值传递不影响外部function修改数字(n){n10;// 只修改函数内的副本}leta5;修改数字(a);console.log(a);// 5外部变量没变化// 引用类型按引用传递影响外部function修改对象(obj){obj.姓名李四;// 修改的是对象的引用地址}let张三{姓名:张三};修改对象(张三);console.log(张三.姓名);// 李四外部对象被修改2返回值函数的“加工成果”用return语句返回结果return后面的代码不会执行相当于“下班信号”。如果没有return函数默认返回undefined。function加乘(a,b){returnab;// 返回结果后面的代码不执行console.log(这句话永远不会执行);}console.log(加乘(2,3));// 5返回结果console.log(加乘(2));// NaNb默认是undefined2undefinedNaN二、函数的“生存空间”作用域与闭包作用域决定了变量的“访问权限”而闭包是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){btypeofb!undefined?b:1;// 没传b就默认1returna*b;}// ES6默认参数直接设默认值function乘法(a,b1){returna*b;}console.log(乘法(5));// 5b默认是1console.log(乘法(5,3));// 15传了b就用传入的值避坑点默认参数的“暂时性死区”默认参数的作用域是独立的不能访问后面的参数否则会报错// 反面例子默认参数访问后面的参数报错function错误例子(ab,b1){returnab;}console.log(错误例子());// ReferenceError: Cannot access b before initialization// 正面例子后面的参数可以访问前面的参数function正确例子(a1,ba){returnab;}console.log(正确例子());// 2a1ba12. 剩余参数接收“不确定数量”的原材料如果函数的参数数量不确定以前要用arguments对象处理现在用剩余参数...变量名更简洁还能直接当成数组使用。// 剩余参数接收所有传入的参数变成数组function求和(...数字们){return数字们.reduce((总和,数字)总和数字,0);}console.log(求和(1,2));// 3console.log(求和(1,2,3,4));// 10不管传多少个参数都能处理剩余参数vs argumentsarguments是函数内的内置对象也能获取所有参数但有两个缺点是“类数组”不是真正的数组需要Array.from(arguments)转换才能用数组方法。箭头函数没有arguments对象。剩余参数直接是数组支持所有数组方法且箭头函数也能使用推荐优先用剩余参数。3. arguments对象老派的“参数容器”arguments是函数内的内置对象存储了所有传入的实参适合老项目兼容或需要动态处理参数的场景。function连接字符串(分隔符){let结果;// arguments[0]是分隔符从arguments[1]开始是要连接的字符串for(leti1;iarguments.length;i){结果arguments[i]分隔符;}return结果;}console.log(连接字符串(、,红,橙,黄));// 红、橙、黄、注意箭头函数没有arguments对象如果需要获取所有参数只能用剩余参数。四、箭头函数ES6的“简化版函数”ES6新增的箭头函数() {}是函数表达式的“简化语法”写法更简洁还解决了传统函数的this绑定问题是开发中的“高频工具”。1. 箭头函数的“简化语法”箭头函数的语法可以根据场景简化越简单的逻辑写起来越爽// 1. 单参数单语句返回省略括号和returnconst加一nn1;// 等价于 function(n) { return n 1; }// 2. 多参数单语句返回参数加括号const求和(a,b)ab;// 3. 多语句返回值需要大括号和returnconst计算平方和(a,b){const平方Aa*a;const平方Bb*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.年龄);// NaNwindow.年龄不存在},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(){};constpnewPerson();// TypeError: Person is not a constructor// 反面例子2箭头函数作为对象方法this指向错误const对象{姓名:张三,说姓名:()console.log(this.姓名)// this指向全局不是对象};对象.说姓名();// undefined五、函数的“其他高级玩法”递归与预定义函数除了上面的核心知识点函数还有两个实用玩法递归自己调用自己和预定义函数JS内置的现成函数。1. 递归函数的“自我调用”递归是函数调用自身的写法适合解决“分治问题”比如遍历树结构、计算阶乘、斐波那契数列逻辑比循环更简洁。经典例子计算阶乘n! n × (n-1) × … × 1function阶乘(n){// 终止条件n0或1时返回1避免无限递归if(n0||n1){return1;}// 递归调用n × 阶乘(n-1)returnn*阶乘(n-1);}console.log(阶乘(5));// 1205×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六、函数实战避坑总结函数定义需要提前调用用“函数声明”临时使用用“函数表达式”。作用域变量访问遵循“就近原则”嵌套函数能访问外层变量。闭包只在需要“保存状态”或“封装私有变量”时使用避免内存泄漏。参数优先用“默认参数剩余参数”替代arguments和手动判断undefined。箭头函数适合回调函数如定时器、数组方法不适合构造函数和对象方法。递归必须有终止条件复杂递归优先用循环替代。七、最后函数的“效率秘籍”复用逻辑优先封装成函数避免重复代码提高可读性和维护性。函数职责单一一个函数只做一件事比如“求和”就只求和不做排序、过滤等其他操作。函数名要“见名知意”比如计算平方而不是fn1验证密码而不是check。函数是JS的核心掌握了函数的定义、作用域、闭包、箭头函数等知识点就能从“会写JS”升级到“写好JS”。