当使用函数表达式和函数声明时的区别和应用场景

457 阅读8分钟

JavaScript中创建函数有两种方式:函数表达式和函数声明。本文将讨论何时使用函数表达式和函数声明,并解释它们之间的区别。

函数声明(Function Declaration)在 JavaScript 中已经被使用了很长一段时间,但是函数表达式(Function Expression)逐渐占据了主流。许多开发人员不确定何时应该使用其中的一种,因此他们最终会使用错误的一种方式。

函数表达式和函数声明之间有一些关键的差异。让我们更仔细地看看这些差异,并了解在你的代码中何时使用函数表达式和函数声明。

function funcDeclaration() {
    return 'A function declaration';
}

let funcExpression = function () {
    return 'A function expression';
}

什么是函数声明?

函数声明是指创建一个函数并给它一个名称。你在写下函数关键字后,跟着函数名来声明函数的名称。例如:

function myFunction() {
  // do something
};

如您所见,函数名称(myFunction)在创建函数时声明。这意味着可以在定义函数之前调用函数。

以下是函数声明的示例:

function add (a, b) {
  return a + b;
};

什么是函数表达式?

函数表达式是指创建一个函数并将其分配给一个变量。该函数是匿名的,也就是说它没有名称。例如:

let myFunction = function() {
  // do something
};

正如你所看到的,函数被分配给了myFunction变量。这意味着你必须在调用之前定义该函数。

下面是一个函数表达式的例子:

let add = function (a, b) {
  return a + b;
};

函数表达式和函数声明之间的区别

函数表达式和函数声明之间有几个关键区别:

  1. 函数声明被提升(hoisted),而函数表达式不会被提升。这意味着你可以在定义函数声明之前调用它,但不能使用函数表达式这样做。
  2. 函数表达式在定义之后可以立即使用,而函数声明必须等待整个脚本被解析后才能使用。
  3. 函数表达式可以用作另一个函数的参数,但函数声明不能。
  4. 函数表达式可以是匿名的,而函数声明不能。

理解函数表达式中的作用域: JavaScript hoisting 的区别

类似于 let 语句,函数声明也被提升到其他代码的顶部。

函数表达式不会被提升。这使它们能够保留从定义它们的作用域中继承的本地变量的副本。

通常情况下,函数声明和函数表达式是可以互换使用的。但是在某些情况下,函数表达式能够产生更易于理解的代码,而无需使用临时函数名称。

如何在表达式和声明之间进行选择

翻译:那么,何时应该使用函数表达式和函数声明呢?

答案取决于您的需求。如果您需要一个更灵活的函数或一个不被提升的函数,则函数表达式是正确的选择。如果您需要一个更可读和易懂的函数,则使用函数声明。

正如您所看到的,这两个语法是相似的。最明显的区别是函数表达式是匿名的,而函数声明具有名称。

如今,当您需要执行函数表达式无法完成的操作时,通常会使用函数声明。如果您不需要执行只能使用函数声明完成的操作,则通常最好使用函数表达式。

当您需要创建递归函数或在定义之前调用函数时,请使用函数声明。作为一个经验法则,当您不需要执行这些操作时,请使用函数表达式以获得更清晰的代码。

函数声明的优点

使用函数声明有几个关键的好处。

它可以使您的代码更易读。如果您有一个很长的函数,给它命名可以帮助您跟踪它正在做什么。 函数声明被提升,这意味着它们在代码中定义之前就可用。如果您需要在定义函数之前使用该函数,则这将有所帮助。

函数表达式的优点

函数表达式也有几个好处。

它们比函数声明更灵活。您可以创建函数表达式并将其分配给不同的变量,在需要在不同位置使用相同函数时非常有用。

函数表达式不会被提升,因此您不能在代码中定义函数之前使用它们。这对于确保函数在定义后才被使用非常有帮助。

何时选择函数声明与函数表达式

在大多数情况下,很容易确定哪种定义函数的方法最适合您的需求。这些指南将帮助您在大多数情况下快速做出决策。

在以下情况下使用函数声明:

您需要更可读和易理解的函数(例如长函数或需要在不同位置使用的函数)。 匿名函数无法满足您的需求。 您需要创建一个递归函数。 您需要在定义函数之前调用该函数。

在其他情况下,可以考虑使用函数表达式。函数表达式更灵活,可以在需要时创建匿名函数,并且不会被提升到代码顶部,这可以帮助您避免意外行为。 使用函数表达式的情况:

