深入理解JavaScript高阶函数:递归、回调、自执行与闭包

作者:拾光

时间:2026-10-24

浏览:140

本文系统介绍JavaScript中五种高阶函数的核心用法与原理。涵盖递归的定义与实现、回调函数的分类及优缺点、自执行函数的语法结构,以及作为值返回的函数特性。重点解析闭包的构成条件、内存机制及常见应用场景,帮助开发者掌握函数式编程的关键技巧。

文章目录

1、前言

在JavaScript中,函数本质上也是一种数据类型,因此可以将其赋值给变量。本文旨在探讨JavaScript中高阶函数的具体应用方式。

2、递归

递归是指函数内部调用自身的行为。可以用一个经典故事来比喻:从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?‘从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?……'” 。

从某种角度来看,递归与循环具有相似性。二者均通过重复执行相同代码来实现逻辑,且都必须包含终止条件,以防止出现无限循环或无限递归的情况。

实现递归需满足以下必要条件:

  • 子问题与原始问题性质相同,且复杂度更低
  • 必须存在明确的出口条件

在JavaScript中,实现递归调用主要有两种方法

  • 直接通过函数名进行调用
  • 利用arguments.callee属性进行调用。

以下代码演示了基本的递归实现:


var v = 1 // 出口条件
function fun() {
  console.log('第' + v + '次调用函数')
  v++
  if (v <= 3) {
    fun()
  }
}
fun()


运行结果如下所示

第1次调用函数 第2次调用函数 第3次调用函数

3、回调函数

既然函数可以像其他数据一样被赋值给变量,那么它自然也可以像其他数据那样被定义、删除、复制,并作为参数传递给其他函数。

当一个函数被作为参数传递给另一个函数时,该被传递的函数被称为回调函数。而接收该参数的函数则被称为目标函数(或外层函数)。

具体示例代码如下:


// 定义一个函数,这个函数有两个函数类型的参数,然后分别执行那两个函数,并返回它们的和。
function sum(a, b) {
  // 目标函数
  return a() + b()
}

function one() {
  // 回调函数
  return 1
}

function two() {
  // 回调函数
  return 2
}

console.log(sum(one, two)) // 3

代码执行流程分析如下:

当调用sum函数时,传入两个实参。在sum函数内部,会将这两个实参作为函数执行,计算其返回值并返回最终结果。

3.1匿名回调函数

所谓匿名回调函数,是指目标函数的参数是一个没有名称的函数。将上一段代码修改为使用匿名回调函数的形式如下:


// 定义一个函数,这个函数有两个函数类型的参数,然后分别执行那两个函数,并返回它们的和。
function sum(a, b) {
  // 目标函数
  return a() + b()
}

console.log(
  sum(
    function () {
      // 匿名回调函数
      return 1
    },
    function () {
      //  匿名回调函数
      return 2
    },
  ),
) // 3

3.2带参数的回调函数

回调函数支持接收额外参数,示例代码如下:


function multiplyByTwo(list, callback) {
  list.forEach(function (v, i) {
    callback(v * 2, i)
  })
}
var list = [1, 2, 3]
multiplyByTwo(list, function (v, i) {
  list[i] = v
})
console.log(list) // [ 2, 4, 6 ]


3.3回调函数的优缺点

  • 匿名回调函数有助于节省全局命名空间
  • 可以将私有数据内容开放给指定位置使用
  • 有助于保证封装性——虽然可以访问私有数据,但无法得知其来源
  • 有助于提升程序性能

然而,回调函数也存在缺点。当目标函数的参数是一个回调函数,而该回调函数的参数又是另一个回调函数,层层嵌套下去……这种“套娃”现象会形成回调陷阱,严重时称为回调地狱。

4、自调函数

所谓的自调函数,是指定义后立即执行的函数。示例代码如下:


;(function () {
  console.log('自调函数')
})()


这种语法看似复杂,实则简单。只需将匿名函数的定义包裹在一对括号中,然后在外部再紧跟一对括号即可。

语法结构如下图所示:

1、前言

除了上述两种常见方式外,自调函数还有以下几种较少使用的方式,示例代码如下:


;+(function (v) {
  // 形参
  var w = 100 // 局部变量
  console.log('自调函数' + v)
})(1) // 实参

