Skip to content

S03-02 JS-基础-函数

[TOC]

函数

概述

伪变量【

在学习编程的过程中,你可能会经常看到foo、bar、baz这些名词:

  • 它们通常被用来作为函数、变量、文件的名词;
  • 目前已经变成了计算机编程的术语一部分;
  • 但是它们本身并没有特别的用途和意义;
  • 常被称之为 “伪变量”(metasyntactic variable)

那么它们有什么由来吗?

  • 事实上,foo、bar这些名词最早从什么时候、地方流行起来的一直是由争论的;
  • 一种说法是通过Digital(迪吉多,数字设备公司,成立于1957年的美国电脑公司)的手册说明流行起来的;
  • 一种说法是说源自于电子学中的反转foo信号;
  • 也有一种说法是foo因为出现在了一个漫画中,漫画中foo代表“好运”,与中文的福读音类似;

总之,foo、bar、baz已经是编程领域非常常用的名词。

  • 我个人也比较习惯在写一些变量、函数名词时使用这些词汇,大家做一个了解;

认识函数

函数(Function):是一段可重复使用的代码块,用于执行特定任务或计算值。函数允许您封装逻辑、提高代码复用率、简化复杂操作并组织程序结构。

核心价值

  1. 代码复用:避免重复编写相同代码
  2. 模块化:将复杂问题分解为小任务
  3. 抽象:隐藏实现细节,暴露简洁接口
  4. 作用域管理:创建独立变量环境

函数分类

  • 内置函数:JS引擎或者浏览器内部提供的已经实现好的函数。
  • 自定义函数:自己编写的函数。

目前, 我们已经接触过几个函数了

  • alert():浏览器弹出一个弹窗。
  • prompt():在浏览器弹窗中接收用户的输入。
  • console.log():在控制台输入内容。
  • String() / Number() / Boolean()

函数使用步骤

函数使用步骤:函数的使用包含两个步骤:

  • 声明函数:封装独立的功能
  • 调用函数:享受封装的成果

声明函数(定义函数):声明函数的过程是对某些功能的封装过程。

  • 在之后的开发中,我们会根据自己的需求定义很多自己的函数。

调用函数(函数调用):调用函数是让已存在的函数为我们所用;

  • 这些函数可以是刚刚自己封装好的某个功能函数。
  • 当然, 我们也可以去使用默认提供的或者其他三方库定义好的函数。

函数的作用:在开发程序时,使用函数可以提高编写的效率以及代码的重用。

声明函数

函数声明(Function Declaration):使用 function 关键字后跟函数名来创建命名函数。函数声明在代码执行前就会被解析和提升(hoisted),使得你可以在声明前调用函数。

语法

js
function functionName(parameter1, parameter2, ...) {
    // 函数体 - 执行代码
    return result; // 可选返回值
}
  • function 关键字:标识函数声明的开始
  • 函数名:函数的标识符,遵循变量命名规则
  • 参数列表:括号内的零个或多个参数,逗号分隔
  • 函数体:花括号 {} 内的代码块
  • 返回值:可选的 return 语句,用于返回结果

调用函数【

调用函数:通过函数名()即可,比如test()

函数定义完后里面的代码是不会执行的,函数必须调用才会执行;

语法

js
functionName(arg1, arg2, ...)

函数组成

参数

函数的参数:是函数定义时声明的变量,用于接收传入函数的值。参数允许我们向函数传递数据,使函数能够根据不同的输入执行相同的操作,从而增加代码的复用性和灵活性。

分类

  • 形参(Parameters):函数定义时声明的变量。在函数内部会把形参当做变量使用。
  • 实参(Arguments):函数调用时实际传入的值。需要按照定义时的顺序依次传递。

语法

js
// 形参: a, b
function add(a, b) {
  return a + b;
}

// 实参: 3, 5
const result = add(3, 5);

返回值

函数的返回值:是指函数执行完成后返回给调用者的数据。在JS中,每个函数调用都会返回一个值,即使没有显式使用return语句。

