函数表达式 与 函数声明 别搞混了

简介: 函数表达式 与 函数声明 别搞混了

在JavaScript中,function关键字做一个简单的工作:创建一个函数。但是,使用关键字定义函数的方式可以创建具有不同属性的函数

在这篇文章中,你将了解如何使用function关键字来编写函数声明和函数表达式,以及这两种类型的函数之间有什么区别。


1. 函数表达式 VS 函数声明

函数声明和函数表达式是使用Function关键字创建函数的两种方法。

让我们举个例子来说明两者的区别——我们先创建两个版本的求和函数:

function sumA(a, b) {
  return a + b;
}
(function sumB(a, b) {
  return a + b;
});
sumA(1, 2); // ???
sumB(1, 2); // ???

在一种情况下,可以像往常一样定义函数(如:sumA() )。在另一种情况下,函数被放置在一对括号中(如:sumB())。

如果你调用sumA(1,2)sumB(1,2)会发生什么?

正如预期的那样,sumA(1, 2)只是返回12个数字的和3。然而,调用sumB(1, 2)抛出一个未捕获的ReferenceError: sumB未定义。

原因是,sumA是使用函数声明创建的,该函数声明在当前作用域中创建了一个函数变量(与函数名同名)。但是sumB是使用函数表达式创建的(它被括在圆括号中),它不会在当前作用域内创建函数变量

如果你想访问使用函数表达式创建的函数,那么将函数对象保存到一个变量中:

// Works!
const sum = (function sumB(a, b) {
  return a + b;
});
sum(1, 2); // => 3

下面是一个关于如何区分函数声明和函数表达式的简单提示:

如果语句以function关键字开头,那么它就是一个函数声明,否则就是一个函数表达式。

// 函数声明: 以 `function` 关键字开头
function sumA(a, b) {
  return a + b;
}
// 函数表达式: 不以 `function` 关键字开头
const mySum = (function sumB(a, b) {
  return a + b;
});
// 函数表达式: 不以 `function` 关键字开头
[1, 2, 3].reduce(function sum3(acc, number) { 
  return acc + number 
});

从更高的角度来看,函数声明对于创建独立的函数很有用,但是函数表达式作为回调很好

现在,让我们深入了解函数声明和函数表达式的行为。

2. 函数声明

正如你在前面的例子中已经看到的,sumA是一个函数声明:

// 函数声明
function sumA(a, b) {
  return a + b;
}
sumA(4, 5); // => 9

当一个语句包含function关键字,后面跟着函数名、一对带有参数(param1, param2, paramN)的圆括号以及用一对**大括号{}**括起来的函数体时,就会发生函数声明。

函数声明创建了一个函数变量——**一个与函数名相同的变量(**例如前面例子中的sumA)。函数变量可以在当前作用域(在函数声明之前和之后),甚至在函数作用域本身内访问。

函数变量通常用于调用函数或将函数对象传递给其他函数(高阶函数)

例如,我们写一个函数sumArray(array),它对数组(数组可以包含数字或其他数组)中的项进行递归求和:

sumArray([10, [1, [5]]]); // => 16
function sumArray(array) {
  let sum = 0;
  for (const item of array) {
    sum += Array.isArray(item) ? sumArray(item) : item;
  }
  return sum;
}
sumArray([1, [4, 6]]); // => 11

函数sumArray(array){…}是一个函数声明。

包含函数对象的函数变量sumArray在当前范围内可用:在 sumArray([10,[1,[5]]])之前和在sumArray([1,[4, 6]])之后的函数声明,以及在函数本身的范围内sumArray(item)(允许递归调用)。

由于变量提升,函数变量在函数声明之前可用。

2.1 函数声明的注意事项

函数声明语法的作用是创建独立的函数。函数声明应该在全局作用域内或直接在其他函数的作用域内:

// Good!
function myFunc1(param1, param2) {
  return param1 + param2;
}
function bigFunction(param) {
  // Good!
  function myFunc2(param1, param2) {
    return param1 + param2;
  }
  const result = myFunc2(1, 3);
  return result + param;
}

出于同样的原因,不建议在条件语句(if)和循环语句(while, for)中使用函数声明:

