第一部分、初识函数
1.什么是函数
在编写代码时,可能会出现非常多的相同代码,或者功能类似的代码,这些代码可能需要大量重复使用,此时就可以使用JavaScript中的函数。
2.函数的使用
函数在使用时分为两步,声明函数和调用函数。
(1)函数的声明
function name(params) {
    函数体语句;
}
 
注意:
- “function”:是关键字,必须小写。
 - “函数名”(name):用户标识符,敬意采用驼峰命名(第一个单词是动词,后面的单词首字母大写)尽量做到"见名知义"。
 - “参数”(params):函数可以有参数,也可以没有参数。"()"是不能省略的。
 - ‘{}’:表示函数的作用范围。
 - 函数定义后并不会执行,只有当函数调用后才会执行。
 
(2)函数的调用
- 直接调用:
 
函数名(参数)
 
- 触发事件调用:
 
时间名 = 函数名(参数)
 
例:
//函数的声明
function getSum(a,b) {
    var sum = a+b;
    return sum;
}
//函数的调用
var s = getSum(10,25);
console.log('s=',s);
 
3.函数的参数
(1)形参(形式参数)
形参(形式参数):在函数定义时出现在函数首部的参数,只是占位符,没有实际的数据。
(2)实参(实在参数)
实参(实在参数):在函数调用时出现在函数首部的参数,是有确定值的变量或常量。
//函数的声明
function getSum(a,b) {    //a,b是形参,在定义时没有确定的值,只是占位符。
    var sum = a+b;
    return sum;
}
//函数的调用
var k=10,t=25;
var s = getSum(k,t);    //k,t是实参
console.log('s=',s);
 
(3)参数之间的数据传递
实参将数据传递给形参(按值传递)。
- 传递的方向是单向的,只能由实参传递给形参。
 - 数据传递时实参和形参按从左向右一一对应区配,与名称无关。
例:参数传递是单向的。 
// 参数传递是单向的:只可以是实参传递给形参,形参发生改变时不会影响实参
function swap(a,b){
    console.log("形参,a="+a+",b="+b);
    var temp = a;
    a = b;
    b = temp;
    console.log("形参,a="+a+",b="+b);
}
var t=10,k=25;
console.log("实参:t="+t+",k="+k);
swap(t,k);
console.log("实参:t="+t+",k="+k);
 

4.函数参数的数量
- 当实参数量多于形参数量时,函数正常执行,多余的实参会被忽略。
 - 当实参数量小于形参数量时。多出来的形参类似于一个已声明但未赋值的变量,其值时undefined。
 
function fun(a,b){
    console.log("a="+a+",b="+b);
}
var i=1,j=2,k=3;
fun(i,j,k);    //实参多于形参  结果为a=1,b=2
fun(i);    //实参小于形参  结果为a=1,b=undefined
 
5.函数的返回值
函数的返回值可以将函数的处理结果返回,用于根据函数的执行结果来决定下一步要做的事情,函数的返回值通过return语句实现。
function 函数名(){
    return要返回的值;    //利用return返回一个值给调用者
}
 
例:定义一个函数判断一个数是否是素数。
// 定义一个函数判断一个数是否是素数。
function isPrime(n){
    for(var i=2;i<n;i++){
        var flag = true;
        if (n%i===0) {
        
            flag = false;
            break;
        }
    }
    if (flag) {
        console.log(n+"是素数!");
    }else{
        console.log(n+"不是素数!");
    }
}
isPrime(4);    //结果为4不是素数!
isPrime(5);    //结果为5是素数!
 
