一、函数的概念
在 JavaScript 编程中,常常会出现大量重复或功能相似的代码片段。为了提高代码的复用性与可维护性,函数应运而生。函数本质上是一段封装好的可重复调用执行的代码块,通过它能够实现特定功能的复用。当使用typeof操作符检查一个函数对象时,会返回function。例如著名的 jQuery 库,它将众多功能封装成函数,对外仅暴露一个顶级对象$,这便是函数封装的典型应用。可以将函数的封装理解为把诸多衣物规整地打包到行李箱中,方便随时取用。
二、函数的使用
(一)声明函数
- 构造函数方式:使用构造函数创建函数对象,即new Function()。此方法将封装的代码以字符串形式传递给构造函数,但在实际开发中较少使用。因为通过构造函数创建函数,会导致代码被解析两次,影响性能。第一次解析常规 JavaScript 代码,第二次解析传入构造函数的字符串。例如:
var fun = new Function("console.log('hello 这是我的第一个函数')"); console.log(typeof fun); // function fun(); |
- 函数声明方式:这是最常用的声明函数的方法,语法为function 函数名([形参1,形参2,形参3,形参4....形参n]){执行语句.....}。函数声明会被 JavaScript 编译器提升到作用域的最前面,因此无论在代码的何处调用该函数,都能正常执行。例如:
function fun2() { console.log("这是我的第二个函数~~~"); alert("hahahaha"); document.write("wuwuwuwu"); } |
- 函数表达式方式:也叫匿名函数,语法为var 变量名 = function([形参1,形参2,形参3,形参4....形参n]){语句.....}。需要注意的是,变量名并非真正的函数名。与函数声明不同,函数表达式必须在定义之后才能调用,因为在解析到函数表达式之前,变量仅被声明为undefined。例如:
var fun3 = function () { console.log("我是匿名函数中的封装的代码"); }; // 相当于赋值语句 fun3(); // 调用函数 |
由于函数通常用于实现特定功能,所以函数名一般使用动词命名,例如getSum,这样能使代码的意图更加清晰。
(二)调用函数
调用函数的语法为变量名/函数名()。当函数被调用时,其中封装的代码会按照顺序依次执行。例如,利用函数计算 1 - 100 之间的累加和:
function getSum() { var sum = 0; for (var i = 1; i <= 100; i++) { sum += i; } return sum; } var result = getSum(); console.log(result); |
三、函数的参数
(一)参数的基本概念
在声明函数时,函数名后的小括号内定义的参数为形参,调用函数时传递的参数则是实参。形参在函数定义阶段仅是占位符,用于接收实参的值,类似于声明但未赋值的变量,多个形参之间用逗号分隔。实参则是在函数调用时实际传递给形参的值,相当于给形参赋值。参数的作用在于,当函数内部某些值不确定时,可通过传递不同的实参来实现函数功能的多样化。例如:
function addNumbers(num1, num2) { return num1 + num2; } var sum = addNumbers(3, 5); console.log(sum); |
(二)形参和实参匹配问题
- 个数一致:当形参和实参的个数相等时,函数能正常输出结果。
- 实参多于形参:若实参的个数多于形参,函数仅会取用与形参个数对应的实参值,多余的实参将被忽略。
- 实参少于形参:当实参个数少于形参时,未被赋值的形参默认被定义为undefined。
调用函数时,JavaScript 解析器不会检查实参的类型,因此需要注意可能接收到非法参数的情况。为保证函数的稳定性和可预测性,建议形参和实参的个数保持一致。
(三)实参的类型
实参可以是任意数据类型,包括对象和函数。当参数较多时,将参数封装到对象中传递是一种良好的实践方式。例如:
function printInfo(person) { console.log(`姓名:${person.name},年龄:${person.age},性别:${person.gender}`); } var personObj = { name: "张三", age: 25, gender: "男" }; printInfo(personObj); |
实参也可以是一个函数,例如:
function executeFunction(func) { func(); } function sayHello() { console.log("Hello!"); } executeFunction(sayHello); |
四、函数的返回值 return
(一)返回值语法结构
函数通过return语句返回结果,语法为function 函数名(){return 需要返回的结果}。例如,输入一个数,求 1 到该数的和,并在此基础上进行简单运算:
var num = +prompt('输入一个数'); function getSum(num) { var sum = 0; for (var i = 1; i <= num; i++) { sum += i; } console.log(sum); return sum; } var res = getSum(num); console.log(res + 1); |
(二)返回值注意事项
- 结果反馈:函数实现特定功能后,通过return将结果返回给调用者,即函数名() = return后面的结果。也可定义变量接收该返回值。
- 终止函数:函数执行到return语句后,后续代码将不再执行,return具有终止函数的作用。
- 单一值返回:return只能返回一个值,若有多个值,仅返回最后一个。如需返回多个值,可将其放入数组或对象中。
- 返回值类型:return后可跟任意类型的值。若return后不跟任何值,相当于返回undefined;若函数中未写return,同样返回undefined。例如:
function sum(a, b, c) { var d = a + b + c; return d; } var result = sum(4, 7, 8); console.log("result=" + result); |
(三)返回值类型
返回值可以是任意数据类型,包括对象和函数。例如返回值为对象:
function fun2() { var obj = { name: "沙和尚" }; return obj; } var a = fun2(); console.log("a=" + a); |
返回值为函数:
function fun3() { function fun4() { alert("我是fun4"); } return fun4; } var a = fun3(); a(); |
(四)break、continue、return的区别
- break:用于结束当前循环体(如for、while循环)。
- continue:跳出本次循环,继续执行下次循环。
- return:不仅能退出循环,还可返回return语句中的值,并结束当前函数的执行。例如:
function fun() { alert("函数要执行了~~~"); for (var i = 0; i < 5; i++) { if (i == 2) { break; // 退出当前的循环 continue; // 用于跳过当次循环 return; // 使用return可以结束整个函数 } console.log(i); } alert("函数执行完了~~~"); } // fun(); |
五、arguments的使用
在调用函数时,浏览器会自动传递两个隐含参数:函数的上下文对象this和封装实参的对象arguments。当不确定传递参数的个数时,可借助arguments获取所有实参。arguments是当前函数的内置对象,所有函数都具备,它存储了调用函数时传递的所有实参。
(一)arguments的特性
- 伪类数组性质:arguments是一个伪类数组对象,它具有数组的length属性,可通过索引操作数据,但不具备真正数组的一些方法,如push、pop等。
- 实参存储:调用函数时传递的所有实参都保存在arguments中。
- 访问实参:即使函数未定义形参,也能通过arguments访问实参,arguments[0]表示第一个实参,arguments[1]表示第二个实参,以此类推。
- arguments.callee:arguments对象有一个callee属性,它指向当前正在执行的函数对象。例如:
function fun() { console.log(arguments.callee); } fun(); |
例如,利用函数求任意个数的最大值:
function getMax() { var max = arguments[0]; for (var i = 0; i < arguments.length; i++) { if (max < arguments[i]) { max = arguments[i]; } } return max; } var num = getMax(1, 2, 3, 33, 111); console.log(num); |
六、函数方法call(),apply() ,bind()
call()、apply()和bind()均为函数对象的方法,需通过函数对象调用。调用call()和apply()方法时,函数会立即执行,并且可将一个对象指定为第一个参数,该对象将成为函数执行时的this。
- call()方法:可在指定对象之后依次传递实参。例如:
var name = "我是window"; function fun(a) { console.log("a=", a); alert(this.name); } var obj = { name: "我是obj1", sayName: function () { alert(this.name); } }; var obj2 = { name: "我是obj2" }; fun.call(obj, 2); |
- apply()方法:需要将实参封装到一个数组中统一传递。例如:
fun.apply(obj2, [2, 3]); |
- bind()方法:bind()方法与call()和apply()不同,它不会立即执行函数,而是返回一个新的函数,新函数的this被绑定为指定的对象。例如:
function greet() { console.log(`Hello, ${this.name}`); } var obj = { name: "Alice" }; var boundGreet = greet.bind(obj); boundGreet(); |
总结this的指向情况:
- 以函数形式调用时,this永远指向window(在严格模式下为undefined)。
- 以方法的形式调用时,this是调用方法的对象。
- 以构造函数的形式调用时,this是新创建的实例对象。
- 使用call和apply调用时,this是指定的对象;若不写第一个参数,默认指向window(严格模式下为undefined)。
七、函数调用函数
每个函数都是独立的代码块,用于完成特定任务,因此函数相互调用的情况十分常见。一般而言,一个函数应专注于完成一件事,以提高代码的可读性和可维护性。例如:
function fn1() { console.log(111); fn2(); console.log('fn1'); } function fn2() { console.log(222); console.log('fn2'); } fn1(); |
八、立即执行函数
立即执行函数的语法结构为(function() { /* 函数体 */ })(); 或 function() { /* 函数体 */ }(); 。函数定义完成后立即被调用,且通常只会执行一次。例如:
// 第一种写法 (function () { alert("我是一个匿名函数~~~"); })(); // 第二种写法 (function () { alert('匿名函数执行方式二'); }()); // 匿名函数传参 (function (a, b) { console.log("a=" + a); console.log("b=" + b); })(123, 456); |
立即执行函数常用于创建独立的作用域,避免变量污染全局作用域,同时可用于初始化一些一次性执行的代码逻辑。