创建函数
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){
// 未传入的参数会显示 undefind
console.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,2
testName(undefined, 4); // 1,4
// ES6 的写法
function testName(a = 1, b = 2) {
console.log(a, b);
console.log(arguments)
}
testName(3); // 3,2
testName(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));