js说:这就是函数

在这里插入图片描述

7、函数

函数实际上是对象,每一个函数都是Function类型的实例,而函数名就是指向函数对象的指针

简单地说,函数就是对象,它封装了一段可被重复调用执行的代码块,我们通过调用函数就可以实现大量代码的重复使用

7.1 定义函数

在这里插入图片描述

定义函数总共有四种方式:

  1. 函数声明
  2. 函数表达式
  3. 箭头函数(ES6)
  4. Function构造函数

1、函数声明

函数声明式我们最常用的定义函数的方法

function foo() {
	console.log("Hello world!")
}

2、函数表达式

函数表达式几乎和函数声明式等价的

var foo = function() {
	console.log("Hello world!")
}

该方式跟变量初始化一样

函数表达式有一点跟函数声明不同的是:它不会有变量提升的现象

3、Function 构造函数

let foo = new Function(console.log("Hello world!"));

这种定义函数的方法不推荐,因为这段代码会被执行两次:

  • 第一次:把它当作常规代码来执行
  • 第二次:解析传给构造函数的字符串

4、箭头函数

箭头函数以短小精悍著称,它和函数表达式有点像

let foo = (sum1, sum2) => { console.log(sum1 + sum2) }

ES6 新增的

7.2 函数的参数

函数的参数包括形参和实参

  • 形参:定义函数时,定义给函数用的变量
  • 实参:调用函数时,传给函数的数据

但是,js偏偏就与其它语言不同,js函数不关心你传入参数的个数,即使你在定义函数时规定要函数要接收两个形参,但是并不意味这你调用函数就得传入两个实参

你传入一个、两个、三个都行,甚至不传都行,解释器不会报错,也没有人说你;是不是惊掉下巴!

我们来看一下:

function foo(a, b) {
    console.log(a + b);
}foo()// NaN
foo(1)// NaN
foo(1, 2, 3)// 3

这是为什么呢?

1、arguments 对象

arguments 何许人也?

arguments 是一个对象,而且是一个伪数组对象,伪数组不是Array的实例

那它有什么作用呢?arguments 就是接收从外面传进来的实参的,传进来的实参按照先来后到的顺序依次存进去arguments中,所以,第一个参数的键名是0,第二个是1,依次排序

一句话:arguments 是一个伪数组,负责存储传进来的参数

所以,我们在使用function关键字定义的函数时,可以通过访问arguments对象,来获取传进来的参数

一定得是 function关键字定义的函数,不能是箭头函数,因为箭头函数不能使用arguments

我们来感受一下:

function foo(a, b) {
    console.log(arguments[0] + arguments[1]);
}
foo(10, 20);  // 30

我们还可以查看 arguments中有多少个参数

function foo(a, b) {
	console.log(typeof arguments);
    console.log(arguments.length);
    console.log(arguments);
}
foo(10, 20);
// object
// 2
// { '0': 10, '1': 20 }

现在可以解释为什么参数怎么传都不会报错

  • 当我们少传或者不传的时候,arguments取出来的实际就是 undefinedundefined本省相加或者与数字相加时,就会返回 NaN
  • 当我们多传时,js解释器只取它需要,其它不需要用到的放一边

所以,js函数的参数只是为了方便才写出来,这样不用很麻烦地写 arguments[0]arguments[1]…,所以参数并不是必须写出来的

2、默认参数值

默认参数值就是我们可以给函数参数设置一个默认的数,当我们传参的时候,函数就是使用我们传递的参数,否则就是使用默认参数

在ES6之前,我们都需要自行检测是否有传参,没有就使用默认参数

// 当不传参的时候,b的类型就是undefined
function foo(a, b) {
    b = (typeof b !== 'undefined') ? b : 20
    console.log(a + b);
}
foo(10);  // 30

在ES6之后支持显式定义默认参数

function foo(a, b=20) {
    console.log(a + b);
}
foo(10);  // 30

现在我们都是使用显式定义默认参数了

需要注意的是:使用默认参数时,arguments 对象的值不反映参数的默认值,只反映传给函数的参数

箭头函数同样也可以使用默认参数

7.3 函数的返回值

在这里插入图片描述

1、return 语句

我们函数只是实现功能,最终的结果需要返回给函数的调用者函数名( ),可以通过 return实现

function share() {
    return "只要你在 我会一直分享";
}
share();  // 执行函数(输出结果需要打印)

我们还可以把函数名share赋给另外一个变量

var foo = share;
  • 现在foo也是函数名了

注意:使用不带括号的函数名只会访问函数指针,而不会执行函数;此时 fooshare 都是指向同一个函数


练习一下:

// 练习一
function getMax(a, b) {
    if (a > b) {
        return a
    } else {
        return b
    }
}
console.log(getMax(10, 20));  // 20


// 练习二:使用三元表达式
function getMax1(a, b) {
    return a > b ? a : b
}
console.log(getMax1(10, 20));  // 20

2、return 结束函数

return函数也相当于一个终止语句,即return 语句之后的代码就都不会被执行了

function share() {
    return '只要你在 我会一直分享!'
    alert('一定会!');   // 并不会被执行
}
console.log(share());  // 只要你在 我会一直分享!

3、return 返回值

return语句只能返回一个值

function getNum(a, b) {
	return a, b;
}
console.log(getNum(10, 20));  // 10

4、函数如果没有return

