1 函数的概念

在 JS 里面,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用。

虽然 for循环语句也能实现一些简单的重复操作,但是比较具有局限性,此时我们就可以使用 JS 中的函数。

函数:就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用。

2 函数的使用

函数在使用时分为两步:声明函数和调用函数。

2.1 声明函数

  1. // 声明函数
  2. function 函数名() {
  3. //函数体代码
  4. }
  • function 是声明函数的关键字,必须小写
  • 由于函数一般是为了实现某个功能才定义的, 所以通常我们将函数名命名为动词,比如 getSum

2.2 调用函数

  1. // 调用函数
  2. 函数名(); // 通过调用函数名来执行函数体代码
  • 调用的时候千万不要忘记添加小括号
  • 口诀:函数不调用,自己不执行。

注意:声明函数本身并不会执行代码,只有调用函数时才会执行函数体代码。

函数提升、同名变量冲突问题

  1. /*
  2. * 函数的使用
  3. * 1:声明函数
  4. * function 函数名(){
  5. * 函数体
  6. * }
  7. * 2:调用函数
  8. * 函数名()
  9. *
  10. * 注意事项
  11. * 1函数名的命名规范遵从标识符(变量)的命名规范
  12. 建议:函数命名使用动词。变量命名使用名词。
  13. * 2函数必须调用才会执行。不调用不执行 。调用几次,。执行几次。
  14. * 3函数的声明位置可以在调用之前也可以在调用之后:凭什么?
  15. * 函数提升。声明变量的代码会被提升到最前。回顾变量提升怎么一回事。
  16. * 4:如果直接打印的是函数的名字:
  17. * 结果是把声明函数的代码给打印出来。当然,这并没有意义。
  18. * 如果你想查看这个函数是如何声明的。可以用一次。但是马老师觉得不如翻一下代码在编辑器里看如何声明的更像人类的操作。
  19. *5:如果函数的名字和变量的名字相同呢:
  20. * 如果变量没有赋值。那么该名字所代表的是函数。 (在作用域中)函数的优先级高于变量。
  21. * 如果变量已经赋值了。那么该名字代表的是变量。使用的时候,使用的是变量的值。
  22. * 所以,如果变量和函数同名。并且变量已经赋值。那么通过该名字调用时,会出错。出错的原因见上一行解释。(使用的时候,使用的是变量的值。)
  23. * 相当于。在使用变量名()而不是函数名()。
  24. * 而变量名() 代表的时值() 再推导,变量名里面存的时具体的数值。限制就相当于时100() 这玩意当然 报错。
  25. * */

2.3 函数的封装

  • 函数的封装是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口
  • 简单理解:封装类似于将电脑配件整合组装到机箱中 ( 类似快递打包)

49-函数封装.jpg

