• JavaScript中的一等公民: 函数(Function)


    1. 函数的基本使用

    使用函数声明或者函数表达式创建一个函数

    foo();  //foo
    bar();  //Uncaught ReferenceError: Cannot access 'bar' before initialization
    //函数声明
    function foo(){
      console.log(foo);
    };
    
    //函数表达式
    const bar = function(){
      console.log('bar');
    };
    
    setTimeOut(function(){
      //匿名函数
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 函数声明会使函数整体提升
    • 函数表达式只会提升声明不会提升函数体

    2. 作用域和闭包

    2.1 函数的作用域

    • 函数有自己的作用域
    • 通常情况下函数作用域用内的变量存在于函数运行期间,函数执行完毕后销毁
    • 函数内可以使用上级作用域的变量,上层作用域无法使用函数内部的变量
    const age = 21;
    function foo(){
      const name = 'ian';
      console.log(age); //21
      return name;
    };
    
    console.log(foo); //ian
    console.log(name); //undefined
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.2 闭包 closure
    形成闭包的三个条件

    • 函数嵌套
    • 内部函数引用了外部函数的变量
    • 外部函数在外层作用域被调用(形成内部函数的引用)
    function outer(){
     const name = 'ian';
     function inner(){ console.log(name) };
     return inner;
    }
    
    const bar = outer();
    bar(); //ian
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    outer()函数执行的时候得到inner函数,inner函数中使用了outer作用域中的name导致outer函数执行完无法正常释放,变量bar又对inner有了引用,形成了闭包。闭包实际上提供了一种有外部访问函数内部变量的方法。

    2.3 闭包的使用场景

    函数柯理化

    function add(x){
      return function(y){
        return  x + y;
      };
    };
    const add5 = add(5);
    const add3 = add(3);
    
    console.log(add5(1)); //6
    console.log(add3(1)); //4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    缓存
    使用闭包缓存列表进行求和

        function sum() {
          const list = [];
          return function (num) {
            if (num) {
              list.push(num);
            }
            else {
              //沒有传递参数时返回求和结果
              let res = 0;
              for (var i = 0; i < list.length; i++) {
                res += list[i];
              };
              return res;
            }
          };
        };
        const add = sum();
        add(1);
        add(2);
        add(3);
        console.log(add()); //6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    使用闭包实现防抖函数

    function debounce(fn, await) {
      let timer = null;
      return function (...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(this, args);
          timer = null;
        }, await);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过闭包实现变量/方法的私有化

    function user(){
      const user ={
        name: 'ian',
        age: 21,
      };
    return function(){
      return name;
    }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.4 闭包的优缺点
    闭包的优点:

    • 提供了外部作用域访问访问函数内部的方法
    • 延长了变量的声明周期
    • 避免定义全局变量造成污染

    闭包的缺点:
    闭包延长了变量的生命周期,增加了变量的引用,大量使用闭包有内存泄露的风险

    2.5 闭包一定会造成内存泄露吗?

    • 内存泄露是指没有被使用的变量一直被引用没有被释放,导致内存一直被占用。
    • 闭包不一定会造成内存泄露,只是增加了内存泄露的风险。

    下面的代码会造成内存泄露

        window.onload=function(){
          const btn = document.getElementById('btn');
          btn.onclick=function(){
            console.log(btn.id);
          }
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    避免闭包中内存泄漏的两种方法:

    • 将不用的变量设置为null
    • 减少不必要的变量
        window.onload = function () {
          const btn = document.getElementById('btn');
          const id = btn.id;
          btn.onclick = function () {
            console.log(id);
          };
          btn = null;
        };
        
        window.onload = function () {
          const btn = document.getElementById('btn');
          document.getElementById('btn').onclick = function () {
            console.log(document.getElementById('btn').id);
          };
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3. 函数的参数(arguments和arguments.callee)

    arguments是一个伪数组,用来获取函数的全部参数

        function add(x, y) {
          return x + y;
        };
    
        function add1() {
          let res = 0;
          for (let i = 0; i < arguments.length; i++) {
            res += arguments[i]
          };
          return res;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    arguments.callee指向参数所属的当前执行的函数

        function foo() {
          console.log(arguments.callee === foo);
        };
        foo();
    
    • 1
    • 2
    • 3
    • 4

    将Argumnets转为数组

      //使用数组的slice方法将伪数组转为数组
        function add() {
          const nums = [].slice.call(arguments);
          console.log(nums);
        }
        //使用拓展运算符将伪数组转为数组
        function add(x, y) {
          const nums = [...arguments];
        };
        add(1, 2); //[1, 2]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4. 构造函数

    4.1 构造函数的使用

    • 构造函数是一种特殊的函数,总是与new关键字一起使用,得到一个对象
    • 构造函数的声明一般以大写字母开头
     function Person(name, age){
      this.name = name;
      this.age = age;
     };
     
     const jack = new Person('jack',21); //{name:'jack', age:21}
     const tom = new Person('tom',22); //{name:'tom', age:22}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.2 构造函数执行的过程

    1. 创建一个新对象,将this指向这个新对象
    2. 执行代码为这个对象添加属性
    3. 返回这个对象

    4.3 构造函数的返回值
    构造函数默认返回一个新的对象,如果有手动的return情况如下:

    • 手动返回基础类型将会被忽略,仍然返回默认的对象
    • 手动返回引用类型的数据将覆盖默认的返回值
        function Person(name,age){
          this.name = name;
          this.age =age;
          return 'person';
        };
        const jack = new Person('jack',23); // //{ name: "jack", age: 23 }
        
        function Person1(name,age){
          this.name = name;
          this.age =age;
          return ['name','age'];
        };
        const tom = new Person1('tom',23); //Array [ "name", "age" ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    5. 立即执行函数 IIFE

    立即执行函数就是声明一个匿名函数然后立即执行它

    5.1 立即执行函数的语法

    function foo(){} ();
    
    • 1

    这个代码会报错,因为function关键字既可作为函数声明也可作为函数表达式,当以function开头的时候会被认为是函数声明,直接使用()去执行会抛出异常,我们在函数外面包上一个括号使之成为函数表达式。正确的写法如下:

    (function(){
      console.log('立即执行函数');
    }());
    
    (function(){
       console.log('立即执行函数');
    })();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    甚至你可以这样写:

    + function(){}(console.log('这是一个函数表达式'));
    - function(){}(console.log('这是一个函数表达式'));
    ! function(){}(console.log('这是一个函数表达式'));
    ~ function(){}(console.log('这是一个函数表达式'));
    
    • 1
    • 2
    • 3
    • 4

    5.2 立即执行函数的作用

    • 立即执行函数只会执行一次,而且是匿名函数无法被手动调用
    • 立即执行函数会形成一个内部的作用域,用来存放变量和方法,起到保护作用
    • 避免对全局变量的污染

    5.3 立即执行函数的经典使用场景

        //点击按钮输出都是5
        const btns = document.getElementsByClassName('btn');
        for(var i = 0;i
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    使用立即执行函数后,每次for循环都会得到一个新的函数,i从1~5分别被保存到5个不同的函数里,因此点击按钮的时候能依次输出1到5。

    6. 递归

    递归就是函数调用自身。递归的两个必要条件:

    • 函数调用自身
    • 设置出口条件

    利用递归实现斐波那契数列

        function fibonacci(n) {
          if (n <= 2) {
            return 1
          } else {
            return fibonacci(n - 1) + fibonacci(n - 2)
          }
        };
    
        fibonacci(1); //1
        fibonacci(2); //1
        fibonacci(3); //2
        fibonacci(4); //3
        fibonacci(5); //5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    7. 高阶函数

    高阶函数的定义: 函数接收函数作为入参

    const nums = [1,2,3];
    nums.filter(function(n){ returen n>2} );
    
    • 1
    • 2

    高阶函数的好处是将具体的计算以函数参数的形式抽离到函数外部,能更灵活的增强函数的功能

    8. 函数式编程

    • 函数编程的前提是函数必须幂等:同样的输入有同样的输出
    • 函数式编程设计到的知识点有函数柯理化(curring)、函数组合(compose)等
    • 更详细的请参考:JavaScript中的函数式编程

    9. eval

    eval接收一个字符串,将这个字符串当作JS语句执行

          const a = 1;
          eval('console.log(a)'); //1
    
    • 1
    • 2

    非严格模式下eval能声明和复写变量的值

    let a = 1;
    eval('a=2; b = 3;')
    console.log(a,b); //  2,3
    
    • 1
    • 2
    • 3

    使用别名调用eval的时候,eval使用全局作用域

    const exec = eval;
    const a = 1;
    funtion foo(){
    const a = 2;
    exec('console.log(a)'); //1
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    总结

    函数的基础使用

    • 使用函数表达式或者函数声明去创建一个函数

    • 函数声明整体提升,函数表达式只提升变量

    • 匿名函数

    • 构造函数

      • 构造函数返回一个对象,使用new操作符执行构造函数
      • 构造函数执行的过程:1. 生成一个对象将this绑定到这个对象 2. 执行构造函数的代码 3.返回结果
      • 构造函数默认返回一个对象,手动返回应用类型的数据将代替默认的返回结果
    • 闭包

      • 闭包产生的三个条件:1. 函数嵌套 2.内部函数使用了外部函数作用域内的变量 3.被返回的内部函数在外部作用域有被引用
      • 闭包的主要功能: 1. 提供了函数外部访问函数内部作用域的方法 2. 延长了变量的声明周期,外部函数执行完里面的变量不会立即销毁
      • 闭包的使用场景:缓存、保存私有属性/方法 、柯理化、防抖
      • 闭包的缺点: 增加了内存的占用,外部函数没执行一次都会增加一次引用,可能会造成内存泄漏
      • 如何避免闭包造成内存泄漏: 不使用的变量手动设置为null
    • 函数的arguments和arguments.callee

      • arguments用来获取函数的全部参数是个伪数组
      • 使用Array.prototype.slice(argument)或者[…arguments]将伪数组转为数组
      • arguments.callee指向参数所属的正在执行的函数
    • 立即执行函数(IIFE)

      • 立即执行函数用途:1. 避免污染全局变量 2.保存私有属性/方法
      • 常见的立即执行函数语法: (function(){}())、 (function(){})()
    • 递归

      • 递归就是函数调用自身
      • 递归的2个必要条件: 1.函数调用自身 2.设置出口
    • eval

      • eval接受一个字符串,讲字符串当作js语句执行
      • 非严格模式下eval可以声明和修改变量
      • 使用别名调用eval时,eval的作用域为全局
  • 相关阅读:
    树-->算法总结
    ES 2024 新特性
    显示控件——字符显示之数据变量
    汉罗塔汉洛塔c++,看不懂ni打我
    3.0 设计模式汇总
    尚硅谷Flume(仅有基础)
    保姆教程angular8(一)
    python | 巧用正则表达式
    只有真正将产业互联网看成是一种嬗变的过程,才能把握其精髓和原始奥义
    工厂想要精益管理需要做好哪些基础性工作?
  • 原文地址:https://blog.csdn.net/qq_44621394/article/details/126963002