简单易懂的javaScript:函数

函数实际上也是对象,每个函数都是Function类型的实例,函数名就是指向函数对象的指针。

函数声明

    function sum (num1, num2) {
      return num1 + num2;
    }

函数表达式

    const sum = function (num1, num2) {
      return num1 + num2;
    };

函数声明和函数表达式几乎是等价的。

ES6新增,箭头函数

    const sum = (num1, num2) => {
      return num1 + num2;
    }
  • 参数应该使用()包裹,如果只有一个参数,则可以省略()

  • 返回值

    • 不使用{}包裹:意味着这个箭头函数的返回值就是这一条语句。

    • 使用{}包裹:

      • 如果不含return关键字,则js会试图将{}内的代码解释为一个对象。

      • 如果包含return关键字,则返回值由return关键字决定。{}内可以编写任意多条语句。

箭头函数不能使用arguments(传入的参数列表)、super(调用父类构造器)、new.target()(判断new关键字的构造函数),不能用作构造函数,也没有prototype属性。

函数的参数

ECMAScript函数不关心传入参数的多少,所有传入的参数都会被放入一个类数组对象argements中,可以通过[]访问传入的参数,也可以使用argements.length获取传入参数的个数。

箭头函数的参数

如果函数是由箭头函数定义的,那么传给函数的参数不能用argements访问

默认参数

在定义函数时,给参数加上param=默认值就可以定义默认参数,默认参数只有在该变量没有传入时生效。

argements不会记录默认参数的值,它只反映传入的参数

参数的定义顺序是按函数参数从左到右定义的,所以后定义的默认参数可以使用前面的变量。

扩展操作符

使用...param的语法可以获取任意长度的参数,也可以将一个可迭代对象展开。

    const nums = [0, 1, 2, 3, 4, 5];

    function sum (...nums) {
      return nums.reduce((acc,num) => acc + num);
    }

    console.log(...nums) // 0 1 2 3 4 5
    console.log(sum(...nums)) // 15

函数提升

函数定义会进行声明提升,在函数被定义前调用函数也不会报错,函数表达式不会进行声明提升

函数作为值

就如同前面介绍的,函数也是一种对象,函数名就是指向函数的指针,也就是说,如果我们将函数的指针作为值传递的话,我们就可以在函数中传递函数。

函数作为返回值

当函数作为返回值时,每次调用一个函数都会将这个返回值函数返回。

    function say1 () {
      console.log('我是say1');
      return function say2 () {
        console.log('22222');
      };
    }
    // receiver接收了say1()的返回值
    let receiver = say1(); // 我是say1 调用了say1函数

    // receiver等价于如下代码
    // let receiver = function say2(){
    //   console.log('22222');
    // }

    // 调用receiver
    receiver() // 22222

    // 如果想直接调用say2而不使用中间变量存储的话
    say1()() // 我是say1 22222

这个技术也是闭包的关键。

回调函数

JavaScript 中的回调函数(Callback Function)是一种将函数作为参数传递给另一个函数,并在特定条件满足时(如异步操作完成、事件触发等)被调用的机制。它是实现异步编程的核心模式之一,尤其在单线程的 JavaScript 环境中,通过回调函数可以避免阻塞主线程,提升程序的响应性和效率。

将函数名作为参数传递给另一个函数,由另一个函数决定怎么使用传入的函数,这个使用者函数就被称为回调函数。

简单说来,回调函数就是把指向函数的指针传递到另一个函数内部,由于拥有了函数的指针,于是可以在另一个函数中调用被传入的函数。

常见的Array.prototype.map()reduce()filter()setTimeOut()等方法都使用了回调函数。

函数内部

  • arguments:和前面介绍的一样,arguments是一个类数组对象,他存储了传入对象的参数。

    • callee:arguments还有一个callee属性,这个属性是一个指向arguments对象所在函数的指针

      在使用函数的递归调用时,如果使用函数名进行递归调用,那么这个函数名就是不能更改的,但使用arguments.callee属性就不会导致这个问题。

      在严格模式下不能使用callee属性,可以使用命名函数表达式

          const func = function f(n) {
            if (n === 2)
              return 2;
            return n * f(n - 1);
          }
      

      在定义函数时参数列表使用f()的形式,就可以通过f()调用自己。

  • this:标准函数中this指向调用的对象,而箭头函数的this指向定义函数时的上下文。

    在之后详解。

  • new.target:检测函数是否是被new关键词调用的,如果被new关键字调用,那么值为被调用的构造函数,如果不是被new关键字调用的,则为undefined。

函数的属性

每个函数都有两个属性

  • length:保存了函数定义时,命名参数的个数

  • prototype:指向函数原型的指针

尾调用优化

尾调用​(Tail Call)是指函数的最后一步操作是调用另一个函数,且无需对返回值进行额外操作。例如:

function foo(x) {
    return bar(x); // 尾调用,直接返回 bar(x) 的结果
}

尾调用优化​(TCO)是 JavaScript 引擎对符合尾调用条件的函数进行栈帧复用的机制。其核心原理是:

  • 当函数 A 的最后一步调用函数 B 时,引擎会直接复用 A 的栈帧,而非创建新的栈帧

  • 这减少了调用栈的深度,避免了递归导致的栈溢出(Stack Overflow),并降低内存消耗

尾调用优化的条件

要使 JavaScript 引擎触发尾调用优化,需满足以下条件:

  1. 严格模式:必须启用 "use strict"

  2. 最后一步调用:调用必须是函数的最后一步操作,返回值直接来自被调用函数,不能有后续计算(如 return g(x) + 1 不满足)

  3. 无闭包依赖:被调函数不能依赖当前函数的局部变量(否则需保留栈帧)

你可能感兴趣的:(javascript,开发语言,ecmascript)