创建函数
1、函数声明
function testName(){// 执行语句}// 调用函数testName();// 每调用一次函数,函数内的代码都会重新执行一次testName();testName();
「函数声明」其实有点类似于「变量声明」,不同的是变量声明用的是var关键字,而函数声明使用function关键字。
var testName = 123;
2、函数表达式(字面量)
var testName = function funName(){// 执行语句}testName(); // 正常执行funName(); // Error
当我们使用函数表达式创建函数的时候,JS会忽略funName然后使用testName去引用函数,testName才是真正要执行的函数。
由此我们可以简化函数表达式:
var testName = function (){// 执行语句}testName();
如何理解函数字面量?
在我们声明变量的时候等于号右面就是字面量,字面量可以是任意的数据类型:
var a = 123;var b = "str";var c = function(){}
3、函数命名的规则
- 不能用数字开头
- 可以用字母、_、$ 开头
- 可以包含数字
- 小驼峰命名,例如:
myTestFunName
函数的参数
在函数中,()里存放的是函数的参数,参数可以丰富函数中的功能,完全由外部控制函数接收的数据。
// param1,param2 就是函数的形参function testName(param1, param2){console.log(testName.length); // 返回函数形参的个数}
这里的参数是没有真正的数据的,只是形式上的占位,所以我们也叫函数的「形参」,形参的名字可以自定义命名。
function testName(param1,param2){console.log(arguments); // 返回实参的数组}testName(1, 2, 3);
当我们调用函数的时候可以传入一些数值,这些数值是可以被函数实际使用的,所以我们称为「实参」。
函数的「实参」和「形参」需要一一对应起来,确保使用的形参就是你要操作的实参,两者的数量可以不相等,但是位置一定要相应。
function testName(param1,param2){console.log(param1,param2); // 1,2}// 多传入的 3 不会被打印,也不会报错testName(1,2,3);function testName2(param1,param2,param3){// 未传入的参数会显示 undefindconsole.log(param1,param2,param3); // 1,2,undefind}testName2(1,2)
参数映射
在函数体内是可以更改实参传过来的值:
function testName(param1, param2) {// 可以更改形参的值param1 = 3;console.log(arguments); // [3, 2]console.log(param1); // 3}testName(1,2);
在函数内打印arguments数组和param1虽然都是 3,但它们并不是同一个东西,它们之间只是一种映射关系,param1是保存在栈内存中,arguments是保存在堆内存中,栈内存保存了堆内存的地址。
映射关系:无论外部如何给实参赋值,函数的形参和 arguments 都会跟着改变,函数的形参和 arguments 的值始终是同步的。
如果没有给形参传值在函数内进行赋值arguments是不会显示的:
function testName(param1, param2) {// 可以更改实参的值param2 = 3;console.log(arguments); // [1]console.log(param2); // 3}testName(1);
如果只传了一个实参,在函数内给第二个形参赋值,这个值并不会反映到第二个命名参数。这是因为 arguments 对象的长度是根据传入的参数个数,而非定义函数时给出的命名参数个数确定的。
传递参数
ECMAScript 中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。如果是原始值,那么就跟原始值变量的复制一样,如果是引用值,那么就跟引用值变量的复制一样。
在按值传递参数时,值会被复制到一个局部变量( arguments对象中的一个槽位)。在按引用传递参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。
function addTen(num) {num += 10;return num;}let count = 20;let result = addTen(count);console.log(count); // 20,没有变化console.log(result); // 30
function setName(obj) {obj.name = "Nicholas";}let person = new Object();setName(person);console.log(person.name); // "Nicholas"
参数的默认值
在我们不给形参传实参的时候也可以给形参一个默认值
// ES5 的写法function testName(a, b) {var a = arguments[0] || 1;var b = arguments[1] || 2;console.log(a, b);}testName(3); // 3,2testName(undefined, 4); // 1,4// ES6 的写法function testName(a = 1, b = 2) {console.log(a, b);console.log(arguments)}testName(3); // 3,2testName(undefined, 4); // 1,4
函数的返回值
函数可以使用return关键字将函数终止执行和返回数据。
function testName(){console.log("我正在执行函数"); // "我正在执行函数"return;console.log("函数执行完成"); // 不会执行}testName();
如果你不显示的写出return,JS引擎会自动给你补全。
function testName(){console.log("我正在执行函数");// return; // JS自动补全}testName(); // "我正在执行函数"
return还可以返回数据:
function testName(){console.log("我正在执行函数"); // "我正在执行函数"return "这是返回值";console.log("函数执行完成"); // 不会执行}console.log(testName()) // "这是返回值"
如果不显式的返回内容,return会返回undefind
function testName(){console.log("我正在执行函数"); // "我正在执行函数"return;}console.log(testName()) // undefind
递归
函数递归就是「函数自己调用自己」。
如何使用递归呢?
首先我们要确定函数执行的「规律」,最后还要给函数一个终止调用自己的「出口」。
因为递归是自己调用自己,一步一步等到下次调用返回的结果,这样就形成了嵌套,所以在性能上不太友好,仅适用处理简单的程序。
// n 的阶乘function fact(n) {if (n === 1) {return 1;} else {return n * fact(n - 1);}}console.log(fact(5))// 斐波拉数列function fb(n) {if (n <= 2) {return 1;} else {return fb(n - 1) + fb(n - 2);}}console.log(fb(6));
