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;
};
函数表达式和函数声明之间的区别
函数表达式和函数声明之间有几个关键区别:
- 函数声明被提升(hoisted),而函数表达式不会被提升。这意味着你可以在定义函数声明之前调用它,但不能使用函数表达式这样做。
- 函数表达式在定义之后可以立即使用,而函数声明必须等待整个脚本被解析后才能使用。
- 函数表达式可以用作另一个函数的参数,但函数声明不能。
- 函数表达式可以是匿名的,而函数声明不能。
理解函数表达式中的作用域: 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
};
}());
结论
正如我们所见,函数表达式并不与函数声明有根本的区别,但它们通常会导致更清晰、更易读的代码。
它们的广泛使用使它们成为每个开发者工具箱中不可或缺的一部分。你是否以我上面未提及的有趣方式在你的代码中使用函数表达式?评论让我知道吧!