在程序中,我们可能有很多重复的功能,比如我的程序中可能经常需要遍历一个数组,那如果我每次遍历数组都去写一个 for 循环,这显然是低效的,也很难维护,为了解决这个问题,就有必要引入函数。
可以说,函数最大的功能就是用来减少重复代码的。我们可以通过以下语法创建一个函数。
js
function 函数名(){
函数体;
}这里的函数体就是一个代码块,以上这种写法,我们称为函数字面量
我们举个例子来说明
js
function test(){
console.log("==============================");//这是一个分隔符
}
test();//调用这个函数,打印了一个分隔符
test();//调用这个函数,又打印了一个分隔符以上就创建了一个名为 test 的函数,这个函数的作用,就是用来输出一个分隔符,当我们需要使用分隔符的时候,只需要调用这个函数即可,不需要再去写 console.log。
从上面的例子可以看出,当我们创建一个函数之后,我们就可以随时使用这个函数。使用函数的方式也很简单,函数名加上一个小括号即可。这样,每次需要这个功能的时候,我们只需要使用这个函数即可,这样就可以大大减少重复代码。
通过形如 function 函数名(){} 这种方式定义的函数,会提升到脚本块的顶部,这一点和变量类似。同时,也会成为全局对象 window 的属性。
js
test();
function test(){
console.log("==============================");//这是一个分隔符
}上例中,函数是可以顺利执行的。
请注意,这里回顾一下变量的提升,对于
var a = 1;这样一个语句,实际上是var a;和a = 1;两条语句的复合,会提升的只有var a;,而后面的赋值语句是不会提升的。
值得注意的是,不管是函数还是变量的提升,都只会提升到当前脚本块的顶部,看一个例子
html
<script>
test();//报错
</script>
<script>
function test(){//只会提升到当前脚本块的顶部
console.log("==============================");//这是一个分隔符
}
</script>在上例中,test 函数的定义只提升到第二个脚本块的顶部,而第一个脚本块中对 test 函数调用的时候,还看不到 test 函数的定义,所以会直接报错。
接下来再看
js
function test(){
console.log("test");
}
window.test();//正确输出了"test"如果使用
typeof 函数名,得到的结果会是一个"function"
JS 是函数级作用域语言,什么意思呢,就是在函数内部声明的变量,只能在函数内部使用。
js
function test(){
var a = 1;
}
test();
console.log(a);//这里并不能正常输出。但如果不使用 var 关键字,则是暗示全局变量,相当于给 window 对象直接添加属性。
js
function test(){
a = 1;
}
test();
console.log(a);//这里可以正常输出。如果在函数内部使用 var 声明变量,变量会提升到当前函数的顶部
js
function test(){
console.log(a);//并不会报错
var a;//这行代码提升到函数的顶部
}
test();函数内定义的变量只会在函数内部生效,这意味着,我们可以在函数内部和函数外部定义同名变量,并不会冲突,函数外部使用函数外部的变量,函数内部则使用内部的变量。
接下来我们写一个函数,用于判断一个数是否是奇数
js
function isOdd(){
var n = 123;
if(n % 2 == 0){
console.log("不是奇数")
}else{
console.log("是奇数");
}
}
isOdd();此时我们可以调用这个函数,判断 123 是不是奇数,但是这显然很扯淡,因为 var n = 123 是写死的,我的程序中可能会大量使用到这个功能,但肯定不可能每一次都判断 123 是不是奇数,我需要实现的效果是,我给他一个数字,他就能判断出这个数是不是奇数。这个时候这个 n 就不能写死,而是由我给他。为了实现这个效果,引入参数的概念。
js
function isOdd(n){
if(n % 2 == 0){
console.log("不是奇数")
}else{
console.log("是奇数");
}
}
isOdd(4);
isOdd(5);在以上例子中,我将 n 写在了创造函数时的括号里面,这个操作相当于在函数最开始的时候使用 var n 创建了一个变量,但是不着急赋值,在使用 isOdd 这个函数的时候,括号里传入了这个数字,相当于就把函数内部的 n 赋值给了我指定的数字,此时这个函数就可以实现判断指定数字是不是奇数的效果。
定义函数的时候写在括号里的变量我们称为参数,可以理解为函数中未知的数据,或者说是调用函数的时候传入的数据。写在函数定义时的参数成为形式参数,而调用函数的时候传入的参数,例如上例中的 4 和 5,称为实际参数。所以,我们使用参数,将函数的内部和外部联系起来。
参数可以有多个,多个参数使用逗号隔开,例如下例
js
//该函数用于计算两个数的和
function sum(a,b){
console.log(a+b);
}
sum(1,2);//输出3参数本质上就是函数内部的变量,只不过这个变量很特殊,只是一个占位符,函数定义的时候并不知道它实际的值是多少,只有当函数被调用的时候,通过在括号里传入参数的方式,参数才会获得实际的数据。
我们可以认为,参数的存在,可以让函数内部可以使用到函数外部的数据。但反过来呢,我怎么让函数外部拿到函数内部的数据呢,我们可以看到,在前面的例子中,我们的结果都是直接输出,但这显然不对,你只能输出,函数外面拿不到最终结果,这个时候就需要 return 关键字。
js
//该函数用于计算两个数的和
function sum(a,b){
return a+b;//返回a+b的数据
}
console.log(sum(1,2));//此时函数外部就可以拿到函数内部返回的结果如果实参没有传递,对应的形参为 undefined
接下来做一个例子,一个数组里有很多个数,我现在写一个函数对这个数组求和,返回结果。
js
function sum(arr){
var s = 0;//用于保存结果
for(var i = 0 ;i<arr.length;i++){//遍历数组,求和
s += arr[i];
}
return s;//最后的结果返回
}
var arr = [1,4,6,66,2,34,8];//一个数组
var result = sum(arr);//调用函数,得到结果
console.log(result);读者可以这样理解,任何一个表达式都会有一个返回值,这个前面介绍过,函数调用,也就是上例中的
sum(arr),这个也是一个表达式,也会有一个返回值,函数的返回值就是创造函数内部的 return 后面的东西,如果没有写 return,或者 return 后面不写任何内容,那么返回值就是 undefined。
接下来考虑一个比较神奇的问题
js
function test(a){
a++;
console.log(a);
}
var a = 1;
test(a);
console.log(a);在上面的例子中,在函数内部将 a 加一,但是函数外部的 a 并没有改变。实际上,函数在进行参数传递的时候传递的是值,将 a 传递给 test 函数的时候,实际传递的是 1 这个数值,而不是变量 a。
值得注意的是,return 语句会直接结束整个函数。
接下来实现一个功能,输入任意年份,输出这一年每个月有多少天
js
/**
* 获取指定年份每个月的天数
* @param {number} year - 需要查询的年份
* 该函数没有返回值,它会在控制台打印出每一年每个月的天数。
*/
function getDay(year) {
// 判断是否为闰年
var isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
// 每一个月的天数(非闰年2月为28天)
var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
// 如果是闰年,则将2月的天数改为29天
if (isLeapYear) {
days[1] = 29;
}
// 循环输出每个月的天数
for (var i = 0; i < days.length; i++) {
console.log(`第${i + 1}月有${days[i]}天`);
}
}
// 调用函数,查询2019年每个月的天数
getDay(2019);