JavaScript 自定义函数
JavaScript 自定义函数
一、函数概述
函数是可重用代码块,完成特定功能,避免重复编码,且仅在调用时执行,可规避页面载入时脚本自动执行问题。
二、函数定义方式
(一)Function 构造函数定义函数
javascript
运行
var 函数名 = new Function("参数1", "参数2", … "函数体");
特点:
- Function 首字母大写,仅在 JavaScript 1.1+ 可用。
- 每次调用构造函数会解析函数体、创建新函数对象,效率低,实际开发少用。
(二)function 语句定义函数(最常用)
javascript
运行
function 函数名(参数1, 参数2 … [形参]) {
<语句块>
return 返回值;
}
关键说明:
- function 关键字:用于声明函数。
- 函数名:合法标识符,区分大小写,需唯一。
- 参数:0 个或多个,多参数用逗号分隔,无论有无参数,小括号必选。
- 函数体:大括号包裹的代码逻辑。
- 返回值:return 关键字可选,决定函数返回结果,无 return 则默认返回 undefined。
- 预解析特性:定义的函数会被预解析,优先执行,可在定义前调用。
(三)表达式中定义函数
javascript
运行
var 函数名 = function(参数1, 参数2, …) { 函数体 };
特点:
- 函数名本质是变量名,函数赋值给变量,JS 1.2+ 可用。
- 示例(禁用鼠标右键):
javascript
运行
var rightKey = function() {
if (event.button == 2) {
alert("禁止使用鼠标右键");
}
};
window.onmousedown = rightKey;
三、函数调用
1. 直接调用
直接通过函数名调用,如 myFunction(); 或 window.myFunction();(函数挂载到 window 时)。
2. 事件处理调用
在 HTML 事件属性中绑定,如:
html
预览
<div onclick="myFunction()"></div>
3. 赋值调用
将函数返回值赋给变量,如:
javascript
运行
var t = myFunction();
四、函数参数
1. 形参 vs 实参
- 形参:函数定义时,括号内声明的参数(形式参数 ),JS 支持给形参设默认值。
- 实参:调用函数时,传入的实际参数 。
2. 参数匹配规则
默认形参与实参一一对应,不匹配时:
- 实参数量 < 形参数量:多余形参值为 undefined。
- 实参数量 > 形参数量:多余实参被忽略。
3. 特殊参数对象 arguments
函数内可通过 arguments 获取所有实参,它是类数组对象,支持下标访问、length 属性。
示例:
javascript
运行
function Test(a, b) {
alert(typeof(arguments)); // 输出 "object",类数组类型
alert(arguments[1]); // 获取下标为 1 的实参值
alert(arguments.length); // 实参数量
alert(arguments.callee); // 函数自身(严格模式禁用)
}
Test("zhang", "li");
4. 默认参数示例
定义带默认参数的函数,不传参时用默认值,如创建表格函数:
javascript
运行
function tab(row, color = 'pink', color2 = 'gray') {
document.write('<table border="1" width="500">');
for (var index = 0; index < row; index++) {
if (index % 2 == 1) {
document.write('<tr bgcolor="' + color + '"><td>' + index + '</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>');
} else {
document.write('<tr bgcolor="' + color2 + '"><td>' + index + '</td><td>1</td><td>1</td><td>1</td><td>1</td></tr>');
}
}
document.write('</table>');
}
// 调用,可传参覆盖默认值
tab(10, 'gray', 'orange');
五、函数返回值
- return 关键字决定函数返回值,无 return 则默认返回 undefined。
- 执行 return 后,函数立即停止执行,后续代码不运行。
- 需在函数外使用内部变量结果时,用 return 返回值。
六、变量的作用域
(一)全局变量与局部变量
- 全局变量:在函数外部声明的变量,作用域覆盖整个 JS 文档,任意位置(包括函数内部)可访问、修改。
- 局部变量:在函数内部用
var声明的变量,仅在当前函数内部生效,函数外部无法访问。
(二)作用域交互规则
1. 全局变量访问局部
全局变量的作用域包含所有局部作用域,因此可在函数内部的任意位置直接使用全局变量。
2. 局部变量暴露到全局
局部变量默认仅在函数内可用,若需在全局使用,有两种方式(需谨慎使用,避免全局污染):
- 方式 1:移除局部变量的
var声明,使其隐式成为全局变量(不推荐,易与现有全局变量冲突)。 - 方式 2:通过函数的
return语句,将局部变量的值返回给全局,再用全局变量接收(推荐,更规范)。
(三)块级作用域(ES6 新增)
在 ES6 之前,JS 无块级作用域(if、for 等代码块无法限制 var 变量的作用域);ES6 中通过 let、const 关键字配合 {} 形成块级作用域,变量仅在当前代码块(如 if {}、for {})内有效,出块后无法访问。
(四)作用域链【了解】
1. 概念
- 只要声明变量,就会产生作用域(全局作用域是默认顶层作用域)。
- 函数内部会形成局部作用域,若函数内嵌套函数,会进一步产生嵌套作用域(子作用域)。
- 当在当前作用域查找某个变量时,若未找到,会自动向上级作用域逐层查找,直至全局作用域,这个逐层查找的过程形成的链条,称为作用域链。
2. 访问规则
- 内部作用域(子作用域)可通过作用域链访问所有外部作用域(父作用域、祖父作用域… 全局作用域)的变量和函数。
- 外部作用域无法访问内部作用域的任何变量和函数,即 “内层可访问外层,外层不可访问内层”。
3. 示例分析
javascript
运行
var n = 10; // 全局变量
function outer() {
function inner() {
function center() {
console.log(n); // 查找链:center → inner → outer → 全局
}
center();
}
inner();
var n = 15; // outer 内局部变量,预解析时声明提升,赋值在后
}
outer(); // 输出 undefined(因 outer 内 n 声明提升,center 查找时取 outer 内未赋值的 n)
4. 经典案例
javascript
运行
var a = 1;
function fn1() {
function fn2() {
console.log(a); // 查找链:fn2 → fn3 → fn1 → 全局,最终取 fn1 内的 a=2
}
function fn3() {
var a = 4; // fn3 内局部变量,不影响 fn2 查找
fn2();
}
var a = 2; // fn1 内局部变量,覆盖全局 a=1
return fn3;
}
var fn = fn1(); // 调用 fn1,返回 fn3 函数
fn(); // 调用 fn3,执行 fn2,输出 2
七、预解析(变量 / 函数提升)
(一)核心原理
JS 代码由浏览器的 JS 解析器执行,执行过程分为两步:预解析 → 代码执行:
- 预解析:在代码真正执行前,解析器会提前处理带
var和function关键字的内容,分为 “变量提升” 和 “函数提升”。 - 代码执行:预解析完成后,按代码书写顺序自上而下执行。
(二)预解析规则
1. 变量提升(var 关键字)
- 仅 “声明” 变量,不进行赋值,变量默认值为
undefined。 - 示例:
var num = 10;预解析时,仅执行var num;,num = 10需在代码执行阶段完成。
2. 函数提升(function 关键字)
- 既 “声明” 函数,又 “定义” 函数(即提前创建函数对象,可直接调用)。
- 示例:
function fun() {}预解析时,fun会被提前挂载到当前作用域,可在函数定义前调用。
3. 优先级
- 函数提升优先级高于变量提升:若变量和函数同名,预解析后函数会覆盖变量的声明(但代码执行阶段,变量赋值会覆盖函数)。
- 预解析仅在当前作用域生效:全局作用域预解析全局变量 / 函数,函数执行时才会预解析函数内部的变量 / 函数。
(三)经典案例解析
案例 1:函数内变量提升
javascript
运行
var num = 10;
fun(); // 调用函数(函数提升,可提前调用)
function fun() {
console.log(num); // 预解析:fun 内提升 var num,未赋值 → 输出 undefined
var num = 20; // 代码执行阶段:给 num 赋值 20
}
// 预解析流程:
// 全局:提升 var num; 、function fun() {}
// fun 内:提升 var num;
// 代码执行流程:
// 全局 num = 10 → 调用 fun → 执行 console.log(num)(undefined)→ num = 20
案例 2:变量与函数同名
javascript
运行
console.log(a); // 函数提升优先,输出函数体:function a() { console.log(123); }
var a = 'hello';
function a() {
console.log(123);
}
console.log(a); // 代码执行阶段,变量赋值覆盖函数 → 输出 'hello'
// 预解析流程:
// 全局:提升 function a() {} → 提升 var a;(被函数覆盖)
// 代码执行流程:
// 第一个 console.log(a)(输出函数)→ a = 'hello' → 第二个 console.log(a)(输出 'hello')
案例 3:函数表达式的提升陷阱
javascript
运行
fn(); // 报错:fn 是变量提升(仅声明 undefined),不是函数提升,无法调用
var fn = function() {
console.log(11);
};
// 预解析流程:
// 全局:提升 var fn;(值为 undefined),函数表达式不参与提升
// 代码执行流程:
// 调用 fn(undefined,类型错误)→ 后续代码不执行
案例 4:隐式全局变量
javascript
运行
f1(); // 调用 f1
console.log(c); // 输出 9(c 是隐式全局变量)
console.log(b); // 输出 9(b 是隐式全局变量)
console.log(a); // 报错:a 是 f1 内局部变量,全局不可访问
function f1() {
var a = b = c = 9; // var a 是局部变量,b、c 无 var 声明,隐式全局
console.log(a); // 输出 9
console.log(b); // 输出 9
console.log(c); // 输出 9
}
// 预解析流程:
// 全局:提升 function f1() {}
// f1 内:提升 var a;
// 代码执行流程:
// 调用 f1 → a=9(局部)、b=9(全局)、c=9(全局)→ 全局 log(c=9)、log(b=9)、log(a)(报错)
案例 5:多层作用域预解析
javascript
运行
var a = 18;
f1(); // 调用 f1
function f1() {
var b = 9;
console.log(a); // 预解析:f1 内提升 var a,未赋值 → 输出 undefined
console.log(b); // 输出 9(f1 内局部变量)
var a = 13; // 代码执行阶段:a 赋值 13
}
// 预解析流程:
// 全局:提升 var a; 、function f1() {}
// f1 内:提升 var b; 、var a;
// 代码执行流程:
// 全局 a=18 → 调用 f1 → b=9 → log(a=undefined) → log(b=9) → a=13
(四)核心结论
- 预解析是 JS 执行的重要特性,需重点关注
var和function的提升差异,避免 “变量未定义” 或 “函数无法调用” 的问题。 - 推荐使用 ES6 的
let/const替代var,let/const无提升特性,且支持块级作用域,能规避多数预解析陷阱。 - 函数优先用声明式(
function fn() {}),避免函数表达式(var fn = function() {})的提升陷阱,确保函数可正常调用。
八、变量的生命周期【了解】
(一)核心规则
JavaScript 变量的生命周期始于声明,终于 “不再被需要”,具体销毁时机随作用域不同而差异:
- 局部变量:在函数执行结束后,函数私有作用域会被销毁,局部变量随作用域释放而自动删除,无法再访问。
- 全局变量:仅在页面关闭(或浏览器标签页关闭)时,全局作用域随页面销毁,全局变量才会被删除;页面运行期间,全局变量始终存在。
(二)经典问题案例:变量生命周期与事件绑定冲突
html
预览
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script>
var lis = document.getElementsByTagName("li");
// 问题:var 声明的 i 是全局变量,循环结束后 i 固定为 6
for(var i=0; i<lis.length;i++){
lis[i].onclick = function(){
alert(i); // 无论点击哪个 li,均弹出 6
}
}
</script>
原因:var 无块级作用域,i 是全局变量,循环结束后 i 取值为 lis.length(6);点击事件触发时,访问的是全局 i,而非循环时的瞬时值(变量生命周期未随循环迭代结束而终止)。
九、变量作用域与生命周期案例解析
通过具体案例理解变量作用域对生命周期的影响,明确不同场景下变量的可访问性:
| 案例序号 | 代码示例 | 执行结果 | 核心原因 |
|---|---|---|---|
| 案例一 |
|
报错(uname is not defined) |
uname 是函数内隐式全局变量,但 test() 未调用,uname 未声明,全局作用域中无此变量。 |
| 案例二 |
|
输出 香水有毒 |
test() 调用后,uname 隐式成为全局变量,页面未关闭前全局可访问,生命周期延续至页面结束。 |
| 案例三 |
|
输出 香水有毒 |
全局变量 uname 声明在前,test() 内通过作用域链访问全局 uname,符合 “内层可访问外层” 规则。 |
| 案例四 |
|
输出 老鼠爱大米 |
test2() 调用时修改全局 uname,test() 访问的是修改后的全局变量(全局变量生命周期未中断,值可被修改)。 |
| 案例五 |
|
输出 老鼠爱大米 |
test2() 返回 test 函数(未立即执行),res() 调用时,uname 已被 test2() 修改为全局值,全局变量生命周期覆盖函数调用全程。 |
十、自执行函数(匿名函数自调用)
(一)定义与核心作用
- 定义:定义后立即执行,且后续不再调用的函数,因无需重复调用,通常省略函数名(匿名函数),语法上通过特殊符号触发 “立即执行”。
- 核心作用:创建独立的局部作用域,保存变量的瞬时值,避免变量污染全局作用域(解决如 “li 点击事件索引异常” 等问题)。
(二)常见语法形式(6 种)
javascript
运行
// 1. 整个表达式用括号包裹(推荐,可读性高)
(function a(){ alert('我是自执行函数') } ());
// 2. 函数体用括号包裹
(function a(){ alert('我是自执行函数') }) ();
// 3. 逻辑运算符触发(!、+、-、~,利用运算符强制表达式执行)
!function a(){ alert('我是自执行函数') }();
+function a(){ alert('我是自执行函数') }();
-function a(){ alert('我是自执行函数') }();
~function a(){ alert('我是自执行函数') }();
// 4. void 关键字触发(忽略返回值,仅执行函数)
void function a(){ alert('我是自执行函数') }();
// 5. new 关键字触发(创建临时对象,执行函数)
new function a(){ alert('我是自执行函数') }();
(三)语法注意事项
- 分号必须性:若上一行代码未用分号结束,自执行函数前需加
;,避免被解析为上一行代码的延续(如函数调用),导致语法错误:javascript
运行
;(function(){ console.log('规范写法') })() // 推荐:前加分号,避免语法冲突
(四)核心应用:解决 li 点击事件索引问题
html
预览
<ul>
<li>这是第1个li</li>
<li>这是第2个li</li>
<li>这是第3个li</li>
<li>这是第4个li</li>
<li>这是第5个li</li>
<li>这是第6个li</li>
</ul>
<script>
var lis = document.querySelectorAll('li');
for(var i = 0;i<lis.length;i++){
// 自执行函数创建独立作用域,接收当前循环的 i 值(实参)
(function(i){
lis[i].onclick = function (){
console.log(i); // 点击第 n 个 li,输出 n-1(正确索引)
}
})(i) // 传入当前循环的 i,作为自执行函数的局部变量
}
</script>
原理:每次循环创建一个自执行函数,i 作为实参传入后成为函数局部变量,其生命周期被限制在自执行函数作用域内;点击事件访问的是局部 i(保存了循环瞬时值),而非全局 i,避免索引异常。
十一、闭包
(一)定义
闭包是 “能够读取其他函数内部变量的函数”,本质是连接函数内部与外部的桥梁。通常表现为 “函数内部嵌套的子函数”,且子函数被外部作用域引用(如返回给外部、赋值给全局变量)。
(二)核心特点
- 跨作用域访问:闭包(子函数)可访问外部函数的局部变量和参数,突破 “外层不可访问内层” 的作用域限制。
- 延长变量生命周期:外部函数执行结束后,其局部变量因被闭包引用,不会被垃圾回收机制回收,仍保存在内存中。
- 作用域依赖性:闭包的作用域链依赖于定义时的外部作用域,而非调用时的作用域(“词法作用域” 特性)。
(三)闭包的创建方式
方式 1:内部函数返回外部变量
javascript
运行
// 外部函数定义局部变量,内部函数返回该变量
function show() {
var num = 10; // 外部函数局部变量
return function() { // 闭包(内部函数)
return num; // 访问外部函数的 num
};
}
var closeFn = show(); // closeFn 引用闭包函数
console.log(closeFn()); // 输出 10(通过闭包访问外部函数局部变量)
方式 2:内部函数作为返回值调用
javascript
运行
function f1() {
var n = 999; // 外部函数局部变量
function f2() { // 闭包(内部函数)
alert(n); // 访问 f1 的局部变量 n
}
return f2; // 返回闭包函数
}
f1()(); // 调用闭包函数,输出 999(f1 执行后,n 仍被闭包引用,未被回收)
方式 3:普通函数改造为闭包
将 “内部函数仅在内部调用” 的普通函数,改为 “内部函数被外部引用” 的闭包,核心是让内部函数脱离外部函数作用域并被外部访问:
javascript
运行
// 普通函数(非闭包):内部函数仅在外部函数内调用
function show() {
var m = 1;
function inner() {
var n = 0;
console.log(++n);
console.log(++m);
};
inner(); // 内部函数仅在内部执行,m 随 show() 结束被回收
}
show(); // 输出 1、2;再次调用 show(),m 重新初始化为 1,输出 1、2
// 闭包函数:内部函数被外部引用
function show() {
var m = 1;
return function() { // 内部函数被返回给外部,成为闭包
var n = 0;
console.log(++n); // 每次调用输出 1(n 是闭包内局部变量,每次调用重新初始化)
console.log(++m); // 每次调用输出 2、3、4...(m 被闭包引用,生命周期延长)
};
}
var closeFn = show();
closeFn(); // 输出 1、2
closeFn(); // 输出 1、3
(四)闭包的用途
- 读取函数内部变量:突破作用域限制,在外部直接访问函数的局部变量(如上述案例中,通过闭包获取
show()内的m)。 - 保存变量瞬时状态:让变量值长期保存在内存中,实现 “状态记忆”(如累加计数器、记录函数调用次数)。
- 模块化开发:通过闭包创建私有变量和公有方法,实现 “数据封装”,避免全局变量污染(ES6 模块普及前的常用方案)。
(五)闭包的缺点与内存泄漏
1. 缺点
- 内存占用高:闭包引用的外部变量不会被回收,长期积累会占用大量内存,导致网页性能下降(如频繁创建闭包未释放)。
- 兼容性问题:在 IE 浏览器(尤其是 IE8 及以下)中,闭包若引用 DOM 元素,易形成 “循环引用”(闭包引用 DOM,DOM 引用闭包),导致内存泄漏。
2. 内存泄漏原理
- 浏览器的垃圾回收机制(GC) 会自动回收 “无引用指向” 的内存;
- 闭包引用外部函数的变量 → 外部函数作用域无法销毁 → 若闭包再被全局变量引用 → 相关内存始终被占用,无法被 GC 回收,形成内存泄漏。
3. 避免内存泄漏的方法
-
手动解除引用:不再使用闭包时,将闭包变量赋值为
null,切断引用链(如closeFn = null),让 GC 回收相关内存。
示例:javascript
运行
function createClosure() { var data = "大量数据"; // 被闭包引用的变量 return function() { console.log(data); }; } var closeFn = createClosure(); // 引用闭包 closeFn(); // 使用闭包:输出 "大量数据" // 不再使用时,手动解除引用 closeFn = null; // 切断闭包与外部的关联,GC 可回收 data 占用的内存 -
避免循环引用:不将 DOM 元素保存为闭包的局部变量,或在闭包执行后手动清除 DOM 引用。
若业务需在闭包中使用 DOM 元素,需避免 “闭包引用 DOM + DOM 引用闭包” 的循环关系,使用后及时清除 DOM 引用:javascript
运行
function bindEvent() { var btn = document.getElementById("btn"); // DOM 元素 var data = "按钮相关数据"; // 避免闭包直接引用 btn,或使用后清除引用 btn.onclick = function() { console.log(data); // 仅引用数据,不引用 DOM }; // 或在事件执行后手动清除 DOM 引用(若闭包需保留) // btn.onclick = function() { // console.log(data, btn); // btn.onclick = null; // 清除事件绑定,切断 DOM 与闭包的关联 // btn = null; // 清除 DOM 引用 // }; } bindEvent(); -
控制闭包创建频率:避免在循环、高频事件(如
resize、scroll)中频繁创建闭包,减少内存占用。若需多次使用,可将闭包缓存复用,而非重复创建:javascript
运行
// 不推荐:循环中频繁创建闭包 var lis = document.querySelectorAll("li"); for (var i = 0; i < lis.length; i++) { (function(i) { lis[i].onclick = function() { console.log(i); }; })(i); } // 优化:复用闭包(或用 let 块级作用域替代) function createClickHandler(index) { // 单个闭包模板,复用逻辑 return function() { console.log(index); }; } for (var i = 0; i < lis.length; i++) { lis[i].onclick = createClickHandler(i); // 复用闭包函数,减少内存占用 } -
使用块级作用域替代部分闭包场景:ES6 引入的
let/const支持块级作用域,可替代闭包实现 “保存变量瞬时值” 的需求,且无内存泄漏风险。例如解决 li 点击索引问题:javascript
运行
var lis = document.querySelectorAll("li"); // let 声明 i,形成块级作用域,每次循环的 i 是独立变量 for (let i = 0; i < lis.length; i++) { lis[i].onclick = function() { console.log(i); // 点击对应 li,输出正确索引,无需闭包 }; }
十二、函数封装经典案例
(一)封装数组反转方法
通过两种思路实现数组反转,兼容任意类型数组元素(数字、字符串、对象等):
方法一:正向遍历 + 反向赋值
javascript
运行
function reverseArray(arr) {
// 校验参数:确保输入是数组
if (!Array.isArray(arr)) {
throw new Error("参数必须是数组");
}
var newArr = [];
// 正向遍历原数组,将元素赋值到新数组的反向索引
for (var i = 0; i < arr.length; i++) {
newArr[arr.length - i - 1] = arr[i];
}
return newArr;
}
// 测试:反转数字数组
var numArr = reverseArray([1, 4, 7, 9, 12, 16]);
console.log(numArr); // 输出 [16, 12, 9, 7, 4, 1]
// 测试:反转字符串数组
var strArr = reverseArray(["a", "b", "c"]);
console.log(strArr); // 输出 ["c", "b", "a"]
方法二:反向遍历 + 正向赋值
javascript
运行
function reverseArray(arr) {
if (!Array.isArray(arr)) {
throw new Error("参数必须是数组");
}
var newArr = [];
// 反向遍历原数组,按顺序添加到新数组
for (var i = arr.length - 1; i >= 0; i--) {
newArr[newArr.length] = arr[i]; // newArr.length 动态增长,无需计算索引
}
return newArr;
}
var testArr = reverseArray([{ name: "A" }, { name: "B" }]);
console.log(testArr); // 输出 [{ name: "B" }, { name: "A" }](对象数组也可反转)
(二)封装冒泡排序方法
冒泡排序核心逻辑:重复遍历数组,相邻元素两两比较,逆序则交换,逐步将最大元素 “冒泡” 到数组末尾:
javascript
运行
function bubbleSort(arr) {
if (!Array.isArray(arr)) {
throw new Error("参数必须是数组");
}
// 外层循环:控制排序轮次(n 个元素需 n-1 轮,已排序元素无需再比较)
for (var i = 0; i < arr.length - 1; i++) {
// 内层循环:每轮比较次数(每轮减少 i 次,排除已排序的末尾元素)
for (var j = 0; j < arr.length - i - 1; j++) {
// 数值比较:若前元素 > 后元素,交换位置(升序排序)
if (arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr; // 返回排序后的数组(原数组也被修改,若需不修改原数组,可先复制数组)
}
// 测试:排序无序数组
var unsortedArr = [40, 8, 15, 18, 12];
var sortedArr = bubbleSort(unsortedArr);
console.log(sortedArr); // 输出 [8, 12, 15, 18, 40]
优化:添加 “已排序” 判断
若数组在某轮遍历中无交换,说明数组已有序,可提前终止循环,减少性能消耗:
javascript
运行
function bubbleSortOptimized(arr) {
if (!Array.isArray(arr)) {
throw new Error("参数必须是数组");
}
var isSorted = false; // 标记数组是否已有序
for (var i = 0; i < arr.length - 1 && !isSorted; i++) {
isSorted = true; // 假设本轮已有序
for (var j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
isSorted = false; // 发生交换,数组未有序
}
}
}
return arr;
}
// 测试:已部分有序的数组
var almostSortedArr = [2, 3, 1, 4, 5];
console.log(bubbleSortOptimized(almostSortedArr)); // 输出 [1, 2, 3, 4, 5],轮次减少
(三)封装平年 / 闰年判断函数
根据闰年规则(能被 4 整除且不能被 100 整除,或能被 400 整除)实现判断,并扩展 “获取某年 2 月天数” 功能:
1. 判断平年 / 闰年
javascript
运行
function isLeapYear(year) {
// 校验参数:确保输入是有效年份(整数)
if (typeof year !== "number" || !Number.isInteger(year) || year < 0) {
throw new Error("参数必须是正整数年份");
}
// 闰年规则:(能被4整除且不能被100整除) 或 能被400整除
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
// 测试:典型年份
console.log(isLeapYear(2000)); // 输出 true(能被400整除,闰年)
console.log(isLeapYear(2020)); // 输出 true(能被4整除且不能被100整除,闰年)
console.log(isLeapYear(2023)); // 输出 false(平年)
console.log(isLeapYear(1900)); // 输出 false(能被100整除但不能被400整除,平年)
2. 扩展:获取某年 2 月天数
javascript
运行
function getFebruaryDays(year) {
// 复用闰年判断函数:闰年 29 天,平年 28 天
return isLeapYear(year) ? 29 : 28;
}
// 测试
console.log(getFebruaryDays(2020)); // 输出 29(闰年)
console.log(getFebruaryDays(2023)); // 输出 28(平年)
(四)封装数组最大值获取函数
遍历数组,通过 “假设最大值” 逐步比较更新,获取数组中的最大值(兼容纯数字数组):
javascript
运行
function getArrayMax(arr) {
// 校验参数:确保输入是数组且非空
if (!Array.isArray(arr) || arr.length === 0) {
throw new Error("参数必须是非空数组");
}
// 校验数组元素:确保所有元素是数字
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] !== "number" || isNaN(arr[i])) {
throw new Error("数组元素必须是有效数字");
}
}
var max = arr[0]; // 假设第一个元素是最大值
// 遍历数组,更新最大值
for (var i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
}
// 测试
var numArr = [1, 34, 96, 23, 78];
console.log(getArrayMax(numArr)); // 输出 96
var negativeArr = [-5, -2, -8, 0];
console.log(getArrayMax(negativeArr)); // 输出 0(支持负数数组)
更多推荐



所有评论(0)