您需要一个更灵活的函数。 您需要一个不被提升的函数。 该函数应该在定义后才能使用。 该函数是匿名的,或者不需要为以后的使用命名。 您希望使用类似立即调用的函数表达式(IIFE)的技术来控制函数何时执行。 您希望将函数作为参数传递给另一个函数。 话虽如此,有很多情况下,函数表达式的灵活性成为了一种强大的资产。

解锁函数表达式:JavaScript 提升的区别

函数表达式比函数声明更有用的几种不同方式:

闭包(Closures) 作为其他函数的参数 立即调用的函数表达式(IIFE)

使用函数表达式创建闭包

闭包在您希望在函数执行之前给函数传递参数时使用。一个很好的例子是在循环 NodeList 时如何受益。

闭包允许您保留其他信息,例如索引,在函数执行后该信息不再可用的情况下。

function tabsHandler(index) {
    return function tabClickEvent(evt) {
        // Do stuff with tab.
        // The index variable can be accessed from within here.
    };
}

let tabs = document.querySelectorAll('.tab'),
    i;

for (i = 0; i < tabs.length; i += 1) {
    tabs[i].onclick = tabsHandler(i);
}

附加的事件处理程序在稍后执行(在循环完成后),因此需要闭包来保留 for 循环的适当值。

// Bad code, demonstrating why a closure is needed
let i;

for (i = 0; i < list.length; i += 1) {
    document.querySelector('#item' + i).onclick = function doSomething(evt) {
        // Do something with item i
        // But, by the time this function executes, the value of i is always list.length
    }
}

将 doSomething() 函数从 for 循环中提取出来可以更容易地理解问题发生的原因。

// Bad code, demonstrating why a closure is needed

let list = document.querySelectorAll('.item'),
    i,
    doSomething = function (evt) {
        // Do something with item i.
        // But, by the time this function executes, the value of i is not what it was in the loop.
    };

for (i = 0; i < list.length; i += 1) {
    item[i].onclick = doSomething;
}

解决方法是将索引作为函数参数传递给外部函数,以便它可以将该值传递给内部函数。通常会看到处理程序函数用于组织内部返回函数所需的信息。

// The following is good code, demonstrating the use of a closure

let list = ['item1', 'item2', 'item3'],
    i,
    doSomethingHandler = function (itemIndex) {
        return function doSomething(evt) {
            // now this doSomething function can retain knowledge of
            // the index variable via the itemIndex parameter,
            // along with other variables that may be available too.
            console.log('Doing something with ' + list[itemIndex]);
        };
    };

for (i = 0; i < list.length; i += 1) {
    list[i].onclick = doSomethingHandler(i);
}

将函数表达式作为参数传递

函数表达式可以直接传递给函数,无需被分配给一个中间临时变量。

通常会看到它们以匿名函数的形式出现。这是一个熟悉的 jQuery 函数表达式示例:

$(document).ready(function () {
    console.log('An anonymous function');
});

在使用 forEach() 等方法处理数组项时,也可以使用函数表达式来处理它们。

它们不必是未命名的匿名函数。最好给函数表达式命名,以帮助表达函数的预期功能,并在调试时提供帮助:

let productIds = ['12356', '13771', '15492'];

productIds.forEach(function showProduct(productId) {
    ...
});

立即调用函数表达式(IIFE)

IIFE 可以帮助防止函数和变量影响全局作用域。

在 IIFE 中定义的所有属性都在匿名函数的作用域内。这是一种常见的设计模式,用于防止代码在其他地方产生不需要或不想要的副作用。

它也用作模块模式,将代码块包含在易于维护的部分中。我们在 "Demystifying JavaScript closures, callbacks, and IIFEs" 中更深入地探讨了这些内容。

以下是一个简单的 IIFE 示例:

(function () {
    // code in here
}());

...当作为一个模块使用时,可以让你的代码更易于维护。

let myModule = (function () {
    let privateMethod = function () {
        console.log('A private method');
    },
    someMethod = function () {
        console.log('A public method');
    },
    anotherMethod = function () {
        console.log('Another public method');
    };

    return {
        someMethod: someMethod,
        anotherMethod: anotherMethod
    };
}());

结论

正如我们所见,函数表达式并不与函数声明有根本的区别,但它们通常会导致更清晰、更易读的代码。

它们的广泛使用使它们成为每个开发者工具箱中不可或缺的一部分。你是否以我上面未提及的有趣方式在你的代码中使用函数表达式?评论让我知道吧!