第七章 函数

1 函数的概念

1.1 什么是函数

函数具有某种特定功能的代码块。

函数其实本质也是一种数据,属于对象数据类型

1.2 为什么要有函数

1)解决代码的冗余问题,形成代码复用。

2)可以把整个代码项目,通过函数模块化。

3) 封装代码,让函数内部的代码对外部不可见。

2. 函数的组成

函数的声明:

函数的调用:

函数由如下部分组成:

  • 函数名,命名规则同变量名一致。
  • 函数体, 函数的内容,代码块。
  • 参数, 分为形参实参
  • 返回值, 函数调用表达式的结果

3 定义函数的三种方式

  • function关键字方式/字面量方式

    function 函数名() {
    
    }
    function 函数名(参数) {
    
    }
    
  • 表达式方式

    var 函数名 = function(){
    
    }
    var 函数名 = function(参数) {
    
    }
    
  • Function构造函数方式

    var 函数名 = new Function('函数体');
    var 函数名 = new Function('参数', '函数体')
    

4 函数调用

1) 在函数名后面加上 () 就是对函数的调用,函数内的代码会执行。

2) 函数名后面不加() 不会调用函数,函数内的代码也不会执行;函数名本质上是个变量名,通过函数名可以引用到函数本身。

5 函数的返回值

5.1 返回值

1)函数名() 被称之为函数调用表达式, 表表达式的值就是函数的返回值

2)在函数体内,return 右边的表达式(或变量、直接量)便是函数的返回值。

3)函数体内没写 return 或者 return 的右边是空的,默认默认会返回 undefined

4)return 除了设置返回值外,还可以结束函数的执行,return 之后的代码不会执行。

5.2 那些函数需要些返回值

什么样的函数需要写返回值?

如果函数的作用是进行某种计算,得到的计算结果最后以返回值的形式返回。

什么样的函数不需要返回值?

函数的功能是实现某个具体的操作(界面操作),无需返回值。

6 函数的参数

6.1 形参和实参

形参: 声明函数的时候,给的参数, 类似于变量名;在声明函数的时候,参数是没有值。

实参:调用函数是给的参数; 实参会按照顺序赋值给形参。

6.2 形参和实参的数量问题

正常情况下,实参数量应该等于形参数量。

如果实参数量大于形参数量, 多出来的实参,将被忽略。

如果实参数量小于形参数量, 有的形参没有对应的实参,取默认值 undefined。

6.3 形参的默认值

JS函数允许形参有默认值,有默认值的形参,在调用函数的时候,可以没有与之对应的实参!

如何实现形参的默认值?

旧版语法:

function demo(a,b) {
    // 判断形参的值,是否是undefined,如果是undefined说明函数调用的时候没有给值,可以设置默认值。
        if (b === undefined) {
            b = 默认值
        }
}

新版语法:

function demo(a, b=默认值) {

}

注意: 有默认值的形参一定要放在后面!

6.4 arguments

arguments 只能在函数内使用。

arguments 是一个类数组对象,具有数组的一些特性。

arguments可以获取所有的实参,所以我门想获取实参的话有两种方式:①用形参;②使用arguments。

可以用来定义可变参数数量的函数:如计算所有参数和,取参数中的最大值,取参数中的最小值,求所有参数平均数。

/**
 * 取所有参数里面的最大值
 */
function max() {
  //设置遍历 默认值的最大值
  var res = arguments[0];
  // 循环比较
  for (var i = 0; i < arguments.length; i ++) {
    if (arguments[i] > res) {
      res = arguments[i];
    }
  }
  // 返回结果
  return res;
}

7 函数的嵌套

函数体内是可以再嵌套函数的。

/**
 * 冒泡排序
 * @params arr Array 需要进行排序的数组
 * @params isUp boolean 是否从大到小; 默认值是false(从小到大)
 * @return 排好序的数组
 */
function sortArr(arr, isUp) {
  // 数组遍历
  for (var i = 0; i < arr.length; i ++) {
    // 进行比较。把最大的元素搞到最后面取
    for (var j = 0; j < arr.length-1-i; j ++) {
      // 先判断是从大到小还是从小到大
      if (isUp) { //从大到小
        //如果前面的元素比后面的小,进行交换
        if (arr[j] < arr[j+1]) {
          exchange();
        }
      } else { //从小到大
        //如果前面的元素比后面的大,进行交换
        if (arr[j] > arr[j+1]) {
          exchange();
        }
      }
    }
  }
  // 把排好序的数组返回
  return arr;

  // 交换
  function exchange(){
    var temp = arr[j]; //第三方变量
    arr[j] = arr[j+1];
    arr[j+1] = temp;
  }
}

