美文网首页
JS函数详细介绍--篇7

JS函数详细介绍--篇7

作者: 扶得一人醉如苏沐晨 | 来源:发表于2023-10-16 09:39 被阅读0次

    一、函数介绍

    函数就是将实现特定功能的代码封装起来,当我们需要实现特定功能时,直接调用函数实现即可,不需要每次都写一堆代码,实现代码的复用。

    函数的作用:

    • 1、实现功能的封装,提高代码复用率
    • 2、用于构建对象的模板(构造函数)

    函数实际上是对象,每个函数都是Function类型的实例,并且都与其他引用类型一样具有属性和方法,由于函数是对象,因此函数名实际上也是一个指向函数对象的指针,不会与某个函数绑定。

    二、函数的声明

    2.1、语法

    function 函数名(形参列表){
        //函数体
    }
    

    我们也可以写成函数表达式的形式:

    const 函数名 = function (形参列表) {
      //函数体
    };
    

    2.2、函数调用

    // 函数声明
    function sum(a, b) {
      return a + b;
    }
    // 函数声明之后,需要调用才能执行
    // 函数调用:函数名(实参)
    console.log(sum(1, 2)); //3
    

    写成函数表达式的形式(含有运算符的式子叫表达式):

    // 函数声明
    const sum = function (a, b) {
      return a + b;
    };
    // 函数调用:函数名(实参)
    console.log(sum(1, 2)); //3
    

    2.3、函数声明提升

    function关键词声明函数,与var声明的变量类似,存在声明提升。

    sum(1, 2); //在此处调用函数不会报错,因为存在函数声明提升
    function sum(a, b) {
      return a + b;
    }
    

    函数表达式的形式,没有声明提升

    sum(1, 2) //报错sum is not defined
    // 函数声明
    const sum = function (a, b) {
      return a + b;
    };
    

    函数表达式就是把一个匿名函数赋值给一个变量,由于const、let声明变量没有变量提升,var有变量提升,但是会为变量赋值为undifined,所以不管哪一种方式定义函数表达式,函数都没有声明提升
    var、let和const声明变量的区别 - 简书 (jianshu.com)

    三、函数内部属性(默认参数)

    函数内部属性(默认参数)只能在函数内部才能访问

    3.1、arguments

    arguments是一个类数组对象,包含着传入函数中的所有参数。arguments支持for...of...迭代,但不支持forEach循环。arguments主要用途是保存函数参数。

    function foo() {
      console.log(arguments); // Arguments [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
      console.log(arguments[1]); // 2
    }
    // 当传递的实参个数超过形参的个数的时候不会报错,所有的实参都会保存在arguments里
    foo(1, 2, 3, 4);
    

    注意:arguments 中存的是实参,而不会存形参

    function foo(a, b = 2, c = 3) {
      console.log(arguments); // Arguments [1, callee: (...), Symbol(Symbol.iterator): ƒ]
      console.log(b); //2
      console.log(c); //3
    }
    //只传了一个实参,那么arguments中就只有一个值
    foo(1);
    
    callee 属性

    arguments 对象有一个名为callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。
    arguments.callee 实际上就是函数名。

    // 递归求n的阶乘
    function factorial(n) {
      if (n == 1) {
        return 1;
      }
      return arguments.callee(n - 1) * n; //arguments.callee 相当于函数名factorial
    }
    console.log(factorial(10));
    
    length 属性

    arguments 对象的 length 属性返回实参个数。

    function foo() {
      console.log(arguments.length); //5
    }
    foo(1, 2, 3, 4, 5);
    

    四、关于this指向问题


    五、作用域

    • 全局作用域
    • 函数作用域
    • 块作用域

    六、作用域链

    6.1、自由变量

    如下代码中,console.log(a)要得到a变量,但是在当前的作用域中没有定义a(可对比一下b)。当前作用域没有定义的变量,称为为 自由变量 。

    var a = 100
    function fn() {
        var b = 200
        console.log(a) // 这里的a 就是一个自由变量  // 100
        console.log(b)
    }
    fn()
    

    6.2、作用域链

    自由变量会顺着作用域向父作用域找,如果父级也没有找到,再一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。

    七、函数调用

    • 函数声明好之后并不会直接运行,需要进行调用才能运行。
    • 调用函数的方式不仅限于 () 执行。

    调用函数的方式:

    • 函数名(实参列表);
    • 函数名.call(执行环境对象,实参列表);
    • 函数名.apply(执行环境对象,实参列表数组);
    • 函数名.bind(执行环境对象)(实参列表);

    下面介绍一下call、apply、bind方法:

    7.1、call(执行环境对象,实参列表);

    call 方法用来改变 this 的指向,第一个参数表示 this 的指向,后面的参数表示传递的实参,可以零个、一个或多个。

    var obj = {
      name: '叶子yes',
      sayName: function (a,b) {
        console.log(this.name); // 叶子yes
        console.log(a,b); // 1,2
      }
    }
    var b = obj.sayName;
    b.call(obj,1,2); // this 指向 obj
    

    7.2、apply(执行环境对象,实参列表数组);

    apply 方法和 call 方法基本相同,也是用来改变 this 的指向,不同点在于,apply 传递实参时采用数组的形式。

    var obj = {
      name: '叶子yes',
      sayName: function (a,b) {
        console.log(this.name); // 叶子yes
        console.log(a,b); // 1,2
      }
    }
    var b = obj.sayName;
    b.apply(obj,[1,2]); // this 指向 obj,实参列表要写成数组的形式
    

    注意:如果call和apply的第一个参数是null,那么this在node环境下指向的是global对象,在HTML中指向的是window对象

    var obj = {
      name: '叶子yes',
      sayName: function (a,b) {
        console.log(this); // window 或者 global
      }
    }
    var b = obj.sayName;
    b.apply(null);
    

    7.3、bind(执行环境对象)(实参列表);

    bind 也是用来改变 this 的指向的,但是 bind(执行环境对象) 返回的是函数,还没有被执行,需要再使用()执行函数。

    var obj = {
      name: '叶子yes',
      sayName: function (a,b) {
        console.log(this.name); // 叶子yes
        console.log(a,b); // 1,2
      }
    }
    var b = obj.sayName;
    var c = b.bind(obj) //返回函数,此时还没有执行,需要再使用()来执行
    console.log(c) //[Function: bound sayName]
    c(1,2) //执行函数
    

    总结:call和apply都是改变上下文中的this并立即执行这个函数,bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别。

    八、回调函数

    回调,就是回头调用的意思。主函数的事先做完,回头再调用传进来的那个函数。

    回调函数的作用

    回调函数一般都用在耗时操作上面:因为主函数不用等待回调函数执行完,可以接着执行自己的代码。比如ajax请求,比如处理文件等。

    下面简单模拟一下回调函数:

    //定义主函数,回调函数作为参数
    function A(callback) {
      callback();
      console.log('我是主函数');
    }
    //定义回调函数
    function B() {
      // 模仿延时操作
      setTimeout(() => {
        console.log('我是回调函数');
      }, 3000);
    }
    //调用主函数,将函数B传进去
    A(B);
    
    输出结果为:
    我是主函数
    我是回调函数
    

    先输出“我是主函数”,3秒后再输出“我是回调函数”

    九、闭包

    9.1、什么是闭包

    闭包(closure)就是能够读取其他函数内部变量的函数(内部函数引用了外部函数中的数据(属性、函数))

    9.2、闭包形成的条件

    闭包的生成有三个必要条件:

    1、函数嵌套函数
    2、内部函数引用了外部函数中的数据(属性、函数)
    3、参数和变量不会被回收

    下面例子中的 closure 就是一个闭包:

    function func() {
      var a = 1, b = 2;
    
      function closure() {
        return a + b;
      }
      return closure;
    }
    console.log(func()()); // 3
    

    9.3、闭包的作用

    • 可以读取函数内部的变量
    • 就是让这些变量的值始终保持在内存中。
    function f1() {
      var n = 999;
      nAdd = function () { 
        n += 1 
      }
      function f2() {
        console.log(n);
      }
      return f2;
    }
    var result = f1();
    result(); // 999
    nAdd();
    result(); // 1000
    

    在这段代码中,result 实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除

    为什么会这样呢?原因就在于f1f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

    这段代码中另一个值得注意的地方,就是nAdd=function(){n+=1}这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

    9.4、使用闭包的注意点

    由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露,这是IE的BUG。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    闭包会在父函数外部改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。多个子函数的scope都是同时指向父级,是完全共享的。因此当父级的变量对象被修改时,所有子函数都受到影响。

    相关文章

      网友评论

          本文标题:JS函数详细介绍--篇7

          本文链接:https://www.haomeiwen.com/subject/rzuwbdtx.html