例:定义一个函数实现对数组的排序(插入法)
//定义一个函数实现对数组的排序(插入法)
function fun(arr){
    for(var i=0;i< arr.length;i++){
        for(var j=i;j>=0;j--){
            if (arr[j]>arr[j+1]) {
                var temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}
var arr = [4,2,3,1];
fun(arr);//实参是数组名,数组名表示数组的首地址
console.log(arr);
 
6.argumens
当不确定函数中接收到了多少个实参的时候,可以用arguments来获取实参。这是因为arguments是当前函数的一个内置对象(是一个数组),所有函数都内置了一个arguments对象,该对象保存了函数调用时传递的所有的实参。
- 内置对象:不管你是否定义,它都是存在的。
 
function fn(){
    console.log(arguments);    //输出所有的实参
    console.log(arguments.length);    //输出实参的个数
    console.log(arguments[0]);    //输出第一个实参
}
fn(1,2,3);
 

第二部分、函数案例
1.求三个数中的最大值
//1.利用函数求三个数中的最大值
function getMax(a,b,c){
    
    var max1=a>b?a:b;
    
    var max=max1>c?max1:c;
    return max;
}
var t = getMax(3,2,1)
console.log(t);
 
2.求数组中的最大值
//2.利用函数求数组中的最大值
function getMax(arr){
    var max = arr[0];
    for(var i=1;i<arr.length;i++){
        if (max<arr[i]) {
            max = arr[i];
        }
    }
    return max;
}
var a = [1,3,2,4];
console.log(getMax(a));
 
3.求所有参数中的最大值
//3.求所有参数中的最大值
function fn(){
    var max = arguments[0];
    for(var i=1;i<arguments.length;i++){
        if (max<arguments[i]) {
            
            max = arguments[i];
        }
    }
    return max;
}
console.log(fn(2,4,1,3));
 
第三部分、函数进阶
1.回调函数
回调函数:将函数A作为参数传递给函数B,在函数B中调用函数A,函数A就称为回调函数。
function cal(num1,num2,fn){     //fn是回调函数
    var k = fn(num1,num2);
    return k;    //对fn进行调用
}
function f1(n1,n2){
    return n1+n2;
}
var m =cal(100,200,f1);    //函数名代表函数的入口地址
console.log(m);
 
2.递归调用
(1)什么是递归
递归:函数自己调用自己。
(2)用递归解决问题的条件
- 问题可以分解。
 - 分解后得到的新问题的解法与原问题的解法相同。
 - 分解的规程要有明确的结束条件
 
(3)递归的过程
-  
自上而下分解问题。
 -  
自下而上回溯得到问题的解。
 
// 用递归求n!
function fac(n){
    if (n==1) {    //递归结束的条件
        return 1;
    }else{
        return n*fac(n-1);    //递归调用:函数自己调用自己
    }
}
console.log("5!=",fac(5));    //结果为120
 
第四部分、作用域
1.全局变量
全局作用域(全局变量):在函数外部定义的变量或在函数内部没有使用var声明的变量。在浏览器页面没有关闭之前一直占用内存空间。比较耗费内存,在浏览器页面关闭时才释放内存。
2.局部变量
局部作用域(局部变量):在函数内部用var关键字定义的变量。只在函数内部起作用,函数调用结束后,局部变量所占用的内存就会被释放。
3.块级变量
块级作用域(块级变量):ES6(ECMAScript 2016)使用let声明的变量,作用范围在语句块中。
for(let i=1;i<=100;i++){
    
}
 
4.全局变量和局部变量的区别
- 在全局作用域下,添加或省略var关键字都可以声明全局变量,全局变量在浏览器关闭页面的时候才会销毁,比较占用内存资源。
 - 在函数中,添加var关键字声明的变量是局部变量,省略var关键字时,如果变量在当前作用域下不存在,会自动向上级作用域查找变量。局部变量在函数执行完成后就会销毁,比较节约内存资源。
 
5.作用域链
作用域链:当在一个函数内部声明另一个函数时,内层函数只能在外层函数作用域内执行,在内层函数执行的过程中,若需要引入某个变量,首先会在当前作用域中寻找,若未找到,则继续向上一层级的作用域中寻找,直到全局作用域,称这种链式的查询关系为作用域链。
var s = 0;
function fun(t){
    console.log("t=",t);
    function f2(){    //函数的声明
        console.log("s=",s);    //链式的查询关系
    }
    
    f2();    //函数的调用(只可以在fun(t)内部调用)
}
fun(110);
 
第五部分、闭包函数
所谓“闭包”指的就是有权访问另一函数作用域内变量(局部变量)的函数。
1.闭包函数的作用
- 可以在函数的外部访问函数内部的变量
 - 可以让变量的值始终保存在内存中
 
注意:
 由于闭包会使得函数中的变量一直被保存在内存中,内存消耗很大,所以闭包的滥用可能会降低程序的处理速度,造成内存消耗等问题。
2.闭包函数的实现
闭包的常见创建方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量可以在函数外部序曲函数内部的变量。
 实例:
function fn(){
    var times = 0;    //times是一个局部变量
    
    var c = function(){    //"闭包":在它的内部可以返回顾问外部函数fn的局部变量times
        return ++times;
    }
    return c;
}
var count = fn();    //将函数fn赋给变量count
console.log(count());    //实际执行的就是函数fn,结果为1
console.log(count());    //结果为2
console.log(count());    //结果为3 
 
第六部分、JavaScript的预解析
预解析:先对var变量和function函数进行解析,然后再去执行其他代码。
1.var预解析
/*原因:JavaScript解析器先解析var num
然后输出num
最后var num = 10
*/
console.log("num=",num);    //结果为undefined(相当于只声明变量不给变量赋值)
var num = 10;
 
2.function函数的预解析
/*
    先解析函数fn()
    在执行console语句
*/	 
console.log("1到100的和为",fn());
function fn(){
    var s = 0;
    for(var i=1;i<=100;i++){
        s += i;
    }
    return s;
}	










