10.函数
10.1 函数的基础知识
为什么会有函数?
在写代码的时候,有一些常用的代码需要书写很多次,如果直接复制粘贴的话,会造成大量的代码冗余;
函数可以封装一段重复的
javascript代码,它只需要声明一次,就可以多次调用;
冗余代码:
-  冗余: 多余的重复或啰嗦内容
-  缺点: -  代码重复,可阅读性差 
-  不易维护,如果代码逻辑变了,所有地方的代码都要跟着改,效率太低了 
 
-  
函数使用分为两步:声明函数 和 调用函数
10.2函数的使用
10.2.1 声明函数
函数声明的语法:
var是用来声明变量的, 函数是用function来声明的,一个函数一般是用来做一件事情的。
function 函数名 (){
    //函数体
} 
//例如:
function sayHi(){
    console.log("Hi~~")
} 
注意:
-  function 声明函数的关键字,必须小写 
-  函数是做某件事情,函数名一般是动词,如:sayHi 
-  函数不调用自己不执行 
10.2.2 调用函数
调用函数的语法:
函数名();
函数体只有在调用的时候才会执行,调用需要()进行调用。可以调用多次
示例代码:
// 声明函数
function sayHi (){
    // 函数体
    console.log("Hi!!");
}
// 调用这个函数
sayHi();  // console.log("Hi!!");
// 注意
console.log(sayHi);  // 打印的是整个函数 
// sayHi:指的就是这个函数
// ():指的是调用
// sayHi():这个函数的调用结果 
注意:
-  调用函数的时候千万不要忘记加小括号 
-  函数不调用自己不执行 
10.2.3 声明函数的两种方式
1、函数声明(命名函数):
// 声明一个函数并且命名了
function 函数名(){
    函数体;
}
函数名();  // 调用函数
/********示例代码***********/
function fn(){
    console.log("啊哈哈");
}
fn(); 
2、函数表达式(匿名函数):
// 必须先声明才能调用
var 函数名 = function(){
    函数体;
}
函数名(); // 调用函数
/********示例代码***********/
var fn = function(){
    console.log("啊哈哈");
}
fn(); 
这两种函数的区别:
-  命名函数可以先调用,再声明,因为预解析 
-  函数表达式必须先声明,再调用(在 DOM中注册事件的时候用的非常的多)
匿名函数:
没有名字
的函数,叫做匿名函数。匿名函数没有办法直接用,需要赋值给变量或者自调用
自调用函数也叫自执行函数,声明和调用一起
-  可以防止变量全局污染 
-  匿名函数自调用示例代码: 
(function(num1,num2){
    console.log(num1);        // 1
    console.log(num2);        // 2
    var name = "张三"
    var age = 18;
    function sayHello() {
      console.log(age);     // 18
      console.log(name);    // "张三"
    }
    sayHello();
})(1,2) 
 
10.3 函数的封装
-  函数的封装是把一个或者多个功能通过函数的方式封装起来,对外只提供一个简单的函数接口 
-  简单理解:封装类似于将电脑配件整合组装到机箱中(类似快递打包) 
 正在上传…重新上传取消
正在上传…重新上传取消
10.4 函数的参数
形参和实参
在声明函数时,可以在函数名称后面的小括号中添加一些参数,这些参数被称为形参,而在调用该函数时,同样也需要传递相应的参数,这些参数被称为实参。
形式参数: 在声明一个函数的时候,为了函数的功能更加灵活,有些值是固定不了的,对于这些固定不了的值。我们可以给函数设置参数。这个参数没有具体的值,仅仅起到一个占位置的作用,我们通常称之为形式参数,也叫形参。
实际参数: 如果函数在声明时,设置了行参,那么在函数调用的时候就需要传入对应的参数,我们把传入的参数叫做实际参数,也叫实参。
| 参数 | 说明 | 
|---|---|
| 形参 | 形式上的参数函数,定义的时候传递的参数当时并不知道是什么 | 
| 实参 | 实际上的参数,函数调用的时候传递的参数实参是传递给形参的 | 
参数的作用:在函数内部某些值不能固定,我们可以通过参数在调用函数时传递不同的值进去。
语法:
//带参数的函数声明
function 函数名(形参1, 形参2, 形参...){
  //函数体
}
//带参数的函数调用
函数名(实参1, 实参2, 实参3); 
// 1.函数可以重复相同的代码
    function cook() {
        console.log('酸辣土豆丝');
    }
    cook();
    cook();
