何时使用函数表达式与函数声明

102 阅读8分钟

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

函数声明已经使用了很长时间,但函数表达式已经逐渐取代了它。许多开发者不确定什么时候使用其中一种,所以他们最终使用了错误的一种。

函数表达式和函数声明之间有几个关键的区别。让我们仔细看看这些区别,以及何时在你的代码中使用函数表达式和函数声明:

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;
};

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

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

  • 函数声明是吊起来的,而函数表达式不是。这意味着你可以在函数声明被定义之前调用它,但你不能用函数表达式来这样做。
  • 对于函数表达式,你可以在定义了一个函数后立即使用它。对于函数声明,你必须等到整个脚本被解析之后。
  • 函数表达式可以作为另一个函数的参数使用,但函数声明不能。
  • 函数表达式可以是匿名的,而函数声明则不能。

了解你的函数表达式中的范围。JavaScript吊装的区别

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

而函数表达式是不被悬挂的。这使得它们可以保留一份来自定义它们的作用域的局部变量的副本。

通常情况下,你可以交替使用函数声明和函数表达式。但有时函数表达式会产生更容易理解的代码,而不需要一个临时的函数名。

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

那么,什么时候应该使用函数表达式与函数声明呢?

答案取决于你的需求。如果你需要一个更灵活的函数或者一个不被吊起的函数,那么函数表达式就是你的选择。如果你需要一个更可读、更易懂的函数,那么就使用函数声明。

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

今天,当你需要做一些函数表达式不能做的事情时,你通常会使用函数声明。如果你不需要做只有函数声明才能做的事情,那么一般来说,最好使用函数表达式。

当你需要创建一个递归的函数时,或者当你需要在定义函数之前调用它时,请使用函数声明。作为一条经验法则,当你不需要做这两件事时,使用函数表达式可以使代码更简洁。

函数声明的好处

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

  • 它可以使你的代码更有可读性:如果你有一个很长的函数,给它一个名字可以帮助你跟踪它在做什么。
  • 函数声明是挂起的,这意味着它们在你的代码中被定义之前就可以使用。如果你需要在函数被定义之前使用它,这对你有帮助。

函数表达式的好处

函数表达式也有一些好处:

  • 它们比函数声明更灵活。你可以创建函数表达式并将其分配给不同的变量,当你需要在不同的地方使用同一个函数时,这很有帮助。
  • 函数表达式不是吊起来的,所以你不能在代码中定义之前使用它们。如果你想确保一个函数只在它被定义后使用,这就有帮助。

什么时候选择函数声明与函数表达式

在大多数情况下,要弄清楚哪种定义函数的方法最适合你的需要是很容易的。这些准则将帮助你在大多数情况下做出快速决定。

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

  • 你需要一个更易读、更易理解的函数(比如一个长函数,或者一个你需要在不同地方使用的函数)
  • 一个匿名函数不能满足你的需要
  • 你需要创建一个可以递归的函数
  • 你需要在函数被定义之前调用它

在以下情况下使用函数表达式

  • 你需要一个更灵活的函数
  • 你需要一个不被提升的函数
  • 该函数只能在它被定义时使用
  • 该函数是匿名的,或者不需要一个名字供以后使用
  • 你想控制函数的执行时间,使用立即调用函数表达式(IIFE)等技术
  • 你想把函数作为一个参数传递给另一个函数

也就是说,在很多情况下,函数表达式的灵活性成为一种强大的资产。

打开函数表达式的大门:JavaScript吊装的区别

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

  • 封闭
  • 其他函数的参数
  • 立即调用的函数表达式(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
    }
}

for 循环中提取出doSomething() 函数,就更容易理解为什么会出现这个问题:

// 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)

IIFEs有助于防止你的函数和变量影响全局范围。

里面的所有属性都落在匿名函数的范围内。这是一种常见的设计模式,用来防止你的代码在其他地方产生不需要的或不想要的副作用。

它也被用作模块模式,在易于维护的部分包含代码块。我们在《解密JavaScript闭包、回调和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
    };
}());

总结

正如我们所看到的,函数表达式与函数声明没有本质的区别,但它们往往能使代码更简洁、更易读。

它们的广泛使用使它们成为每个开发人员工具箱中必不可少的一部分。你是否在你的代码中以任何有趣的方式使用函数表达式,而我在上面没有提到?请发表评论,让我知道。