JavaScript ES5 函数全解析:从基础到高级应用
函数是 JavaScript(ES5)的核心语法单元,不仅承担代码复用功能,更是实现作用域隔离、异步编程和模块化的基础。
一、函数核心概念与专有名词解析
| 专有名词 | 定义与说明 | 
|---|---|
| 函数声明(Function Declaration) | 通过 function 关键字显式定义函数名的方式,支持 “函数提升”,可在声明前调用。 | 
| 函数表达式(Function Expression) | 将匿名 / 命名函数赋值给变量的方式,不支持函数提升,需赋值后调用。 | 
| arguments 对象 | 函数内部的类数组对象(具有 length 属性但无数组方法),存储所有传入的实参。 | 
| this 绑定 | 函数执行时的 “上下文对象”,指向由调用方式决定(非定义位置),ES5 无箭头函数绑定规则。 | 
| IIFE(立即执行函数) | 全称 Immediately Invoked Function Expression,声明后立即执行,用于创建独立作用域。 | 
| 作用域链(Scope Chain) | 函数嵌套时形成的变量查找链,从当前函数作用域向上遍历至全局作用域。 | 
| 闭包(Closure) | 有权访问另一个函数作用域中变量的函数,本质是作用域链的 “延长保留”。 | 
| 回调函数(Callback) | 作为参数传递给另一个函数的函数,在特定时机(如异步操作完成)被调用。 | 
二、函数定义与声明:两种方式的细节对比
函数有 “函数声明” 和 “函数表达式” 两种定义方式,二者在提升特性、作用域表现上存在关键差异,需结合实例理解。
2.1 函数声明(支持函数提升)
语法与实例
javascript
// 函数声明:function 关键字 + 函数名 + 参数列表 + 函数体
function calculateSum(a, b) {// 函数体:实现求和逻辑return a + b;
}// 关键特性:函数提升(声明前调用仍可执行)
console.log(calculateSum(3, 5)); // 输出:8(正常执行,无报错)
专有名词补充:函数提升(Function Hoisting)
ES5 解析代码时,会将函数声明的 “函数名 + 函数体” 整体提升至当前作用域顶部,因此可在声明前调用。但需注意:仅函数声明支持提升,函数表达式不支持。
2.2 函数表达式(不支持函数提升)
匿名函数表达式即 “无函数名的函数”,赋值后通过变量名调用,是回调函数、一次性逻辑封装的常用形式。
javascript
// 1. 声明变量 fn,赋值为一个匿名函数(函数表达式的核心语法)
// 右侧 function(){} 是匿名函数,无函数名,作为值赋值给 fn
var fn = function(a, b) {// 函数内部逻辑:计算两数之和// 内部可访问参数 a、b,以及外部作用域的变量(后续详解)var sum = a + b;console.log("匿名函数内部:a + b =", sum); // 输出计算结果return sum; // 返回结果给外部调用者
};// 2. 调用函数表达式:通过变量名 fn 调用,传入实参
// 外部通过变量名触发函数执行,获取返回值
var result = fn(3, 5);
console.log("外部访问函数返回值:", result); // 输出:外部访问函数返回值:8// 关键注意:匿名函数本身无名字,无法在内部通过函数名递归(需用 arguments.callee)
var factorial = function(n) {if (n === 1) return 1;// 匿名函数内部无法通过“函数名”递归,需用 arguments.callee 指向当前函数return n * arguments.callee(n - 1);
};
console.log("匿名函数递归(阶乘):", factorial(5)); // 输出:120
命名函数表达式(带函数名)
命名函数表达式是 “给函数本身指定名字的表达式”,函数名仅在函数内部可用,外部仍通过变量名调用,适用于需要在函数内部递归、调试(函数名会显示在调用栈中)的场景。
javascript
// 1. 声明变量 calculate,赋值为一个“带名字的函数”(函数名:multiply)
// 右侧 function multiply(a, b){} 是命名函数,函数名 multiply 仅内部可用
var calculate = function multiply(a, b) {// 函数内部可通过函数名 multiply 访问自身(用于递归)console.log("命名函数内部:函数名 =", multiply.name); // 输出:命名函数内部:函数名 = multiplyreturn a * b;
};// 2. 外部调用:只能通过变量名 calculate 调用,无法直接用 multiply
console.log("外部调用命名函数表达式:", calculate(4, 6)); // 输出:24
// console.log(multiply(4, 6)); // 报错:multiply is not defined(外部无法访问函数内部的名字)// 3. 命名函数表达式的核心优势:内部递归更清晰(无需依赖 arguments.callee)
var fibonacci = function fib(n) {if (n <= 2) return 1;// 内部通过函数名 fib 递归,语义更清晰,避免 arguments.callee 的歧义return fib(n - 1) + fib(n - 2);
};
console.log("命名函数递归(斐波那契):", fibonacci(6)); // 输出:8(1,1,2,3,5,8)
函数表达式的作用域访问规则:内部与外部能否互相访问?
函数表达式的 “内外访问权限” 由 ES5 作用域规则决定:ES5 仅支持 “全局作用域” 和 “函数作用域”,无块级作用域,访问权限遵循 “内层可访问外层,外层不可访问内层” 的原则。
核心规则总结
| 访问方向 | 是否允许 | 底层逻辑 | 
|---|---|---|
| 函数内部访问外部变量 | 允许 | 作用域链规则:内部作用域会向上遍历外层作用域,直到找到变量或到达全局作用域。 | 
| 函数外部访问内部变量 | 不允许 | 函数作用域隔离:内部变量仅在函数内部生效,外部无访问路径。 | 
语法与实例(匿名 / 命名两种形式)
javascript
// 1. 匿名函数表达式:无函数名,赋值给变量
var calculateMultiply = function(a, b) {return a * b;
};// 2. 命名函数表达式:函数名仅在内部可用(用于递归或调试)
var calculateDivide = function divide(a, b) {console.log(divide.name); // 输出:divide(内部访问函数名)if (b === 0) return "除数不能为0";return a / b;
};// 关键特性:无函数提升(声明前调用会报错)
// console.log(calculateMultiply(4, 6)); // 报错:calculateMultiply is not a function(若放在赋值前)
console.log(calculateMultiply(4, 6)); // 输出:24(赋值后调用正常)
console.log(calculateDivide(10, 2)); // 输出:5
两种定义方式的核心差异
| 对比维度 | 函数声明 | 函数表达式 | 
|---|---|---|
| 提升支持 | 支持(整体提升至作用域顶部) | 不支持(变量提升但值为 undefined) | 
| 函数名 | 必须显式定义 | 可选(匿名 / 命名,命名仅内部可用) | 
| 适用场景 | 独立函数(如工具函数) | 赋值给变量、作为参数(如回调函数) | 
三、函数内部核心属性:arguments 与 this
函数内部有两个特殊属性:arguments(处理参数)和 this(处理上下文),二者是实现灵活逻辑的关键。
3.1 arguments 对象:类数组的参数集合
定义与特性
arguments 是函数内部的类数组对象,存储所有传入的实参,支持:
- 通过索引访问参数(如 
arguments[0]获取第一个实参); length属性获取实参个数;callee属性指向当前函数(用于匿名函数递归)。
实践实例
javascript
// 实例1:处理不确定个数的参数(求和)
function sum() {var total = 0;// 遍历 arguments(类数组,需用 for 循环,无 forEach 方法)for (var i = 0; i < arguments.length; i++) {// 过滤非数字参数(增强鲁棒性)if (typeof arguments[i] === "number") {total += arguments[i];}}return total;
}console.log(sum(1, 2, 3)); // 输出:6
console.log(sum(10, 20, "a", 30)); // 输出:60(忽略非数字参数)// 实例2:arguments.callee 实现匿名函数递归(计算阶乘)
var factorial = function(n) {if (n === 1) return 1;// callee 指向当前函数,避免依赖外部变量名return n * arguments.callee(n - 1);
};console.log(factorial(5)); // 输出:120(5! = 5*4*3*2*1)
3.2 this 绑定:动态上下文对象
定义与核心规则
this 是函数执行时的 “上下文对象”,其指向由调用方式决定,与函数定义位置无关,ES5 中常见绑定场景如下:
实践实例(4 种核心绑定场景)
javascript
运行
// 场景1:全局调用 -> this 指向全局对象(浏览器:window,Node:global)
function getGlobalContext() {console.log(this === window); // 浏览器环境输出:trueconsole.log(this === global); // Node 环境输出:true
}
getGlobalContext();// 场景2:对象方法调用 -> this 指向调用对象
var user = {name: "张三",age: 25,introduce: function() {return `我是${this.name},今年${this.age}岁`;}
};
console.log(user.introduce()); // 输出:我是张三,今年25岁// 场景3:call/apply 强制绑定 -> this 指向指定对象
var anotherUser = { name: "李四", age: 30 };
// call:参数列表传递(逗号分隔)
console.log(user.introduce.call(anotherUser)); // 输出:我是李四,今年30岁
// apply:参数数组传递
console.log(user.introduce.apply(anotherUser)); // 输出:我是李四,今年30岁// 场景4:bind 延迟绑定 -> 返回 this 绑定后的新函数
var boundIntroduce = user.introduce.bind(anotherUser);
setTimeout(boundIntroduce, 1000); // 1秒后输出:我是李四,今年30岁
关键提醒
- 避免在全局作用域滥用 
this(易导致变量污染); - 函数作为回调时(如定时器、数组 
forEach),this默认指向全局对象(需通过bind手动绑定)。 
四、立即执行函数(IIFE):作用域隔离的核心工具
IIFE 的核心作用是 “创建独立作用域,避免变量污染全局”,尤其适用于模块化开发和循环变量隔离。
4.1 IIFE 语法与原理
核心语法(两种常用形式)
javascript
// 形式1:外层括号包裹函数体 + 执行括号(推荐,语义清晰)
(function(a, b) {console.log("IIFE 形式1:" + (a + b)); // 输出:IIFE 形式1:15
})(10, 5);// 形式2:函数体括号 + 执行括号(省略外层括号,利用运算符优先级)
(function(a, b) {console.log("IIFE 形式2:" + (a * b)); // 输出:IIFE 形式2:50
}(25, 2));// 简化形式(利用一元运算符,如 !、+、-,省略外层括号)
!function(a) {console.log("IIFE 简化形式:" + a); // 输出:IIFE 简化形式:8
}(8);
原理:为什么需要括号?
ES5 解析器会将以 function 开头的语句视为 “函数声明”,而函数声明不能直接调用(需通过函数名)。通过括号包裹,可将其转为 “函数表达式”,从而支持立即执行。
4.2 IIFE 核心应用:解决循环变量污染
问题场景(未用 IIFE 的缺陷)
javascript
// 问题:循环中 var 声明的变量共享,导致定时器输出异常
for (var i = 0; i < 3; i++) {setTimeout(function() {console.log("未用 IIFE:" + i); // 输出:3 3 3(循环结束后 i 已变为 3)}, 100);
}
解决方案(IIFE 隔离作用域)
javascript
// 方案:通过 IIFE 将每次迭代的 i 传入独立作用域
for (var i = 0; i < 3; i++) {(function(j) {// j 是 IIFE 的参数,每次迭代独立保存 i 的当前值setTimeout(function() {console.log("用 IIFE:" + j); // 输出:0 1 2(正常输出每次迭代的 i)}, 100);})(i); // 将当前 i 作为实参传入
}
五、闭包(Closure):函数与作用域的进阶融合
闭包定义为 “有权访问另一个函数作用域中变量的函数”,其本质是作用域链的延长保留,是 ES5 中实现 “状态保存” 和 “模块化” 的核心手段。
5.1 闭包的创建与底层原理
核心条件(满足以下 3 点即形成闭包)
- 函数嵌套(内层函数嵌套在外层函数中);
 - 内层函数引用外层函数的变量 / 参数;
 - 外层函数返回内层函数(或内层函数被外部引用)。
 
实践实例(计数器:闭包保存状态)
javascript
// 外层函数:定义局部变量 count
function createCounter() {var count = 0; // 被闭包引用的外层变量,不会被垃圾回收var step = 1; // 额外参数,演示多变量保存// 内层函数:引用 count 和 step,形成闭包return {increment: function() {count += step;return count;},setStep: function(newStep) {step = newStep; // 可通过闭包修改外层变量}};
}// 调用外层函数,获取闭包对象(保存 count 和 step 的状态)
var counter1 = createCounter();
var counter2 = createCounter(); // 独立闭包,状态不共享// 操作 counter1
console.log(counter1.increment()); // 输出:1(count=1,step=1)
counter1.setStep(2); // 修改 step 为 2
console.log(counter1.increment()); // 输出:3(count=1+2=3)// 操作 counter2(与 counter1 状态独立)
console.log(counter2.increment()); // 输出:1(count=1,step=1)
原理补充:垃圾回收机制
正常情况下,函数执行完后,其局部变量会被垃圾回收(释放内存)。但闭包中,由于内层函数引用外层变量,外层函数的作用域会被保留,变量不会被回收,从而实现 “状态持久化”。
5.2 闭包的核心应用场景
场景 1:实现私有变量(模块化基础)
javascript
// 模块:通过闭包隐藏内部变量,仅暴露指定方法
var userModule = (function() {// 私有变量:仅内部可访问,外部无法直接修改var privateName = "张三";var privateAge = 25;// 暴露的公共方法(闭包,引用私有变量)return {getName: function() {return privateName;},setAge: function(newAge) {// 可在方法中添加校验逻辑,保证数据安全if (typeof newAge === "number" && newAge > 0) {privateAge = newAge;}return privateAge;},getInfo: function() {return `姓名:${privateName},年龄:${privateAge}`;}};
})();// 外部访问:只能通过暴露的方法操作,无法直接修改私有变量
console.log(userModule.getName()); // 输出:张三
console.log(userModule.setAge(28)); // 输出:28
console.log(userModule.getInfo()); // 输出:姓名:张三,年龄:28
console.log(userModule.privateName); // 输出:undefined(私有变量不可直接访问)
场景 2:异步操作中的变量绑定
javascript
// 场景:异步请求中保留请求参数(如 AJAX 回调)
function fetchData(url) {// 模拟 AJAX 异步请求setTimeout(function() {// 闭包引用外层变量 url,即使 fetchData 执行完,url 仍被保留console.log(`请求地址:${url},模拟返回数据成功`);}, 1000);
}fetchData("https://api.example.com/user"); // 1秒后输出:请求地址:https://api.example.com/user,模拟返回数据成功
fetchData("https://api.example.com/order"); // 1秒后输出:请求地址:https://api.example.com/order,模拟返回数据成功
5.3 闭包的注意事项
- 内存占用问题:闭包会保留外层作用域,滥用可能导致内存泄漏(如 DOM 事件绑定后未解绑),建议不用时手动解除引用(如 
counter = null); - 变量共享问题:若闭包引用循环变量,需通过 IIFE 隔离(参考前文循环变量示例);
 - this 指向问题:闭包中的 
this默认指向全局对象,需通过bind或变量保存(如var self = this)。 
六、函数调用的进阶:回调函数与异步编程
“函数可作为返回值或参数”,其中 “作为参数” 即回调函数,是 ES5 中实现异步编程的核心方式(如定时器、AJAX、事件绑定)。
6.1 回调函数的定义与分类
核心定义
回调函数是 “作为参数传递给另一个函数的函数”,在被调用函数执行到特定时机时触发,分为同步回调和异步回调。
实践实例
javascript
// 1. 同步回调(立即执行,如数组遍历)
var numbers = [1, 2, 3, 4];
// forEach 的参数是同步回调,遍历完成后立即执行
numbers.forEach(function(item, index) {console.log(`索引${index}:${item * 2}`); // 输出:索引0:2 索引1:4 索引2:6 索引3:8
});// 2. 异步回调(延迟执行,如定时器)
function loadData(callback) {console.log("开始加载数据...");// 模拟异步操作(如 AJAX 请求)setTimeout(function() {var data = { name: "李四", age: 30 };callback(data); // 异步完成后调用回调,传递数据}, 1500);
}// 调用函数并传入回调
loadData(function(result) {console.log("加载完成,数据:", result); // 1.5秒后输出:加载完成,数据:{ name: '李四', age: 30 }
});
console.log("加载请求已发送(不阻塞后续代码)"); // 先输出:加载请求已发送(不阻塞后续代码)
6.2 回调函数的常见应用
- DOM 事件绑定:如 
button.onclick = function() {}; - AJAX 请求:如 
XMLHttpRequest的onreadystatechange事件; - 定时器:如 
setTimeout、setInterval; - 工具函数:如 
Array.prototype.map、filter、reduce。 
七、补充:函数的其他关键细节
7.1 函数的 length 属性
 “function.length 表示函数希望接受的命名参数个数”,即形参个数(不包含剩余参数)。
javascript
function add(a, b) {return a + b;
}function multiply(a, b, c) {return a * b * c;
}console.log(add.length); // 输出:2(形参个数为 2)
console.log(multiply.length); // 输出:3(形参个数为 3)
7.2 函数作为对象(Function 类型)
ES5 中函数是 Function 类型的实例,因此具有属性和方法(如 name、call、apply、bind)。
javascript
function greet(name) {return `Hello, ${name}`;
}console.log(greet instanceof Function); // 输出:true(函数是 Function 实例)
console.log(greet.name); // 输出:greet(函数名属性)
console.log(greet.toString()); // 输出函数的源代码:function greet(name) { return `Hello, ${name}`; }
总结
ES5 函数的核心知识点可归纳为 “三大基础 + 两大进阶”:
- 三大基础:函数定义(声明 / 表达式)、内部属性(arguments/this)、调用方式(普通 / 方法 /call/apply/bind);
 - 两大进阶:IIFE(作用域隔离)、闭包(状态保存与模块化)。
 
函数是 JavaScript 编程的 “基石”,掌握以上内容不仅能应对 ES5 环境的开发需求,也能为理解 ES6+ 的箭头函数、解构赋值等特性奠定基础。建议结合实例反复调试,重点突破 this 绑定和闭包的细节~