!(function (v) {
  var w = 100 // 局部变量
  console.log('自调函数' + v)
})(2)

~(function (v) {
  var w = 100 // 局部变量
  console.log('自调函数' + v)
})(3)

使用自调匿名函数的主要好处是不会产生任何全局变量。

其缺点在于这样的函数无法被重复执行(除非将其置于循环或其他函数中)。这也使得即时函数非常适合用于执行一次性或初始化的任务。

5、作为值的函数

将一个函数作为另一个函数的执行结果进行返回,这个被返回的函数被称为“作为值的函数”。

示例代码如下:


function outer() {
  var v = 100
  // 在函数的函数体中定义另一个函数 -> 内部(私有)函数
  return function () {
    // 使用匿名函数
    return v * 2
  }
}
var result = outer()
console.log(result) // [Function]


这样做的好处包括:

  • 有助于确保全局命名空间的纯净性(这意味着发生命名冲突的概率极低)。
  • 确保数据的私有性 — 这使得我们可以选择仅将部分必要的函数暴露给“外部世界”,而保留其他函数,使其不被应用程序的其他部分访问。

6、闭包

闭包是在函数概念中提出的。简单来说,如果一个函数定义中引用了函数外部定义的变量,并且该函数可以在其定义环境之外被执行,那么当内部函数以某种方式被任何外部函数作用域访问时,就产生了闭包。

实际上,闭包可以被视为一种更为广义的函数概念。因为它已经不再是传统意义上定义的简单函数。

构成闭包需要满足以下条件:

  • 外部函数中定义了内部函数。
  • 外部函数具有返回值,且返回值为内部函数。
  • 内部函数引用了外部函数的变量。

闭包也存在一些缺点:

  • 作用域关系不如常规代码直观。
  • 由于变量不会被垃圾回收机制回收,因此会占用一定的内存空间。

闭包的主要作用包括:

  • 可以访问同级作用域的数据。
  • 可以读取其他元素的内部变量。
  • 可以延长变量的作用域。
  • 可以避免污染全局变量

闭包的工作原理:

我们可以将函数的执行过程分为两个阶段,即预编译阶段和执行阶段;

  • 在预编译阶段,如果发现内部函数使用了外部函数的变量,它会在内存中创建一个闭包对象并保存相应的值;如果闭包已存在,则只需增加对应的属性值即可。
  • 在执行完成后,函数执行上下文会被销毁,函数对闭包对象的引用也会被移除,但内部函数仍然持有对该闭包的引用,因此内部函数可以继续访问外部函数的变量。

闭包主要利用了作用域链的特性:在函数内部定义的函数会将包含该函数的活动对象添加到自身的作用域链中。函数执行完毕后,其执行作用域链虽然销毁,但因内部函数的作用域链仍然引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被销毁后,这些活动对象才会被销毁。

闭包实现的示例代码:


// 1. 通过返回的内部函数来操作函数中的局部变量
function fun () {
    var v = 100; // 局部变量
    // 通过返回一个对象的方式访问局部变量v 来完成闭包
    return {
        set: function (x) {
            v = x;
        },
        get: function () {
            return v
        }
    }
}
var result = fun();
result.set(200)
console.log(result.get()); // 200



// 2. 定义一个局部变量,计算该函数一共调用几次
var generate_count = function () {
    var container = 0;
    return function () {
        container++
        console.log(`这是第${container}次调用`);
    }
}

var result = generate_count();
result(); // 这是第1次调用
result(); // 这是第2次调用
result(); // 这是第3次调用


// 3.修改 Math.pow() 函数,让求一个数的平方或者立方时,不需要每次传递第二个参数

/*
Math.pow(4, 2) // 求4的平方
Math.pow(4, 3) // 求4的立方
*/

// 写一个函数生成器
function makePower (power) {
    return (number) => {
        return Math.pow(number, power)
    }
}
// 平方
let power2 = makePower(2)
// 立方
let power3 = makePower(3)
// 求4的平方
console.log(power2(4)) // 16
// 求4的立方
console.log(power3(4)) // 62

总结:

本文介绍了JavaScript中的5个高阶函数,分别是递归、回调函数、自调函数、作为值的函数以及闭包的使用方法和实现原理。

到此这篇关于JS 高阶函数的文章就介绍到这了,更多相关JS 阶函数内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章