// 2.我们可以利用函数的参数实现函数重复不同的代码
    function 函数名(形参1,形参2...){           //在声明函数的小括号里面是形参(形式上的参数)
    }
    函数名(实参1,实参2...);                    //在函数调用的小括号里面是实参(实际的参数)
// 3.形参和实参的执行过程
    function cook(aru) {                    //形参是接受实参的aru =‘酸辣土豆丝·形参类似于一个变量
        console.log(aru);
    }
    cook('酸辣土豆丝');
    cook('大肘子');
//4.函数的参数可以有,也可以没有个数不限 
案例:
// 1.利用函数求任意两个数的和
    function getSum(num1,num2) {
        console.log(num1 + num2);
    }
    getSum(1,3);                //结果:4
    getSum(3,8);                // 11
// 2.利用函数求任意两个数之间的和
    function getsums(start, end) {
        var sum = e;
        for ( var i = start; i <= end; i++)(
            sum += i;
        }
    console.log(sum);
    }
    getSums(1,100);             // 5050
    getsums(1,10);              // 55
// 3.注意点
//(1)多个参数之间用逗号隔开
//(2)形参可以看做是不用声明的变量 
10.4.1函数形参实参个数不匹配问题:
| 参数个数 | 说明 | 
|---|---|
| 实参个 = 形参个数 | 输出正确结果 | 
| 实参个数 > 形参个数 | 只取到形参的个数(多余参数不执行) | 
| 实参个数 < 形参个数 | 多的形参定义为undefined,结果为NaN | 
特点:
-  在函数调用的时候,需要传递对应的参数,把实参的值赋值给形参。 
-  实参如果多于形参的个数:多传的参数就丢弃了
-  实参如果少于形参的个数:没有传的参数,值就是undefined。(容易出问题)
//函数形参实参个数匹配
function getSum(num1,num2) {
console.log(num1 + num2);
}
// 1.如果实参的个数和形参的个数一致,则正常输出结果
getsum(1,2);
// 2.如果实参的个数多于形参的个数     会取到形参的个数(多余参数不执行)
getsum(1,2,3);
// 3.如果实参的个数小于形参的个数     多于的形参定义为undefined   最终的结果就是NaN
//形参可以看做是不用声明的变量num2是一个变量但是没有接受值﹑结果就是undefined
getSum(1);                          // NaN
//建议我们尽量让实参的个数和形参相匹配 
10.4.2 小结
-  函数可以带参数也可以不带参数 
-  声明函数的时候,函数名括号里面的是形参,形参的默认值为undefined调用函数的时候,函数名括号里面的是实参 
-  多个参数中间用逗号分隔 
-  形参的个数可以和实参个数不匹配,但是结果不可预计,我们尽量要匹配 
10.5 函数的返回值
10.5.1 return介绍
当函数执行完的时候,我们期望函数给我一些反馈(比如计算的结果),这个时候可以让函数返回一些东西。也就是返回值。函数通过
return返回一个返回值
// 1.函数是做某件事或者实现某种功能 
    function cook( aru) {
        console.log(aru);   //在函数内部写输出函数其实是不合理的
    }
    cook('大肘子');
//标准写法:
    function cook( aru) {
        return aru;
    }
    console.log( cook('大肘子'));
// 2.函数的返回值格式
    function函数名(){
        return需要返回的结果;
    }
    函数名();
//(1)我们函数只是实现某种功能,最终的结果需要返回给函数的调用者"函数名()"通过 return实现的
//(2)只要函数遇到return 就把后面的结果返回给函数的调用者﹐函数名() = return后面的结果
//3.代码验证
    function getResult(){
        return 666;
    }
    getResult();                    // 相当于 getResult() = 666
    console.log(getResult());
    
// 4.求任意两个数的和
    function getSum( num1,num2) {
        return num1 + num2;
    }
    console.log(getSum(1,,2));  // 3 