8 作用域

8.1 变量的作用域

作用域是变量的可作用范围,变量只有在自己的作用域下才会生效。

函数会产生作用域,在函数内定义的变量只能在函数内使用。

8.2 作用域分类

局部作用域: 函数内定义的变量和形参的作用域就是局部作用域;这样的变量称之为局部变量

全局作用域: 在函数外面定义的变量的作用域是全局作用域;这样的变量称之为全局变量

块级作用域: 在代码块中定义的变量的作用域是块级作用域;这样的变量称之为块级变量ES6才支持

局部变量 只能在定义变量的函数内使用; 全局变量 在任意地方都可以使用。

注意:

函数内的形参也是局部变量,作用域范围就是所在函数。

了解:

函数内不使用关键字 var 声明的变量会被当做全局变量,但不建议这么做,在严格模式下,不使用关键字 var 就声明变量,会报错!

8.3 作用域链

① 什么是作用域链

1) 函数会限制变量的作用域范围,而函数内是可以再嵌套函数的,函数的层层嵌套,就形成了一个作用域链

2)作用域链描述的是程序在执行过程当中寻找变量的过程。

② 作用域链寻找变量的过程

当函数内使用某个变量的时候,会按照如下过程找到该变量:

1) 先从自身所在作用域去查找,如果没有再从上级作用域当中去查找,直到找到全局作用域当中。

2)如果其中有找到,就不会再往上查找,直接使用

3)如果都没有找到,那么就会报引用错误提示变量没有定义

③ 注意:

一个变量的作用域只与函数声明的位置有关,与函数调用的位置无关!

8.4 变量作用域原理

8.5 作用域面试题

请写出以下程序的输出内容

var num = 10;
function fun() {
    var num = 20;
    fun2();
}

function fun2() {
    console.log(num);
}


fun();

9 变量提升和函数提升

9.1 变量提升

JS 会把变量的声明提升到本作用域的最前面。(只是提前声明了变量,但并没有给变量赋值)

function demo(){
  console.log(num);  // undefined
  var num = 200; //声明局部变量
  console.log(num); // 200
}
demo();

9.2 函数提升

JS 会把函数连声明带值提升到本作用域的最前面, 函数可以在函数声明之前调用。

只有字面量方式(function关键字方式)声明的函数才能函数提升,表达式方式和构造函数方式声明的函数,只能提升声明没有值(与变量提升相同)!

相对于变量提升,函数提升权重更高(存在与函数同名变量的情况下)

console.log(a);  // undefined
console.log(fn); // 函数提升,不但提升了声明,带着值也提升;
fn(); //可以调用
console.log(demo); //变量提升,提升了变量的声明,没有值
// demo(); 无法调用

var a = 100;
// 字面量方式声明
function fn() {
  console.log('我是fn');
}
// 表达式方式声明
var demo = function(){
  console.log('我是demo');
};

9.3 预解析

变量和函数之所以会提升,是因为程序在代码执行之前会先进行预解析

与解析遵循如下规则:

  • 预解析先去解析函数声明定义的函数,整体会被提升。
  • 再去解析带 var 的变量。
  • 函数重名会覆盖,变量重名会忽略。
  • 变量如果不带var,变量是不会进行预解析的;只有带var的变量才会进行预解析。
  • 表达式方式和构造函数方式定义的函数也是当做变量去解析。

9.4 变量提升面试题

// ① --------------------------------------------------------    
    alert(a);    
    a = 0;

// ② --------------------------------------------------------
    alert(a);    
    var a = 0;
    alert(a);    

// ③ --------------------------------------------------------
    alert(a);    
    var a = '我是变量';
    function a(){ alert('我是函数') }
    alert(a);    

// ④ --------------------------------------------------------
    alert(a); 
    a++;
    alert(a);    
    var a = '我是变量';
    function a(){ alert('我是函数') }
    alert(a)   

// ⑤ --------------------------------------------------------
    alert(a);   
    var a = 0;
    alert(a);   
    function fn(){
         alert(a);    
         var a = 1;
         alert(a);    
    }
    fn()
    alert(a);