// Bad!
if (myCondition) {
  function myFunction(a, b) {
    return a * b;
  }
} else {
  function myFunction(a, b) {
    return a + b;
  }
}
myFunction(2, 3);

而使用函数表达式可以更好地在条件语句中创建函数。

3. 函数表达式

function关键字在表达式中创建一个函数(有或没有名称)时,才是一个函数表达式。

以下是使用表达式创建的函数示例:

// 函数表达式
const sum = (function sumB(a, b) {
  return a + b;
});
const myObject = {
  myMethod: function() {
    return 42;
  }
};
const numbers = [4, 1, 6];
numbers.forEach(function callback(number) {
  console.log(number);
  // logs 4
  // logs 1
  // logs 1
});

在函数表达式中创建的函数有两种:

  1. 如果表达式内的函数没有名称,例如function() {return 42},则这是一个匿名函数表达式;
  2. 如果函数有名字,例如前面例子中的sumBcallback,那么这就是一个命名函数表达式

3.1 函数表达式的注意事项

函数表达式适合作为回调函数或由条件创建的函数:

// 由条件创建的函数
let callback;
if (true) {
  callback = function() { return 42 };
} else {
  callback = function() { return 3.14 };
}
// 作为回调函数使用
[1, 2, 3].map(function increment(number) {
  return number + 1;
}); // => [2, 3, 4]

如果你已经创建了一个命名函数表达式,请注意函数变量只在已创建函数范围内可用:

const numbers = [4];
numbers.forEach(function callback(number) {
  console.log(callback); // logs function() { ... }
});
console.log(callback); // ReferenceError: callback is not defined
  • Callback是一个命名函数表达式,因此Callback函数变量只能在Callback()函数范围内使用,而不能在Callback()函数范围外使用。

但是,如果你将函数对象存储到一个常规变量中,那么你可以在函数作用域内外从该变量访问函数对象:

const callback = function(number) {
  console.log(callback); // logs function() { ... }
};
const numbers = [4];
numbers.forEach(callback);
console.log(callback); // logs function() { ... }

4. 总结

根据如何使用function关键字创建函数,可以以两种方式来创建函数:函数声明和函数表达式。

函数声明发生在以function关键字开始的语句中:

// 函数声明
function sumA(a, b) {
  return a + b;
}

函数声明对于创建独立的、通用的函数非常有用。

但是,如果一个语句不以function关键字开头,那么就有一个函数表达式:

// 函数表达式
(function sumB(a, b) {
  return a + b;
});

使用函数表达式创建的函数对于根据条件创建回调函数或函数很有用。

最后,考考大家:

function sum(a, b) { return a + b } + 1;

是函数声明还是函数表达式,留言写下你的答案!



相关文章
|
12天前
|
存储 缓存 JavaScript
哪些情况适合使用块级作用域,哪些情况适合使用函数作用域?
【10月更文挑战第29天】块级作用域和函数作用域在不同的场景下各有优势,合理地选择和运用这两种作用域可以使JavaScript代码更加清晰、高效和易于维护。在实际开发中,需要根据具体的业务需求、代码结构和编程模式来决定使用哪种作用域,或者在适当的情况下结合使用两者,以达到最佳的编程效果。
|
3月前
|
JavaScript 前端开发
IIFE(立即执行函数表达式)
IIFE(立即执行函数表达式)
39 1
|
3月前
函数声明与函数表达式的区别是什么?
函数声明与函数表达式的区别是什么?
36 0
|
6月前
|
JavaScript 前端开发
函数声明与函数表达式的区别
函数声明与函数表达式的区别
27 2
|
6月前
|
编译器
函数声明
函数声明。
114 1
|
6月前
|
编译器 C++
C++中函数的默认参数(缺省参数)
C++中函数的默认参数(缺省参数)
57 0
|
存储
函数声明与函数表达式的区别?
函数声明与函数表达式的区别?
32 0
函数重载&函数默认参数
函数重载&函数默认参数
38 0
|
JavaScript 前端开发 Go
立即执行函数
立即执行函数
58 0
|
自然语言处理 JavaScript 前端开发
深入理解立即执行函数
深入理解立即执行函数