返回值语法:
//声明一个带返回值的函数
function 函数名(形参1, 形参2, 形参...){
  //函数体
  return 返回值;
}
//可以通过变量来接收这个返回值
var 变量 = 函数名(实参1, 实参2, 实参3);
console.log(变量);    // 输出这个变量值 
函数的调用结果就是返回值,因此我们可以直接对函数调用结果进行操作。
示例代码1:
// 计算 n1- n2之间所有数的乘积
function getProduct(n1, n2) {
    var product = 1;
    for (var i = n1; i <= n2; i++) {
        product *= i;
    }
    return product; // 返回计算的值
}
var pro = getProduct(1, 5); // 用变量pro接收一下返回的值
console.log(pro);   // 120 
示例代码2:
//利用函数求数组[5,2,99,100,66,88]中的最大数值。
    function getArrMax( arr) {      // arr接受一个数组
        var max = arr[e];
        for (var i =1; i <= arr.length; i++){
            if (arr[i]>max) {
                max = arr[i];
            }
        }
        return max;
    }
var re = getArrMax([5,2,99,100,66,88]);
console.log(re); 
10.5.2 return 注意事项
-  return终止函数 
//函数返回值注意事项
// 1. return终止函数I
    function getsum(num1,num2) {
        return num1 + num2;     // return后面的代码不会被执行alert('我是不会被执行的哦!')
    }
    console.log(getSum(1,2)); 
-  return只能返回一个值 
// 2. return只能返回一个值
    function fn(num1, num2) {
        return num1,num2;//返回的结果是最后一个值
    }
    console.log(fn(1,2)); 
-  return可以用数组的方法返回多个值 
// 3.我们求任意两个数的加减乘数结果
    function getResult(num1,num2){
        return [num1 + num2, num1 - num2,num1 * num2, num1 / num2];
    }
    var re = getResult(1,2);    //返回的是一个数组console.log(re); 
-  函数没有return返回undefined 函数都是有返回值的 -  如果有return则返回return后面的值 
-  如果没有return则返回undefined 
 
-  
    function fun1() i
        return 666;
    }
    console.log(fun1());    //返回666
---------------------------------------------------------
    function fun2() {
    }
    console.log(fun2());    //函数返回的结果是undefined 
 
10.5.3 break ,continue ,return的区别
-  break:结束当前的循环体(如for、while ) 
-  continue :跳出本次循环,继续执行下次循环(如for、while ) 
-  return : 不仅可以退出循环,还能够返回return语句中的值,同时还可以结束当前的函数体内的代码(即退出当前函数) 
10.6 函数三要素
函数三要素包括:
-  函数名 
-  参数 
-  返回值 
10.7 文档注释
关于文档注释,javascript中还有一种注释叫做文档注释,经常用在函数
声明处,用来解释这个函数的作用。
文档注释: /** 这是文档注释 */
以后写的函数的声明,都应该加上文档注释,方便阅读
示例代码:
/**
 * 求圆的面积
 * @param r {number} 圆的半径
 * @returns {number} 圆的面积
 */
function getArea (r) {
    return Math.PI * r * r;
} 
 
10.8 arguments的使用
当我们不确定有多少个参数传递的时候,可以用arguments来获取。在JavaScript中,arguments实际上它是当前函数的一个内置对象。所有函数都内置了一个
arguments对象,arguments对象中存储了传递的所有实参。
arguments展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:
-  具有length属性 
-  按索引方式储存数据 
-  不具有数组的push , pop等方法 
注:只有函数才有arguments对象,而且是每个函数都内置好了这个arguments
// arguments的使用
    function fn() {
        console.log(arguments);             //里面存储了所有传递过来的实参    arguments = [1,2,3]
        console.log(arguments.length);
    }
    fn(1,2,3); 
运算结果:
 正在上传…重新上传取消
正在上传…重新上传取消
获取数组里面某一个元素的方法:
function fn() {
      console.log(arguments[2]);    // arguments[索引号]
    }
    fn(1, 2, 3, 4, 5); 
运算结果:
 正在上传…重新上传取消
正在上传…重新上传取消
按照数组的方式遍历arguments:
 function fn() {
      for (var i = 0; i < arguments.length; i++) {
        console.log(arguments[i]);
      }
    }
    fn(1, 2, 3);
    fn(1, 2, 3, 4, 5); 
运算结果:
 正在上传…重新上传取消