案例:利用函数计算1-100之间的累加和

  1. /*
  2. 计算1-100之间值的函数
  3. */
  4. // 声明函数
  5. function getSum(){
  6. var sumNum = 0;// 准备一个变量,保存数字和
  7. for (var i = 1; i <= 100; i++) {
  8. sumNum += i;// 把每个数值 都累加 到变量中 }
  9. alert(sumNum);
  10. }
  11. // 调用函数
  12. getSum();

2.4 提问

  1. 函数是做什么的(作用)?
  2. 声明函数用什么关键词?
  3. 如何调用函数?
  4. 封装是什么意思?

3 函数的参数

3.1 形参和实参

声明函数时,可以在函数名称后面的小括号中添加一些参数,这些参数被称为形参,而在调用该函数时, 同样也需要传递相应的参数,这些参数被称为实参

参数 说明
形参 形式上的参数 函数定义的时候 传递的参数 当前并不知道是什么
实参 实际上的参数 函数调用的时候传递的参数 实参是传递给形参的

参数的作用 : 在函数内部某些值不能固定,我们可以通过参数在调用函数时传递不同的值进去。

  1. // 带参数的函数声明
  2. function 函数名(形参1, 形参2 , 形参3...) { // 可以定义任意多的参数,用逗号分隔
  3. // 函数体
  4. }
  5. // 带参数的函数调用
  6. 函数名(实参1, 实参2, 实参3...);

案例:利用函数求任意两个数的和

  1. function getSum(num1, num2) {
  2. console.log(num1 + num2);
  3. }
  4. getSum(1, 3); // 4
  5. getSum(6, 5); // 11

3.2 函数参数的传递过程

  1. 调用的时候实参值是传递给形参的
  2. 形参简单理解为:不用声明的变量
  3. 实参和形参的多个参数之间用逗号(,)分隔

3.3 函数形参和实参个数不匹配问题

参数个数 说明
实参个数等于形参个数 输出正确结果
实参个数多于形参个数 只取到形参的个数
实参个数少于形参个数 多的形参定义为undefined
  1. function sum(num1, num2) {
  2. console.log(num1 + num2);
  3. }
  4. sum(100, 200); // 形参和实参个数相等,输出正确结果
  5. sum(100, 400, 500, 700); // 实参个数多于形参,只取到形参的个数
  6. sum(200); // 实参个数少于形参,多的形参定义为undefined,结果为NaN

注意

  1. JavaScript中,形参的默认值是**undefined**。
  2. 但是可以自己在设置形参的时候,给定默认值。如果不传实参,使用默认值。如果传递实参,将会覆盖掉默认值,使用实参。
  1. function sum(num1 = 10,num2 = 20){
  2. return num1 + num2;
  3. }
  4. //如果调用时没有传递参数
  5. console.log(sum()); //结果:30

3.4 小结

  • 函数可以带参数也可以不带参数
  • 声明函数的时候,函数名括号里面的是形参,形参的默认值为 undefined
  • 调用函数的时候,函数名括号里面的是实参
  • 多个参数中间用逗号分隔
  • 形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配

4 函数的返回值

4.1 return 语句

有的时候,我们会希望函数将值返回给调用者,此时通过使用 return 语句就可以实现。 return 语句的语法格式如下:

  1. // 声明函数
  2. function 函数名(){
  3. ...
  4. return 需要返回的值;
  5. }
  6. // 调用函数
  7. 函数名(); // 此时调用函数就可以得到函数体内return 后面的值
  • 在使用 return 语句时,函数会停止执行,并返回指定的值
  • 如果函数没有 return ,返回的值是 undefined

例如,声明了一个sum()函数,该函数的返回值为666,其代码如下:

  1. // 声明函数
  2. function sum(){
  3. ...
  4. return 666
  5. }
  6. // 调用函数
  7. sum(); // 此时 sum 的值就等于666,因为 return 语句会把自身后面的值返回给调用者

案例 1:利用函数求任意两个数的最大值

  1. function getMax(num1, num2) {
  2. return num1 > num2 ? num1 : num2;
  3. }
  4. console.log(getMax(1, 2));
  5. console.log(getMax(11, 2));

案例 2:利用函数求任意一个数组中的最大值

求数组 [5,2,99,101,67,77] 中的最大数值。

  1. //定义一个获取数组中最大数的函数
  2. function getMaxFromArr(numArray){
  3. var maxNum = numArray[0];
  4. for(var i =0;i < numArray.length;i++){
  5. if(numArray[i] > maxNum){
  6. maxNum = numArray[i];
  7. }
  8. }
  9. return maxNum;
  10. }
  11. var arrNum = [5,2,99,101,67,77];
  12. var maxN = getMaxFromArr(arrNum); // 这个实参是个数组
  13. alert('最大值为:'+ maxN);

4.2 return 终止函数

return 语句之后的代码不被执行。

  1. function add(num1num2){
  2. //函数体
  3. return num1 + num2; // 注意:return 后的代码不执行
  4. alert('我不会被执行,因为前面有 return');
  5. }
  6. var resNum = add(21,6); // 调用函数,传入两个实参,并通过 resNum 接收函数返回值
  7. alert(resNum); // 27

4.3 return 的返回值

return 只能返回一个值。如果用逗号隔开多个值,以最后一个为准。

  1. function add(num1num2){
  2. //函数体
  3. return num1num2;
  4. }
  5. var resNum = add(21,6); // 调用函数,传入两个实参,并通过 resNum 接收函数返回值
  6. alert(resNum); // 6

案例:创建一个函数,实现两个数之间的加减乘除运算,并将结果返回

  1. var a = parseFloat(prompt('请输入第一个数'));
  2. var b = parseFloat(prompt('请输入第二个数'));
  3. function count(a, b) {
  4. var arr = [a + b, a - b, a * b, a / b];
  5. return arr;
  6. }
  7. var result = count(a, b);
  8. console.log(result);

4.4 函数没有 return 返回 undefined

函数都是有返回值的

  1. 如果有return 则返回 return 后面的值
  2. 如果没有return 则返回 undefined

4.5 break ,continue ,return 的区别

  • break :结束当前的循环体(如 for、while)
  • continue :跳出本次循环,继续执行下次循环(如 for、while)
  • return :不仅可以退出循环(必须是函数体中的循环),还能够返回 return 语句中的值,同时还可以结束当前的函数体内的代码

通过榨汁机看透函数

50-榨汁机函数.jpg

作业

① 写一个函数,用户输入任意两个数字的任意算术运算(简单的计算器小功能),并能弹出运算后的结果。

② 写一个函数,用户输入任意两个数字的最大值,并能出弹运算后的结果。

③ 写一个函数,用户输入任意三个不同数字的最大值,并能弹出运算后的结果。

④ 写一个函数,用户输入一个数判断是否是素数,并返弹出回值(又叫质数,只能被1和自身整数的数)

5 arguments的使用

arguments

当我们不确定有多少个参数传递的时候,可以用 arguments 来获取。在 JavaScript 中,arguments 实际上 它是当前函数的一个内置对象。所有函数都内置了一个 arguments 对象,arguments 对象中存储了传递的所有实参。

arguments展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:

  • 具有 length 属性
  • 按索引方式储存数据
  • 不具有数组的 push , pop 等方法

案例:利用函数求任意个数的最大值

  1. function maxValue() {
  2. var max = arguments[0];
  3. for (var i = 0; i < arguments.length; i++) {
  4. if (max < arguments[i]) {
  5. max = arguments[i];
  6. }
  7. }
  8. return max;
  9. }
  10. console.log(maxValue(2, 4, 5, 9));
  11. console.log(maxValue(12, 4, 9));

6 函数案例

案例 1: 利用函数封装方式,翻转任意一个数组

  1. function reverse(arr) {
  2. var newArr = [];
  3. for (var i = arr.length - 1; i >= 0; i--) {
  4. newArr[newArr.length] = arr[i];
  5. }
  6. return newArr;
  7. }
  8. var arr1 = reverse([1, 3, 4, 6, 9]);
  9. console.log(arr1);

案例 2: 利用函数封装方式,对数组排序 — 冒泡排序

  1. function sort(arr) {
  2. for (var i = 0; i < arr.length - 1; i++) {
  3. for (var j = 0; j < arr.length - i - 1; j++) {
  4. if (arr[j] > arr[j + 1]) {
  5. var temp = arr[j];
  6. arr[j] = arr[j + 1];
  7. arr[j + 1] = temp;
  8. }
  9. }
  10. }
  11. return arr;
  12. }

案例 3: 判断闰年

要求:输入一个年份,判断是否是闰年(闰年:能被4整除并且不能被100整数,或者能被400整除)

  1. function isRun(year) {
  2. var flag = false;
  3. if (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0) {
  4. flag = true;
  5. }
  6. return flag;
  7. }
  8. console.log(isRun(2010));
  9. console.log(isRun(2012));

函数可以调用另外一个函数

因为每个函数都是独立的代码块,用于完成特殊任务,因此经常会用到函数相互调用的情况。

51-函数调用.jpg

因为每个函数都是独立的代码块,用于完成特殊任务,因此经常会用到函数相互调用的情况。

  1. function fn1() {
  2. console.log(111);
  3. fn2();
  4. console.log('fn1');
  5. }
  6. function fn2() {
  7. console.log(222);
  8. console.log('fn2');
  9. }
  10. fn1();

案例 4: 用户输入年份,输出当前年份2月份的天数

如果是闰年,则2月份是 29天, 如果是平年,则2月份是 28天

7 函数的两种声明方式

1 自定义函数方式(命名函数)

利用函数关键字 function 自定义函数方式。

  1. // 声明定义方式
  2. function fn() {...} // 调用
  3. fn();
  • 因为有名字,所以也被称为命名函数
  • 调用函数的代码既可以放到声明函数的前面,也可以放在声明函数的后面

2 函数表达式方式(匿名函数)

利用函数表达式方式的写法如下:

  1. // 这是函数表达式写法,匿名函数后面跟分号结束
  2. var fn = function(){...};
  3. // 调用的方式,函数调用必须写到函数体下面
  4. fn();
  • 因为函数没有名字,所以也被称为匿名函数
  • 这个fn 里面存储的是一个函数
  • 函数表达式方式原理跟声明变量方式是一致的
  • 函数调用的代码必须写到函数体后面

作业

  1. 写一个函数,实现反转任意数组。
  2. 写一个函数,实现对数字数组的排序。
  3. 做一个简易计算器

52-计算器.gif

  1. /*
  2. * 封装一个运算结果的函数、
  3. * 参数:
  4. * 1:运算种类。1+ 2- 3* 4/
  5. * 2:参与运算的两个数字
  6. * 返回值。对应的运算结果
  7. * */
  8. function calc(operation,num1, num2) {
  9. //获取四则运算的结果,
  10. let arr = [num1 + num2, num1 - num2, num1 * num2, num1 / num2];
  11. console.log(arr);
  12. //如果是操作是? 返回对应的?
  13. console.log(operation);
  14. switch (operation) {
  15. case '1':
  16. return arr[0];
  17. break;
  18. case '2':
  19. return arr[1];
  20. break;
  21. case '3':
  22. return arr[2];
  23. break;
  24. case '4':
  25. return arr[3];
  26. break;
  27. default :
  28. return 'error!'
  29. }
  30. }
  31. //声明一个变量 保存操作选项
  32. let operation;
  33. do {
  34. operation = prompt('欢迎使用马哥心算计算器\n1.加法。\n2.减法。\n3.乘法。\n4.除法。\n5.退出。');
  35. //如果是第一次就输入的5 终止循环代码
  36. if (operation === '5'){
  37. break;
  38. }
  39. var num1 = parseInt(prompt('第一个数'));
  40. var num2 = parseInt(prompt('第2个数'));
  41. //不管输入的是什么,都得到想要的结果。
  42. let res = calc(operation,num1, num2);
  43. alert('结果:' + res);
  44. } while (operation != 5);

8 JavaScript 作用域

目录:

  • 作用域
  • 变量的作用域
  • 作用域链

1 作用域

1.1 作用域概述

通常来说,一段程序代码中所用到的名字并不总是有效和可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域的使用提高了程序逻辑的局部性,增强了程序的可靠性,减少了名字冲突。

JavaScript(es6前)中的作用域有两种:

  • 全局作用域
  • 局部作用域(函数作用域)

在ES6之后,增加了块级作用域。:分支和循环。

1.2 全局作用域

作用于所有代码执行的环境(整个 script 标签内部)或者一个独立的 js 文件。

1.3 局部作用域 (函数作用域)

作用于函数内的代码环境,就是局部作用域。 因为跟函数有关系,所以也称为函数作用域。

1.4 ES6之前JS 没有块级作用域

  • 块作用域由 { } 包括。
  • 在其他编程语言中(如 java、c#等),在 if 语句、循环语句中创建的变量,仅仅只能在本 if 语句、本循 环语句中使用,如下面的Java代码:
  1. if(true){
  2. int num = 123;
  3. system.out.print(num); // 123
  4. }
  5. system.out.print(num); // 报错

Js中没有块级作用域(在ES6之前)。

  1. if(true){ .
  2. var num = 123;
  3. console.log(123); //123
  4. }
  5. console.log(123); //123
  6. //es6之后,使用let声明。有作用域。

2 变量的作用域

2.1 变量作用域的分类

在JavaScript中,根据作用域的不同,变量可以分为两种:

  • 全局变量
  • 局部变量

2.2 全局变量

在全局作用域下声明的变量叫做全局变量(在函数外部定义的变量)

  • 全局变量在代码的任何位置都可以使用
  • 在全局作用域下 var 声明的变量 是全局变量
  • 特殊情况下,在函数内不使用 var 声明的变量也是全局变量(不建议使用)

2.3 局部变量

在局部作用域下声明的变量叫做局部变量(在函数内部定义的变量)

  • 局部变量只能在该函数内部使用
  • 在函数内部 var 声明的变量是局部变量
  • 函数的形参实际上就是局部变量

2.4 全局变量和局部变量的区别

  • 全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
  • 局部变量:只在函数内部使用,当其所在的函数代码块被执行时,会被初始化;当函数代码块运行结束后,就会被销毁,因此更节省内存空间

3 作用域链

  • 只要是代码,就至少有一个作用域
  • 写在函数内部的局部作用域
  • 如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域
  • 根据在内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称为作用域链

案例 1: 结果是几?

  1. function f1() {
  2. var num = 123;
  3. function f2() {
  4. console.log( num );
  5. }
  6. f2();
  7. }
  8. var num = 456;
  9. f1();

案例分析

53-作用域链.jpg

作用域链:采取就近原则的方式来查找变量最终的值。

案例 2: 结果是几?

  1. var a = 1;
  2. function fn1() {
  3. var a = 2;
  4. var b = '22';
  5. fn2();
  6. function fn2() {
  7. var a = 3;
  8. fn3();
  9. function fn3() {
  10. var a = 4;
  11. console.log(a); //a的值 ?
  12. console.log(b); //b的值 ?
  13. }
  14. }
  15. }
  16. fn1();

9 JavaScript 预解析

目标:

  • 能够知道解析器运行 JS 分为哪两步
  • 能够说出变量提升的步骤和运行过程
  • 能够说出函数提升的步骤和运行过程

目录:

  • 预解析
  • 变量预解析和函数预解析
  • 预解析案例

1 预解析

JavaScript 代码是由浏览器中的 JavaScript 解析器来执行的。JavaScript 解析器在运行 JavaScript 代码的 时候分为两步:预解析和代码执行。

  • 预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中 进行提前声明或者定义。
  • 代码执行: 从上到下执行JS语句。

预解析只会发生在通过 var 定义的变量和 function 上。学习预解析能够让我们知道为什么在变量声明之前 访问变量的值是 undefined,为什么在函数声明之前就可以调用函数。

2 变量预解析和函数预解析

2.1 变量预解析(变量提升)

预解析也叫做变量、函数提升。
变量提升: 变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。

  1. console.log(num); // 结果是多少?
  2. var num = 10; // ?

2.2 函数预解析(函数提升)

函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。

  1. fn();
  2. function fn() {
  3. console.log('马哥最帅');
  4. }

2.3 解决函数表达式声明调用问题

  1. fn();
  2. var fn = function() {
  3. console.log('想不到吧');
  4. }

练习: 结果是几?

  1. // 练习
  2. alert(a);
  3. var a = 1;
  4. alert(a)
  5. function a(){
  6. return false;
  7. }

3 预解析案例

案例 1: 结果是几?

  1. // 案例1
  2. var num = 10;
  3. fun();
  4. function fun() {
  5. console.log(num);
  6. var num = 20;
  7. }

案例 2: 结果是几?

  1. // 案例2
  2. var num = 10;
  3. function fn(){
  4. console.log(num);
  5. var num = 20;
  6. console.log(num);
  7. }
  8. fn();

案例 3: 结果是几?

  1. // 案例3
  2. var a = 18;
  3. f1();
  4. function f1() {
  5. var b = 9;
  6. console.log(a);
  7. console.log(b);
  8. var a = '123';
  9. }

案例 4: 结果是几?

  1. f1();
  2. console.log(c);
  3. console.log(b);
  4. console.log(a);
  5. function f1() {
  6. var a = b = c = 9;
  7. console.log(a);
  8. console.log(b);
  9. console.log(c);
  10. }