10 自调用函数 IIFE

10.1 匿名函数

没有名字的函数称之为 匿名函数

function() {
    //匿名函数
}

匿名函数声明玩之后要立即调用,否则没有意义。

10.2 自调用函数

函数声明完立即调用,称之为自调用函数,也叫立即调用函数,英文简称 IIFE,英文全称 Immediately Invoked Function Expression

// 函数允许匿名,但是匿名的函数要立即使用
// 自调用函数,立即调用函数
(function(){
console.log('哈哈哈,我被调用了');
})();

// 自调用函数 传参
(function(a, b){
console.log(a+'和'+b+'跳舞');
})('曹操', '刘姥姥');


// 当然不匿名的自调用函数也是可以的,不过没有意义
(function fn(){
  console.log('fn 被调用了');
})();

注意:

两个连续的自调用函数,之间必须加分号,告诉浏览器是不同的函数,否则会有语法错误。

或者,在后面的自调用函数前加 ! 等没有副作用的一元运算符。

10.3 自调用函数的作用

1)减少全局变量的使用,把自己代码或者每个特效的代码写到一个自调用函数中, 防止外部命名空间污染(全局变量污染)

2)隐藏内部代码暴露接口,实现模块化开发。

11 回调函数

11.1 什么是回调函数

回调函数满足三个条件:

1)函数是我定义的。

2)我没有调用。

3)函数最终执行了。

满足以上三个条件的函数就是回调函数

11.2 常见使用回调函数的地方

作为其他函数的参数,函数的参数可以是个函数,而作为参数的那个函数就被称作回调函数

2)事件函数

3)定时器函数

4)ajax 的回调函数

4)生命周期钩子函数

注意:

匿名函数很适合做回调函数,也就是回调函数很多时候是个匿名函数。

11.3 实现一个回调函数

1)声明一个函数 fn,函数的参数类型要求是函数。

2)fn 的函数体内,调用传递进来的回调函数。

3)fn 内部调用回调函数的时候,还可以给回调函数传个实参。

// 声明函数,参数的类型要求是函数
function fun(callback) {
  //使用一下参数 调用参数 调用回调韩
  callback();
}
// 调用fun,传一个匿名函数进去
fun(function(){
  console.log('啊,作为一个回调函数,我被调用了');
});

// -------------------------------------

// 声明函数,参数的类型要求是函数 
function demo(callback) {
  callback(100, 200); // 调用回调函数的时候,还给回调函数传个实参
}
// 接收匿名函数作为参数,回调函数自己得定义两个形参
demo(function(a,b){
  console.log(a, b);
});
//接收非匿名函数作为参数
demo(fn1);
function fn1(a, b) {
  console.log(a+b);
}

/**
 * @param num1
 * @param num2
 * @param call
*/
function progress(num1, num2, call) {
  call(num1, num2);
}

progress(100, 200, function(a,b){console.log(a+b)});
progress(100, 200, function(a,b){console.log(a*b)});

12 递归函数

12.1 递归函数的概念

一个函数的内部如果又调用了自己,称作是函数的递归调用,这样的函数就是递归函数

12.2 递归函数成功的条件

1)必须有一个明显的结束条件

2)必须有一个趋近于结束条件的趋势

/*
  * 实现某个数字的阶乘
  * */
function fn(n) {
  // 当 n<=1 的时候,就结束了,不再进行递归了。
  if (n <= 1) {
    return 1;
  }
  return n * fn(n-1);
}

console.log(fn(3));


/**
 *  调用 fn(3)
 *       3 * fn(2)
 *       调用fn(2)
 *           2 * fn(1)  结果2
 *           调用fn(1)
 *               return 1
 *           调用完fn(1)
 *        调用完fn(2)
 *   调用完 fn(3)  结果6
 * */

1.3 递归的缺点

1)函数递归调用很容易发生灾难(内存泄漏)而调用失败。

2)函数递归调用效率不高,能不用就不用。

12.4 递归的应用场景

后端的操作中有些场景必须要递归函数来完成,如:

1)删除文件夹以及里面的内容,需要递归删除(操作系统的原始接口只能删除文件和空文件夹)

2)复制文件夹以及里面的内容。

3)剪切文件夹以及里面的内容。

results matching ""

    No results matching ""