函数(function)也叫做功能、方法。
函数可以将一段代码封装起来,被封装起来的函数具备某一项特殊功能,内部封装的一段代码作为一个完整的结构体(函数体),要执行就都执行,要不执行就都不执行。
函数必须先定义才能使用,否则会出现引用错误。
声明语法:
function 函数名(参数) {
封装的结构体;
}
//定义函数 function fun() { console.log(2); }
函数命名规则:由数字、字母、下划线、$符号组成,不能以数字为开头,区分字母大小写,不能使用关键字和保留字。
函数声明的时候,函数体并不会执行,只有函数被调用才会被执行。
调用方法:函数名();
function fun() { console.log(2); } //调用函数 fun();
函数内部语句执行的位置,与函数定义的位置无关,与函数调用的位置有关。
函数可以一次定义,多次调用执行。
接口:就是函数的参数,函数的参数本质是一个变量,可以接收任意类型的数据,导致函数执行结果根据参数不同,结果也不同。
一个函数可以设置 0 个或者多个参数,多个参数之间用逗号分隔。
函数的参数根据书写位置不同,名称也不同:
形式参数:定义()内部的参数,叫形式参数。本质是变量,可以接收实际参数传递过来的数据,简称形参。
实际参数:调用()内部的参数,叫实际参数。本质就是传递的各种类型的数据,传递给每个形参,简称实参。
function fun(a,b) { // 小括号中的a,b为形式参数 console.log(a,b); } //调用函数 fun(4,5);// 小括号中的4,5为实际参数
函数执行过程中,伴随传参过程:
一般自己封装的函数或者其他人封装的函数需要有一个API接口说明,告诉用户参数需要传递什么类型的数据,实现什么功能
// 定义一个求和函数,传入两个参数 // 参数:传两个参数,数据类型为数字 // 功能:得到两个数字之和 function sum(a,b) { console.log(a + b); } // 调用函数中,给小括号内部添加数据 sum(3,4); sum("3",4);
利用函数内部的一个return的关键字设置函数的返回值。
作用-1:函数内部如果结构体执行到一个return的关键字,会立即停止后面代码的执行
// return 可以终止函数的执行 function sum() { console.log(1); console.log(2); console.log(3); return; console.log(4); console.log(5); console.log(6); } // 调用函数 sum();
作用-2:可以在return关键字后面添加空格,空格后面而已定义一个数据字面量或者表达式,函数在执行完自身功能之后,整体会被return爱化成一个表达式,表达式必须求出一个值继续可以参与程序,表达式的值就是return后面的数据
// 使用返回值,制作函数运行结果后的结果 function fun(a,b) { return a + b; } // 调用函数 console.log(fun(3,4));
返回值应用:
函数如果有返回值,执行结果可以当成普通数据参与程序;或者可以作为一个普通数据赋值给一个变量,甚至赋值给其他函数的实参
// 使用返回值,制作函数运行结果后的结果 function fun(a,b) { return a + b; } // 调用函数 console.log(fun(3,4)); // 将返回值赋值给变量 var num = fun(3,4); console.log(num); // 将返回值赋值给函数的实参 console.log(fun(2,fun(3,4)));
注意:函数没有设置return语句,那么函数返回值是undefined;设置return,但是return后面没有值,默认返回值也是undefined。
是函数的另一种定义方式
定义方法:将函数的定义、匿名函数(没有函数名)赋值给一个变量
调用函数表达式,方法是给变量名加()执行,不能使用函数名加()执行
// 定义一个函数表达式 var foo = function fun() { console.log(1); }; // 实际采用方式,因为在调用是使用的是变量名 var foo2 = function() { console.log(2); }; // 调用函数式,只能用变量名调用,函数名调用不成功 foo(); foo2();
函数是一种单独的数据类型function。可以参与其他程序
如:
// 函数是一种数据类型,可以当成其他函数的参数 setInterval(function() { console.log(1); }, 1000);
// 将函数当成另一个函数的返回值 function fn(b) { var a = 10; return function() { alert(a + b); } }
所有函数都内置了一个arguments对象。arguments对象中存储了传递的所有实参。
arguments是一个伪数组,因此可以进行遍历。
函数的实参与形参个数可以补贴,所有的实参都存储在arguments对象中。
// 调用函数的时候,实参的个数可以与形参不同 //函数内部有一个 arguments 对象,会接收所有的实参 function fun() { console.log(arguments); console.log(arguments.length); // 使用数组遍历方法可以获取每一项实参 for(var i = 0; i <= arguments.length-1; i++) { console.log(arguments[i]); } } // 调用函数 fun(1,2,3,4,5,6,7);
案例应用:定义一个求和函数,如果传入1个参数,返回它自己,如果传入两个参数,返回他们的和,如果传入三个参数,先比较前两个的大小,大的与第三个参数求和返回,如果传入四个及以上,输出错误提示。
function sum(a,b,c) { // 条件分支语句,根据实参个数走不同的分支 switch (arguments.length) { case 1: return a; break; case 2: return a + b; break; case 3: return a > b ? a + c : b + c; break; default: // 提示用户实参个数传递错误 // 模拟控制台报错 throw new Error("参数个数不能超过 3 个"); } } // 调用函数 console.log(sum(1)); console.log(sum(1,2)); console.log(sum(1,2,3)); console.log(sum(1,2,3,4,5));
函数内部可以通过函数名调用函数自身的方式,叫做函数递归。
更多时候,使用递归去解决一些数学中的现象。函数递归次数太多,会出现错误:超出计算机最大计算能力
/* 函数,如果我们传入的参数是 1 ,返回 1 ,如果传入的是 1 以上的 数字,让它返回参数 + 函数调用上一项 */ function fun(a) { if (a === 1) { return 1; } else { return a + fun(a - 1); } } // 调用函数 console.log(fun(1)); console.log(fun(2)); console.log(fun(3)); console.log(fun(100000000000)); //报错:超出计算机最大计算能力
案例应用:输出斐波那契数列的某一项的值
// 菲波那切数列:后面的一项数据是前两项数据之和。1、1、2、3、5、8、13、21、34、55、、、 // 参数:正整数 // 返回值:对应的整数位置的菲波那切数列的值 function fibo(a) { // a 表示项数 if (a === 1 || a === 2) { return 1; } else { // 返回 前一项 和 前前一项 return fibo(a - 1) + fibo(a -2); } } // 调用函数 console.log(fibo(1)); console.log(fibo(2)); console.log(fibo(3)); console.log(fibo(4)); console.log(fibo(5));
如果定义一个变量在函数内部,只能在函数内部被访问到,在函数外部不能使用这个变量,函数就是变量定义的作用域。
局部变量:定义在函数内部的变量,只能在函数作用域内被访问到。
全局变量:从广义上来说,也是一种局部变量,定义在全局的变量,作用域范围是全局,在整个JavaScript程序任意位置都能够被访问到。
// 定义函数 function fun() { var a = 1; console.log(a); } // 执行函数 fun(); // 函数外部调用 a console.log(a); //不成功 // 函数的参数也是局部变量 function sum(a) { a = 2; console.log(a); } // 调用函数 sum(1); console.log(a); //不成功 // 函数也有自己的作用域 function outer() { var a = 1; function inner() { console.log(2); } // 函数内部调用子函数才能成功 inner(); } // 调用函数 outer(); inner(); // 错误方式 定义范围在函数内部
局部变量退出作用域之后被销毁,全局变量关闭网页或者浏览器才会被销毁。
函数参数也是局部变量, 只能在函数内部被使用,在函数外面没有定义。
函数也有自己的作用域,定义在哪个作用域内部,只能在这个作用域范围内被访问,出了作用域不能被访问的。 函数定义在另一个函数内部,如果外部函数没有执行时,相当于内部代码没写。
将这样的所有的作用域列出来,可以有一个结构: 函数内指向函数外的链式结构。就称作作用域链。
function f1() { function f2() { } } var num = 456; function f3() { function f4() { } }
图示:
遮蔽效应:程序在遇到一个变量时,使用时作用域查找顺序,不同层次的函数内都有可能定义相同名 字的变量,一个变量在使用时,会优先从自己所在层作用域查找变量,如果当前层没有变 量定义会按照顺序从本层往外依次查找,直到找到第一个变量定义。整个过程中会发生内 层变量遮蔽外层变量的效果,叫做“遮蔽效应。
// 全局作用域 var a = 1; // 创建函数 function outer() { var a = 2; // 内部函数 function inner() { var a = 3; console.log(a); } inner(); } // 调用 outer();
不写var关键字的影响
在函数内部想要定义新的变量,如果不加关键字var,相当于定义的全局变量,如果全局也有相同的标识符,会被函数内部的变量影响,局部变量污染全局变量,所以每次定义变量是都必须写var关键字。
JavaScript 代码的执行是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器执行 JavaScript 代码的时候,分为两个过程:预解析过程和代码执行过程。
预解析过程:
1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
2. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
3. 先提升 var,再提升 function。
JavaScript 的执行过程:在预解析之后,根据新的代码顺序,从上往下按照既定规律执行 js 代码。
提升顺序:
函数表达式进行的是变量声明提升,提升后变 量内部存的是一个 undefined。在前面进行函数方法调用,数据类型会提示错误。 建议:定义函数时,最好使用 function 关键字定义方式,这样函数声明提升可以永远生效。
// 预解析 把变量、函数声明部分提升到了作用域最顶部 //模拟提升 // var a; // 相当于存储了一个undefined的值 // function fun() { // console.log(2); // } // 调用一个变量 console.log(a); console.log(fun); // 后定义变量 var a = 1; // a = 1; var fun = "haha"; //先调用函数 fun(); // 定义函数 function fun() { console.log(2); } // 调用 fun(); foo(); // 函数表达式进行的是变量声明提升 var foo = function () { console.log(a); }
IIFE:immediately-invoked function expression,叫做即时调用的函数表达式,也叫做自调用函数,表示函数在定义时就立即调用。
函数调用方式:函数名或函数表达式的变量名后面加 () 运算符。
函数矮化成表达式的方法,可以让函数参与一些运算,也就是说给函数前面加一些运算符。
数学运算符:+ - ()
逻辑运算符:!非运算
//关键字定义的方式,不能立即执行 // function fun() { // console.log(1); // }(); //函数表达式方式,可以在定义是被立即执行 var foo = function () { console.log(2); }(); //通过在函数前面添加操作符,可以讲函数矮化成表达式 + function fun() { console.log(1); }(); - function fun() { console.log(1); }(); (function fun() { console.log(1); })(); !function fun() { console.log(1); }(); // IIFE 关注了函数的作用域,在外面是调用不了函数的 fun(); //常用的IIFE结构 (function(a){ console.log(a); })(4);
IIFE 结构可以关住函数的作用域,在结构外面是不能调用函数的。
IIFE 最常用的是 () 运算符,而且函数可以不写函数名,使用匿名函数。