函数如果没有return,则它会返回undefined

function foo(a) {
    a++};
console.log(foo(10));  // undefined

breakcontinuereturn 的区别

函数说明主要针对对象
break结束当前的循环体(如forwhile循环
continue跳出本次循环,继续执行下次循环(如forwhile循环
return不仅可以退出循环,还能返回return语句中的值,同时还能结束当前的函数体内的程序循环、函数

7.4 函数的调用

在这里插入图片描述

函数的调用有几种方式:

  • 常规调用
  • 被另外的函数调用
  • 立即调用

1、常规调用

常规调用就是我们自己定义函数,然后自己调用

function share() {
    return "只要你在 我会一直分享";
}
share();  // 调用函数

这种是再正常不过的了

2、被另外的函数调用

就是通过其他函数来调用自己

function foo1() {
    console.log(10)
}

function foo() {
    console.log(20)
    foo1();
}
foo()  // 一次性执行两个函数

但很多时候,函数是作为值被传入到另外的函数中

在js中,函数名就是就是变量,所以函数可以被使用在任何可以使用变量的地方。这就意味着不仅可以在一个函数中返回另一个函数,还可以把函数作为参数传给另一个函数

定义一个函数:

function add(a) {
    return a + 20
}

把该函数作为参数传入另一个函数中:

// 第一个参数是传入函数,第二个是传入这个函数的数值
function callAdd(callback, num) {
    return callback(num)
}

var result = callAdd(add, 10)
console.log(result);  // 30
  • 上面callAdd函数是为了访问 callback函数,而不是调用它,所以不能带括号
  • 这种方式在回调函数中经常使用

3、立即执行函数

我们平时定义的函数,都是需要自己来调用的,但是有一种函数是自己执行的:立即执行函数

立即执行函数也叫立即调用函数表达式(IIFE),以后看到 IIFE就要知道是 立即执行函数

IIFE 由于被包含在括号中,所以会被解释为函数表达式

(function() {
	函数体
})()

简单栗子:

(function() {
    console.log('你好 世界!');
})();  // 你好 世界!

括号还可以传参数:

(function(a, b) {
    console.log(a + b);
})(10, 20);  // 30

立即执行函数在ES6以前可是有大作用的,那时尚未支持块级作用域,在var大行其道的年代,使用var关键字声明的循环迭代变量i,它并不会被限制在for循环中的块级作用域内

for (var i = 0; i < 5; i++) {}
console.log(i);  // 5

外面居然访问得到块级作用域里面的变量,这是绝对不允许的,所以 IIFE 就出来“救场”了

(function() {
    for (var i = 0; i < 5; i++) {}
})()
console.log(i);  // 报错

将循环定义在 IIFE中,就可以防止变量定义外泄;因为只要函数执行完毕,其作用链就会被销毁

但是,在ES6之后,let声明关键字出世,块级作用域的变量就被锁定

for (let i = 0; i < 5; i++) {}
console.log(i); // 报错
  • 所以, 现在就不必大动干戈使用IIFE

7.5 私有变量

任何定义在函数中的变量,都是私有变量,外部是不能访问到的

function foo() {
    let num = 10;
}
foo();
console.log(num);  // 报错
  • 无论使用var 还是 let 声明的变量都是私有变量

要想访问函数里面的变量,也不是没有办法,这就涉及到了另一个东西:闭包;这个我们后面再来讨论

7.6 递归函数

递归函数就是一个函数通过名称自己调用自己

我们计算 1 * 2 * 3 * 4 * 5时,就可以使用递归思想:

function factorial(num) {
    if (num === 1) {
        return num
    }
    return num * factorial(num - 1);
};
let sum = factorial(5);
console.log(sum); // 120

其运算过程是这样的:

retrun 5 * factorial(4);  // 第一次
retrun 5 * (4 * factorial(3));  // 第二次
retrun 5 * (4 * (3 * factorial(2)));  // 第三次
retrun 5 * (4 * (3 * (2 * factorial(1))));  // 第四次
retrun 5 * (4 * (3 * (2 * (1))));  // 第五次

注意:当我们把函数赋给其它变量,再令factorial = null,会出现报错

let anotherF = factorial;
factorial = null
console.log(anotherF(5)); // 报错

这是因为anotherF 保留了对函数的引用,调用它就需要递归调用 factorial函数,但此时factorial已不是函数了,所以会报错

我们可以创建一个命名函数表达式f() ,然后再它赋值给变量 factorial,这样即使把函数赋值给其它变量,再怎么赋值就都不影响到新变量调用函数了,因为它递归调用的是f()f不曾改变

let factorial = function f(num) {
    if (num === 1) {
        return num
    }
    return num * f(num - 1);
};

let anotherF = factorial;
factorial = null
console.log(anotherF(5)); // 120

再来看一道题:打印斐波那契数列

斐波那契数列:前两个数是1,后面的每个数都是前两个数之和(如:1, 1, 2, 3, 5, 8, 13…)

// 封装一个斐波那契数列的函数
let factorial = function f(num) {
    if (num <= 2) {
        return 1
    }
    return f(num - 1) + f(num - 2)
};

// 打印前10个数
var arr = []
for (var i = 1; i <= 10; i++) {
    arr.push(factorial(i))
}
console.log(arr);  // [ 1,  1,  2,  3,  5, 8, 13, 21, 34, 55]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值