正在上传…重新上传取消
10.9 函数综合练习
1. 利用函数求任意个数的最大值
    function getMax() {
      var max = arguments[0];
      for (var i = 1; i < arguments.length; i++) {
        if (arguments[i] > max) {
          max = arguments[i];
        }
      }
      return max;
    }
    console.log(getMax(1, 2, 3));
    console.log(getMax(1, 2, 3, 4, 5));
    console.log(getMax(21, 2, 71, 666, 5, 100)); 
运算结果:
 正在上传…重新上传取消
正在上传…重新上传取消
2. 利用函数封装方式,翻转任意一个数组
    //利用函数翻转任意数组 reverse翻转
    function reverse(arr) {
      var newArr = [];
      for (var i = arr.length - 1; i >= 0; i--) {
        newArr[newArr.length] = arr[i];
      }
      return newArr;
    }
    var arr1 = reverse([9, 7, 5, 3, 1]);
    console.log(arr1);
    var arr2 = reverse(['red', 'green', 'blue', 'yellow']);
    console.log(arr2);
 
运算结果:
 正在上传…重新上传取消
正在上传…重新上传取消
3. 利用函数封装方式,对数组排序 -- 冒泡排序
    //利用函数冒泡排序sort排序
    function sort(arr) {
      for (var i = 0; i < arr.length - 1; i++) {
        for (var j = 0; j < arr.length - i - 1; j++) {
          if (arr[j] > arr[j + 1]) {
            var temp = arr[i];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
          }
        }
      }
      return arr;
    }
    var arr1 = sort([1, 5, 2, 9]);
    console.log(arr1);
    var arr2 = sort([12, 7, 66, 999]);
    console.log(arr2); 
运算结果:
 正在上传…重新上传取消
正在上传…重新上传取消
4. 求任意数的阶乘(从1到n的积)
function getProduct (n){
    var product = 1; 
    for(var i = 1; i <= n; i++){
       product *= i; 
    }
    return product;
}
console.log(getProduct(5));  // 120
console.log(getProduct(3));  // 6    
 
5. 求任意数组中的最大值与最小值
function getMaxAndMin(arr) {
    var max = arr[0];
    var min = arr[0];
    for (var i = 0; i < arr.length; i++) {
        if (max < arr[i]) {
            max = arr[i];
        }
        if (min > arr[i]) {
            min = arr[i];
        }
    }
    return [max, min]; // 返回一个数组
}
console.log(getMaxAndMin([11, 45, 59, 12, 8, 36, 14, 25]));  // [59 8] 
 
