写在前面
大家好,我是一溪风月🤖,一名前端工程师,在JavaScript的世界里,函数是极为关键的存在,它就像是程序的"瑞士军刀",能实现各种各样的功能。无论是简单的弹窗提示,还是复杂的算法实现,函数都发挥着重要作用。今天,就让我们一起深入探索JavaScript函数的奥秘。
一.那些编程里的"神秘名词":foo、bar、baz
在编程学习过程中,大家可能经常看到foo、bar、baz这些词,它们常被用作函数、变量或文件的名称 。虽然这些词本身没有特别含义,被称为"伪变量",但在编程领域却十分常用。关于它们的由来有多种说法,比如有人认为是通过Digital公司的手册说明流行起来的;也有人说源自电子学中的反转foo信号;还有一种说法是foo出现在漫画中,代表"好运",和中文"福"读音相似。了解这些有趣的背景知识,也能为我们的编程学习增添一些乐趣。
二.函数初相识:定义与使用
(一)函数是什么
函数是对某段代码的封装,用于实现特定功能。我们在日常编程中已经接触过不少函数,像alert
函数,它能在浏览器中弹出一个弹窗,用于提示用户信息;prompt
函数,可以在弹窗里接收用户输入;console.log
函数,能在控制台输出内容;还有String
、Number
、Boolean
等函数。这些函数都是JavaScript引擎或浏览器为我们提供的"工具",帮助我们完成各种任务。当然,我们也可以自己编写函数,满足特定的业务需求。
(二)函数使用的两步曲:声明与调用
使用函数主要有两个步骤:声明函数和调用函数。声明函数就是把实现特定功能的代码封装起来,而调用函数则是让这些封装好的功能发挥作用。
- 声明函数 :在JavaScript中,声明函数使用
function
关键字,语法如下:
js
function 函数名() {
// 函数封装的代码
// 这里可以写实现具体功能的代码
}
需要注意的是,函数名的命名规则和变量名一致,要尽量做到见名知意,并且因为函数通常表示某种行为,所以使用动词会更合适。比如,定义一个打印个人信息的函数,可以命名为printPersonInfo
。另外,函数定义完后,里面的代码并不会立即执行,必须通过调用函数才能执行。
-
调用函数 :调用函数非常简单,直接使用函数名加上一对小括号
()
即可,例如定义了一个test
函数,调用它就用test()
。下面通过两个练习来加深理解:- 练习一:定义一个函数,打印一个人的个人信息
js
function printPersonInfo() {
let name = "张三";
let age = 20;
console.log(`姓名:${name},年龄:${age}`);
}
printPersonInfo();
- 练习二:定义一个函数,函数中计算10和20数字的和,并且打印出结果
js
function calculateSum() {
let num1 = 10;
let num2 = 20;
let sum = num1 + num2;
console.log(`10和20的和是:${sum}`);
}
calculateSum();
三.函数的参数
函数的参数能让函数更加通用,对于相同的数据处理逻辑,可以适应更多不同的数据。在函数内部,参数就像变量一样,可以进行数据处理。调用函数时,按照函数定义的参数顺序,把需要处理的数据传递进去。参数分为形参和实参:
- 形参:定义函数时,小括号中的参数就是形参,它用于接收参数,在函数内部当作变量使用。
- 实参:调用函数时,小括号中的参数是实参,用来把数据传递到函数内部。
下面通过几个练习来看看不同参数数量的函数使用:
-
一个参数的函数练习:
- 练习一:传入一个名字,对这个人say Hello
js
function sayHello(name) {
console.log(`Hello, ${name}!`);
}
sayHello("李四");
- 练习二:为某个朋友唱生日快乐歌
js
function singHappyBirthday(friendName) {
console.log(`Happy Birthday to ${friendName}!`);
}
singHappyBirthday("王五");
-
两个参数的函数练习:
- 练习三:传入两个数字,计算两个数字的和,并且打印结果
js
function calculateSum(num1, num2) {
let sum = num1 + num2;
console.log(`${num1}和${num2}的和是:${sum}`);
}
calculateSum(5, 8);
四.函数的返回值
函数不仅可以有参数,还能有返回值,使用return
关键字来返回结果。一旦执行return
操作,当前函数就会终止。如果函数中没有使用return
语句,或者return
后面没有任何值,函数的返回值都是undefined
。下面通过几个练习来理解函数的返回值:
- 练习一:实现一个加法计算器
js
function add(num1, num2) {
return num1 + num2;
}
let result = add(3, 7);
console.log(`3和7相加的结果是:${result}`);
- 练习二:定义一个函数,传入宽高,计算矩形区域的面积
js
function calculateRectangleArea(width, height) {
return width * height;
}
let area = calculateRectangleArea(5, 4);
console.log(`宽为5,高为4的矩形面积是:${area}`);
- 练习三:定义一个函数,传入半径,计算圆形的面积
js
function calculateCircleArea(radius) {
return Math.PI * radius * radius;
}
let circleArea = calculateCircleArea(3);
console.log(`半径为3的圆形面积是:${circleArea}`);
- 练习四:定义一个函数,传入n(n为正整数),计算1~n数字的和
js
function sumFrom1ToN(n) {
let sum = 0;
for (let i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
let sumResult = sumFrom1ToN(5);
console.log(`1到5的数字和是:${sumResult}`);
- 实战函数练习:传入一个数字,可以根据数字转化成显示为亿、万文字显示的文本
js
function formatNumberToText(num) {
if (num >= 100000000) {
return `${num / 100000000}亿`;
} else if (num >= 10000) {
return `${num / 10000}万`;
} else {
return num.toString();
}
}
let numText = formatNumberToText(500000000);
console.log(numText);
五.arguments参数
在函数中有一个特殊的对象arguments
,它是所有(非箭头)函数都可用的局部变量。这个对象存放着所有调用者传入的参数,从0位置开始依次存放。虽然它的类型是object
(类似数组,但不是真正的数组),不过用法和数组很相似。如果调用者传入的参数比函数接收的参数多,就可以通过arguments
获取所有参数。由于涉及到数组、对象等概念,刚开始学习的同学了解有这么个参数就行,后续深入学习时会详细研究它和数组之间的转化。例如:
js
function showArgs() {
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
showArgs(1, 2, 3);
六.函数中调用函数
在开发过程中,函数内部是可以调用其他函数的。比如:
js
function foo() {
console.log("foo函数被调用");
}
function bar() {
foo();
}
bar();
那函数能不能调用自己呢?答案是可以的,不过必须要有结束条件,否则会产生无限调用,导致报错。例如下面这个错误的示例:
js
var count = 0;
function bar() {
console.log(count++);
bar();
}
bar();
// 报错:Uncaught RangeError: Maximum call stack size exceeded
这种函数调用自己的方式有个专业名词,叫做递归(Recursion)。递归是一种很重要的编程思想,它能把一个复杂的任务转化成可以重复执行的相同任务。以实现一个幂函数pow
为例,我们可以用for
循环来实现:
js
function pow(a, n) {
var result = 1;
for (var i = 0; i < n; i++) {
result *= a;
}
return result;
}
也可以用递归的方式实现。在数学上,(x^{n}=x * x^{n - 1}),所以在函数实现时可以这样写:
js
function pow(x, n) {
if (n === 1) return x;
return x * pow(x, n - 1);
}
递归的代码初次接触可能会觉得有点绕,刚开始学习函数的同学可以先跳过,后续学习数据结构与算法时,会经常用到递归来解决算法问题。
七.局部变量和外部变量
在JavaScript(ES5之前)中没有块级作用域的概念,但函数可以定义自己的作用域。作用域表示一些标识符的有效范围,函数的作用域意味着在函数内部定义的变量,只有在函数内部才能被访问到。
- 局部变量:定义在函数内部的变量就是局部变量。
- 外部变量:定义在函数外部的变量称为外部变量。
- 全局变量 :在函数之外(比如在
<script>
标签中声明的变量)声明的变量就是全局变量,全局变量在任何函数中都可见。通过var
声明的全局变量还会在window
对象上添加一个属性(了解即可)。在函数中访问变量时,会优先访问自己函数中的变量,如果没找到,才会去外部访问。关于块级作用域、作用域链、变量提升、AO、VO、GO等概念,后续会深入学习。例如:
js
let globalVar = "我是全局变量";
function testFunction() {
let localVar = "我是局部变量";
console.log(localVar); // 输出:我是局部变量
console.log(globalVar); // 输出:我是全局变量
}
testFunction();
八.函数表达式与头等函数
(一)函数表达式
前面定义函数的方式叫函数声明,还有一种写法是函数表达式。例如:
js
var foo = function() {
console.log("foo函数");
};
这里function
关键字后面没有函数名,函数表达式允许省略函数名。函数声明和函数表达式有一些区别:
区别点 | 函数声明 | 函数表达式 |
---|---|---|
语法 | 在主代码流中声明为单独的语句 | 在一个表达式中或另一个语法结构中创建 |
创建时机 | 在脚本运行前,JavaScript会先寻找并创建全局函数声明 | 代码执行到达时被创建,从那一刻起才可用 |
开发选择 | 优先考虑,组织代码更灵活,声明前可调用 | 根据具体需求,比如需要在特定表达式中创建函数时使用 |
(二)头等函数与函数式编程
在JavaScript中,函数是一种特殊的值(其类型是对象,关于对象后续会深入学习),并且可以被当作头等公民。这意味着函数能作为别的函数的参数、函数的返回值,还能赋值给变量或存储在数据结构中。支持这种编程方式的语言,我们称之为函数式编程,JavaScript就是其中之一。例如:
js
function foo() {
console.log("foo函数执行");
}
var bar = foo;
bar();
函数还能传递给另外一个函数,像下面这种情况:
js
function foo(fn) {
fn();
}
function bar() {
console.log("我是bar函数被调用");
}
foo(bar);
这里foo
函数接受一个函数作为参数,这种函数也被称为高阶函数。高阶函数必须至少满足两个条件之一:接受一个或多个函数作为输入;输出一个函数。另外,如果传入函数时没有指定函数名,或者通过函数表达式指定函数对应的变量,这个函数就叫做匿名函数。
九.立即执行函数
(一)什么是立即执行函数
立即执行函数,专业名字叫Immediately - Invoked Function Expression(IIFE),它的特点是函数定义完后会立即执行。它由两部分组成,第一部分是定义一个匿名函数,这个函数有自己独立的作用域;第二部分是后面的()
,表示这个函数被执行了。例如:
js
(function() {
console.log("立即执行函数");
})();
(二)立即执行函数的作用
立即执行函数会创建一个独立的执行上下文环境,这样可以避免外界访问或修改内部的变量,也能防止内部变量被外界修改。比如在操作DOM元素时:
js
var btns = document.querySelectorAll(".btn");
for (var i = 0; i < btns.length; i++) {
(function(m) {
btns[m].onclick = function() {
console.log(`第${m}个按钮被点击了`);
};
})(i);
}
(三)立即执行函数的注意事项
立即执行函数必须是一个表达式,不能是函数声明。下面这种写法会报错,因为它被当成了函数声明:
js
function foo() {
}()
console.log("立即执行函数");
// 报错
而把函数用圆括号包裹起来,就会被当作表达式解析,例如:
js
(function foo() {
console.log("立即执行函数");
})();
另外,还有一些其他写法也能实现立即执行函数,比如:
js
+function foo() {
console.log("立即执行函数");
}();
(function foo() {
console.log("立即执行函数");
}());
十.代码风格与调试技巧
(一)代码风格规范
在编写JavaScript代码时,遵循良好的代码风格规范很重要。比如:参数之间要有空格;函数名和括号之间要有空格;操作符周围要有空格;for
、if
、while
等关键字后面要有空格;代码缩进使用2个空格;逻辑块之间要有空行;每行代码不要太长等。良好的代码风格能让代码更易读、易维护。
(二)Chrome的debug调试技巧
在开发过程中,调试代码是必不可少的环节。Chrome浏览器提供了强大的调试工具。例如,在代码中添加debugger
关键字,当代码执行到这一行时,会暂停执行,这时可以在调试面板中查看变量的值、调用栈信息等,方便我们排查代码中的问题。比如下面这段代码:
js
function foo() {
console.log("fo0函数执行");
setTimeout(function() { }, 2000);
}
function bar() {
console.log("bar函数执行");
foo();
}
debugger;
bar();
在Chrome浏览器的开发者工具中,就能利用调试功能,一步步查看代码的执行过程,找到可能存在的问题。
十一.总结
这篇文章到这里就结束了🔚,JavaScript函数的知识丰富而有趣,从基础的声明调用,到参数、返回值、递归,再到函数的各种高级特性,每一部分都值得我们深入学习和研究。希望通过这篇文章,大家能对JavaScript函数有更全面、更深入的理解,在编程的道路上更上一层楼。