语法

  1. 显式返回:使用 return 语句显式指定返回值

    js
    // 1. 使用 return 语句显式指定返回值
    function add(a, b) {
      return a + b; // 返回两数之和
    }
    
    const result = add(3, 5); // result = 8
  2. 隐式返回:没有return语句,或者return后没有值,则默认返回undefined

    • 仅用于副作用的函数(如修改 DOM、日志记录)通常不返回值
    js
    // 2. 没有return语句的函数
    function noReturn() {
      // 什么都不做
    }
    console.log(noReturn()); // undefined
    
    // 2. return后没有值
    function emptyReturn() {
      return;
    }
    console.log(emptyReturn()); // undefined

return:具有以下特性:

  • 立即终止函数:执行到 return 时函数立即结束
  • 只能返回单个值:但可通过数组/对象返回多个值
  • 位置灵活:可在函数任意位置使用

返回多个值:通过数组或对象返回多个结果。

js
// 使用数组
function calculate(a, b) {
  return [
    a + b, // 和
    a - b, // 差
    a * b, // 积
    a / b  // 商
  ];
}
const [sum, difference, product, quotient] = calculate(10, 2);
js
// 使用对象
function getDimensions() {
  return {
    width: window.innerWidth,
    height: window.innerHeight,
    ratio: window.innerWidth / window.innerHeight
  };
}
const {width, height, ratio} = getDimensions();

链式返回值:返回对象自身以支持链式调用。

js
class Calculator {
  constructor(value = 0) {
    this.value = value;
  }
  
  add(num) {
    this.value += num;
    return this; // 返回自身实例
  }
  
  multiply(num) {
    this.value *= num;
    return this;
  }
}

const result = new Calculator(5)
  .add(3)      // 5 + 3 = 8
  .multiply(2) // 8 * 2 = 16
  .add(10)     // 16 + 10 = 26
  .value;      // 最终结果: 26

异步返回值:处理异步操作返回值。

js
// 返回Promise
function fetchData(url) {
  return fetch(url)
    .then(response => response.json());
}

// 使用async/await
async function getUser(userId) {
  try {
    const response = await fetchData(`/users/${userId}`);
    return await response.json();
  } catch (error) {
    return { error: "Failed to fetch user" };
  }
}

函数中调用函数

在开发中,函数内部是可以调用另外一个函数的。

image-20250519104328333

image-20250519104337663

既然函数中可以调用另外一个函数,那么函数是否可以调用自己呢?

  • 当然是可以的;
  • 但是函数调用自己必须有结束条件,否则会产生无限调用,造成报错;

image-20250519104349216

image-20250519104355900

递归函数

事实上,函数调用自己还有一个专业的名词,叫做递归(Recursion)

在语言学方面,我们也可以描述为递归:

  • 从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?
  • 递归读取上面的话;

递归是一种重要的编程思想:

  • 将一个复杂的任务,转化成可以重复执行的相同任务;

案例:实现一个自己的幂函数pow(pow单词可以表示指数的意思)

  • 我们可以先用for循环来实现;

image-20250519104406734

递归的实现思路

另一种实现思路是递归实现:

  • 这是因为在数学上:x n = x * xn-1
  • 那么对于函数的调用,我们也可以进行划分:
  • 这里需要有一个结束的条件,就是当n已经等于1的时候就不需要拆分了;

image-20250519104433555

所以最终的代码如下:

image-20250519104441527

image-20250519104502179

递归的代码第一次接触会有点绕,对于初次接触函数的同学,可以先跳过去。

  • 后续我们讲解数据结构与算法时,会使用递归来解决一些算法问题;

局部变量和外部变量

在JavaScript(ES5之前)中没有块级作用域的概念,但是函数可以定义自己的作用域。

  • 作用域(Scope)表示一些标识符的作用有效范围(所以也有被翻译为有效范围的);
  • 函数的作用域表示在函数内部定义的变量,只有在函数内部可以被访问到;

外部变量和局部变量的概念:

  • 定义在函数内部的变量,被称之为局部变量(Local Variables)。
  • 定义在函数外部的变量,被称之为外部变量(Outer Variables)。

