# 函数

函数是一个 JavaScript 代码块,定义之后,可以被执行或调用任意多次。JavaScript 函数是参数化的,即函数定义可以包含一组标识符,称为参数或形参(parameter)。

这些形参类似函数体内定义的局部变量。函数调用会为这些形参提供值或实参(argument)。

函数通常会使用实参的值计算自己的返回值,这个返回值会成为函数调用表达式的值。除了实参,每个调用还有另外一个值,即调用上下文(invocation context),也就是 this 关键字的值。

设计用来初始化一个新对象的函数称为构造函数(constructor)

# 1、函数声明

function test() {
  const x = 1;
  const y = 2;
  return x + y;
}
1
2
3
4
5

# 2、调用函数

函数可以通过 5 种方式来调用:

  • 作为函数
  • 作为方法
  • 作为构造函数
  • 通过 call() 或 apply() 方法间接调用
  • 通过 JavaScript 语言特性隐式调用(与常规函数调用不同)

# 2.1 递归函数与调用栈

递归函数是调用自己的函数,当函数调用另一个函数时,就会有一个新执行上下文被推到这个调用栈上面。

如果函数调用自己达到上万次,很可能会导致“最大调用栈溢出”(Maximum call-stack size exceeded)错误。

# 2.2 方法调用链

如果方法返回对象,那么基于这个方法调用的返回值还可以继续调用其他方法。这样就会得到表现为一个表达式的一系列方法调用(或方法调用链)。

a().b().c().d();
1

# 2.3 隐式函数调用

  • 如果对象有获取方法或设置方法,则查询或设置其属性值可能会调用这些方法。
  • 当对象在字符串上下文中使用时(比如当拼接对象与字符串时),会调用对象的 toString() 方法。类似地,当对象用于数值上下文时,则会调用它的 valueOf() 方法。
  • 在遍历可迭代对象的元素时,也会涉及一系列方法调用
  • 标签模板字面量是一种伪装的函数调用。
  • 代理对象的行为完全由函数控制。这些对象上的几乎任何操作都会导致一个函数被调用。

这个概念是 Martin Fowler 提出的

# 3、定义自己的函数属性

test.counter = 0;
function test() {
  return test.counter++;
}

test(); // => 0
test(); // => 1
1
2
3
4
5
6
7

函数将自身作为一个数组

function test(arr, n) {
  for (let i = 0; i < arr.length; i++) {
    test[i] = arr[i] * n;
  }
}
test([3, 4, 5], 2);
console.log(test[0]); // 6
1
2
3
4
5
6
7

# 4、call() 和 apply() 方法

call() 和 apply() 允许间接调用一个函数,就像这个函数是某个其他对象的方法一样。call() 和 apply() 的第一个参数都是要在其上调用这个函数的对象,也就是函数的调用上下文,在函数体内它会变成 this 关键字的值。

f.call(o);
f.apply(o);

f.call(o, 1, 2)
f.apply(o, [1, 2])

const b = Math.max.apply(null, [1, 2, 3, 4]);
console.log(b);
1
2
3
4
5
6
7
8

# 5、bind() 方法

bind() 方法的主要目的是把函数绑定到对象。如果在函数 f 上调用 bind() 方法并传入对象 o,则这个方法会返回一个新函数。如果作为函数来调用这个新函数,就会像 f 是 o 的方法一样调用原始函数。传给这个新函数的所有参数都会传给原始函数。

function f(y) { return this.x + y }
let o = { x: 1 }
let g = f.bind(o)
g(2)
1
2
3
4

箭头函数从定义它们的环境中继承 this 值,且这个值不能被 bind() 覆盖,因此如果前面代码中的函数 f() 是以箭头函数定义的,则绑定不会起作用。

bind() 可以执行“部分应用”,即在第一个参数之后传给 bind() 的参数也会随着 this 值一起被绑定。部分应用是函数式编程中的一个常用技术,有时候也被称为柯里化(currying)。

let sum = (x, y) => x + y;
let succ = sum.bind(null, 1);
succ(2) // => 3

function f(y, z) { return this.x + y + z }
let g = f.bind({ x: 1 }, 2);
g(3) // => 6
1
2
3
4
5
6
7

# 6、toString() 方法

实践中,这个方法的多数(不是全部)实现都返回函数完整的源代码。内置函数返回的字符串中通常包含“[native code]”,表示函数体。

# 7、Function() 构造函数

Function() 构造函数可以接收任意多个字符串参数,其中最后一个参数是函数体的文本。这个函数体文本中可以包含任意JavaScript 语句,相互以分号分隔。传给这个构造函数的其他字符串都用于指定新函数的参数名。如果新函数没有参数,可以只给构造函数传一个字符串(也就是函数体)。

Function() 构造函数不接收任何指定新函数名字的参数。与函数字面量一样,Function() 构造函数创建的也是匿名函数。

const f = new Function('x', 'y', 'return x*y;');
// 相当于:
const f = function(x, y) { return x*y; };
1
2
3
  • Function() 函数允许在运行时动态创建和编译 JavaScript 函数。
  • Function() 构造函数每次被调用时都会解析函数体并创建一个新函数对象。如果在循环中或者被频繁调用的函数中出现了对它的调用,可能会影响程序性能。
  • Function() 始终编译成顶级函数
let a = 'hello'
function test() {
  let a = 'world'
  return new Function('return a')
}

test()() // => 'hello'
1
2
3
4
5
6
7
上次更新: 2/21/2023, 9:39:49 PM