10.10函数的作用域
在函数中,只有
全局作用域和函数作用域,因为在if、while、for等语句中定义的变量都是全局变量。
全局变量: 在最外层声明的变量就是全局变量,全局变量在任何地方都能访问的到。
局部变量: 在函数中声明的变量,就是局部变量,局部变量只有在当前函数体内能够访问。
隐式全局变量: 没有使用var定义的变量也是全局变量。
作用域: 变量可以发挥作用的区域
全局作用域: 在script标签内,函数外定义的作用域就是全局作用域。在全局作用域中定义的变量都是全局变量。
函数作用域: 在函数中的区域叫做函数作用域,在函数作用域中定义的变量就是局部变量,只能在当前函数内访问。
10.11 预解析
js解析器执行js代码的时候,分为两个过程:
预解析过程和代码执行过程
预解析过程:
-  把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。 
-  把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。 
-  先提升 var,再提升function
预解析例题:
第一题:
console.log(a);    // 打印a这个函数整体
var a = 1;
function a(){
console.log("呵呵");
}
console.log(a);    // 1
// 预解析后为
/**
var a;
function a(){
    console.log("呵呵");
}
console.log(a);    // 打印a这个函数整体
a = 1;
console.log(a);    // 1
*/ 
第二题:
var num = 10;
fn1();
function fn1() {
    //在函数调用的时候,这个函数也会做预解析操作。
    console.log(num);   // undefined
    var num = 20;
    console.log(num);   // 20
}
console.log(num);       // 10
// 预解析后为
/**
var num ;
function fn1() {
    var num;
    console.log(num);   // undefined
    num = 20;
    console.log(num);   // 20
}
num = 10;
fn1();
console.log(num);       // 10
*/ 
第三题:
var a = 18;
var b = 30;
fn();
function fn() {
    var b = 9;
    console.log(a); // undefined
    console.log(b); // 9
    var a = 20;
}
// 预解析后为
/**
var a;
var b;
function fn() {
    var b;
    b = 9;
    var a;
    console.log(a);     // 自己作用域里有的就不要出去找
    console.log(b);     // 9
    a = 20;
}
a = 18;
b = 30;
fn();
*/ 
第四题:
fn();
var b = 10;
console.log(c); // 9
console.log(b); // 10
console.log(a); // 报错
function fn() {
    var a = 9;
    b = 9;
    c = 9;
    console.log(a); // 9
    console.log(b); // 9 
    console.log(c); // 9
}
// 预解析之后
/**
var b;
function fn() {
    var a;
    a = 9;
    b = 9;
    c = 9;
    console.log(a); // 9
    console.log(b); // 9
    console.log(c); // 9
}
fn();
b = 10;
console.log(c); // 9
console.log(b); // 10
console.log(a); // 报错
*/ 
第五题:
function fn() { 
    console.log(num1);  // undefined
    console.log(num2);  // undefined
    console.log(num3);  // 30
    var num1 = 10;
    var num2 = 20;
    num3 = 40;
    console.log(num1);  // 10
    console.log(num2);  // 20
    console.log(num3);  // 40
}
var num1 = 20;
var num3 = 30;
fn();
console.log(num1);      // 20
console.log(num3);      // 40
console.log(num2);      // 报错
// 预解析之后
/**
var num1;
var num3;
function fn() {
    var num1;
    var num2;
    console.log(num1); // undefined
    console.log(num2); // undefined
    console.log(num3); // 30
    num1 = 10;
    num2 = 20;
    num3 = 40;
    console.log(num1); // 10
    console.log(num2); // 20
    console.log(num3); // 40
}
num1 = 20;
num3 = 30;
fn();
console.log(num1); // 20
console.log(num3); // 40
console.log(num2); // 报错
*/ 
10.12 递归函数
函数直接或者间接调用自己,必须要留
出口,不然就调死了
示例代码:
// 用递归求1-100的和
/* 
    之前封装过一个getSum的函数比如getSum(100),就是求的1-100的和
    现在我们可以这样理解:
    1-100的和我们可以看做是 100 + getSum(99)
    getSum(99) 可以看成 99 + getSum(98)。。。
    依次这样推下去,但是要注意,到getSum(1)的时候,要留出口,否则会一直死循环下去
 */
function getSum(n) {
    if (n == 1) {       // 一定要留出口
        return 1;
    }
    return n + getSum(n - 1);
}
console.log(getSum(100)); 
10.13 回调函数
回调函数:把函数当成参数来使用,那么这个函数就叫回调函数。函数也是一种数据类型
示例代码:
/*
思考,之前封装了一个bubbleSort排序的函数,但是只能排元素是数字的数组
现在想要判断字符串的长度,或者对象的属性的时候就很麻烦,就需要重新写一个函数
比如字符串长度,就需要是arr[j].length - arr[i+1].length
*/
function bubbleSort(arr, fn) {
    for (var i = 0; i < arr.length; i++) {
        var flag = true;
        for (var j = 0; j < arr.length - 1 - i; j++) {
        
            // 传一个函数进来,并且将arr[j], arr[j + 1]作为两个参数传进去
            if (fn(arr[j], arr[j + 1]) > 0) {
                flag = false;
                var temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        if (flag) {
            break;
        }
    }
}
// 纯数字数组
var arr = [4, 3, 1, 6, 22, 21, 41, 4];
// 调用的时候,我们需要将fn函数的两个参数也传进去
// 这种把一个函数作为参数传进另一个函数的方式就叫回调函数
bubbleSort(arr, function(a, b) { // a b 就相当于 arr[j] 和 arr[j+1]
    return a - b;
    // 如果是 return b - a 就相当于,上面的 arr[j+1] - arr[j]>0 那么就是从大到小排序 
});
console.log(arr);
// 封装后的排序函数也可以直接根据字符串的长度进行排序了
var arrStr = ["aaa", "bb", "cccc", "d"];
bubbleSort(arrStr, function(a, b) {
    // 因为传进去的是一个函数,arr[j] 和 arr[j+1]是以两个参数的形式传进去的
    // 当数组元素是字符串的时候,就可以进行.length操作了
    return a.length - b.length;
});
console.log(arrStr);
                


