什么是全局变量?

  • 在函数之外声明的变量(在script中声明的),称之为全局变量。
  • 全局变量在任何函数中都是可见的。
  • 通过var声明的全局变量会在window对象上添加一个属性(了解);

在函数中,访问变量的顺序是什么呢?

  • 优先访问自己函数中的变量,没有找到时,在外部中访问。

关于块级作用域、作用域链、变量提升、AO、VO、GO等概念我们后续将进行学习。

函数表达式(Function Expressions)

在JavaScript中,函数并不是一种神奇的语法结构,而是一种特殊的值。

  • 前面定义函数的方式,我们称之为函数的声明(Function Declaration);

还有另外一种写法是函数表达式(Function Expressions):

image-20250519104515619

注意,function 关键字后面没有函数名

  • 函数表达式允许省略函数名。

无论函数是如何创建的,函数都是一个值(这个值的类型是一个对象,对象的概念后面会讲到)。

在JavaScript开发中,我们可以将函数作为头等公民。

函数声明 vs 函数表达式

在开发中,函数的声明和函数表达式有什么区别,以及如何选择呢?

首先,语法不同:

  • 函数声明:在主代码流中声明为单独的语句的函数。
  • 函数表达式:在一个表达式中或另一个语法结构中创建的函数。

其次,JavaScript创建函数的时机是不同的:

  • 函数表达式是在代码执行到达时被创建,并且仅从那一刻起可用。
  • 在函数声明被定义之前,它就可以被调用。 ✓ 这是内部算法的原故; ✓ 当 JavaScript 准备 运行脚本时,首先会在脚本中寻找全局函数声明,并创建这些函数;

开发中如何选择呢?

  • 当我们需要声明一个函数时,首先考虑函数声明语法。
  • 它能够为组织代码提供更多的灵活性,因为我们可以在声明这些函数之前调用这些函数。

JavaScript头等函数

头等函数(first-class function;第一级函数)是指在程序设计语言中,函数被当作头等公民。

  • 这意味着,函数可以作为别的函数的参数、函数的返回值,赋值给变量或存储在数据结构中;
  • 有人主张也应包括支持匿名函数(待会儿会讲到);

通常我们对作为头等公民的编程方式,称之为函数式编程

  • JavaScript就是符合函数式编程的语言,这个也是JavaScript的一大特点;

比如:函数可以在变量和变量之间相互进行赋值;

image-20250519104533450

回调函数(Callback Function)

既然函数可以作为一个值相互赋值,那么也可以传递给另外一个函数。

image-20250519104545255

foo这种函数我们也可以称之为高阶函数(Higher-order function);

高阶函数必须至少满足两个条件之一:

  • 接受一个或多个函数作为输入;
  • 输出一个函数;

匿名(anonymous)函数的理解:

  • 如果在传入一个函数时,我们没有指定这个函数的名词或者通过函数表达式指定函数对应的变量,那么这个函数称之为匿名 函数。

立即执行函数

什么是立即执行函数?

  • 专业名字:Immediately-Invoked Function Expression(IIFE 立即调用函数表达式)
  • 表达的含义是一个函数定义完后被立即执行; ✓ 第一部分是定义了一个匿名函数,这个函数有自己独立的作用域。 ✓ 第二部分是后面的(),表示这个函数被执行了

image-20250519104559106

这个东西有什么用?

  • 会创建一个独立的执行上下文环境,可以避免外界访问或修改内部的变量,也避免了对内部变量的修改

image-20250519104609279

立即执行函数的其他写法

立即执行函数必须是一个表达式(整体),不能是函数声明(了解即可):

  • 下面的这种写法会报错,因为是一个函数声明,不是一个函数表达式;
  • 当圆括号出现在匿名函数的末尾想要调用函数时,它会默认将函数当成是函数声明。

image-20250519104619230

当圆括号包裹函数时,它会默认将函数作为表达式去解析,而不是函数声明。

image-20250519104627631

下面是一个函数表达式,所以可以执行

image-20250519104637069

image-20250519104643801

代码风格

image-20250519104655357

Chrome的debug调试技巧

image-20250519104707612