IIFE – 立即执行函数表达式






4.86/5 (11投票s)
关于 JavaScript 中立即执行函数表达式的深入指南。
这是关于管理 JavaScript 作用域的系列文章中的第一篇。在全局作用域中创建的变量和函数由于可能被应用程序的其他部分覆盖而存在风险。在您和您的朋友展示 JavaScript 技能的小型边项目可能不会出现这个问题,但在有许多人在不同 JavaScript 文件上工作的真实项目中,这肯定会成为一个问题。我们将首先了解 IIFE 是什么,然后讨论一个使用 IIFE 和不使用 IIFE 的实际示例。
立即执行函数表达式 (IIFE,发音为 iffy)
IIFE 是一个包含在一对括号中的匿名函数,并且会立即被调用。括号对为主函数内部的所有代码创建了一个局部作用域,并将匿名函数变成了一个函数表达式。这解释了“立即执行函数表达式”这个名字。如果您不熟悉函数表达式,我鼓励您阅读我的文章《理解 JavaScript 中的函数》。
一个典型的 IIFE 看起来像这样。
(function() { // Your awesome code here }());
有些人也这样写。
(function() { // Your awesome code here })();
我更喜欢第一种风格,这也是 Douglas Crockford 推荐的。因此,我所有的解释都将基于第一种风格。
最外层的括号对将它里面的所有内容变成一个表达式,因为括号不能包含 JavaScript 语句。函数定义后的另一对括号会立即调用该函数。让我们看一个例子。
(function() { alert("I created my first IIFE today"); }());
上面的函数将被自动调用,并在消息框中显示消息。我们将尝试逐个理解这个 IIFE。让我们从编写您想执行的函数开始。
function() { alert("I created my first IIFE today"); }
这是一个匿名函数,JavaScript 将为此报告错误,因为这种匿名函数语法无效。但目前我们可以暂且忽略它,因为我们的 IIFE 还没有完成。现在我们将在函数体末尾添加一对括号,使其立即执行。
function() { alert("I created my first IIFE today"); }()
最后一步是将所有内容包装在另一对括号中,使其成为一个函数表达式。
(function() { alert("I created my first IIFE today"); }());
在 IIFE 块内定义的任何变量或函数都作用于该块的局部范围,外部代码无法更改它们。
(function() { var x = "Hello"; console.log(x); // Shows "Hello" to console }()); console.log(x); // Throws error "Uncauht ReferenceError: x is not defined"
也可以向 IIFE 传递参数。我们是这样做的。
(function(firstName, lastName) { var fullName = firstName + " " + lastName; console.log(fullName); }("Jim", "Cooper")); // Output: Jim Cooper
值“Jim”和“Cooper”作为 firstName
和 lastName
传递给函数,并用于构造 fullName
。
现在我们了解了 IIFE,让我们以电子商务应用程序中的产品详情页面为例,看看 IIFE 如何解决一些严重的问题。
您可以考虑在电子商务应用程序中有一个名为 productName
的全局变量,它在产品详情页面上运行的 JS 文件中创建于全局作用域。产品详情页面可能正在执行许多分散在多个 JS 文件中的 JavaScript 代码。虽然加载和显示产品信息的脚本可能写在一个文件中,但将产品添加到购物车的脚本可能存在于另一个文件中。可能有一个专门的 JS 文件来加载和显示产品评论。关键是,当今真实世界的应用程序中的网页运行着大量的 JavaScript 文件来处理不同的场景。
productName
这个产品名称是一个非常常见的术语,以至于产品详情页面上的多个 JS 文件可能都有一个名为 productName
的变量。如果变量不是在本地作用域下创建的,那么 productName
变量很可能在其他文件中被意外修改。
真实世界的应用程序是基于适当的作用域来实现的。我希望您此时会同意全局变量在 JavaScript 中是邪恶的,并且创建全局变量是一种不良实践。让我们看一个例子,试着理解代码中的问题。
function getPhone() { return "iPhone7"; } function displayProductDetails(productName) { console.log("This is " + productName); } function addProductToCart(productName) { console.log(productName + " added to your shopping cart"); } function makePayment(productName) { console.log("Making payment for " + productName); } function getRelatedProducts() { var relatedProducts = ["Headphone", "Phone cover", "Tempered glass"]; return relatedProducts; } function suggestMeAHeadphone() { var relatedProducts = getRelatedProducts(); productName = relatedProducts[0]; return productName; } var productName = getPhone(); displayProductDetails(productName); addProductToCart(productName); var headPhone = suggestMeAHeadphone(); makePayment(productName); // Output: // This is iPhone7 // iPhone7 added to your shopping cart // Making payment for Headphone
上面的示例演示了一个产品详情页面,其中产品详细信息在用户界面中加载和显示。用户可以将产品添加到购物车。用户可以选择相关产品推荐中的产品,并可能将其添加到购物车(为了简化,我没有编写将相关产品添加到购物车的代码)。最后,用户为产品(Phone)付款。
这可能不是一个您会非常欣赏的例子,但意图是理解全局变量引起的问题。如果您仔细查看代码,函数 getPhone
的调用返回的产品存储在变量 productName
中。由于该变量在任何函数之外,它将存在于全局命名空间中。
现在看一下发生在 makePayment
之前的 suggestMeAHeadphone
函数的调用。suggestMeAHeadphone
函数调用 getRelatedProducts
函数,将相关产品列表中的第一个产品分配给变量 productName
,并返回 productName
。您可能已经注意到,变量 productName
被赋予了一个新值,即 suggestMeAHeadphone
函数中耳机的名称。最后,将调用 makePayment
函数来完成结账流程。但是,由于全局变量 productName
的所有邪恶之处,makePayment
函数将写入“Making payment for Headphone”这条语句,而它本应写入“Making payment for iPhone7”。
用 IIFE 来解决
既然您已经了解了立即执行函数表达式的基础知识,让我们尝试使用 IIFE 来编写电子商务示例,并避免变量被外部代码访问局部作用域。除了处理“相关产品”的代码之外,我将所有代码都放入一个 IIFE 中。请看。
(function() { function getPhone() { return "iPhone7"; } function displayProductDetails(productName) { onsole.log("This is " + productName); } function addProductToCart(productName) { console.log(productName + " added to your shopping cart"); } function makePayment(productName) { console.log("Making payment for " + productName); } var productName = getPhone(); displayProductDetails(productName); addProductToCart(productName); makePayment(productName); }()); // Output: // This is iPhone7 // iPhone7 added to your shopping cart // Making payment for iPhone7
我再次说明,这段代码并不非常现实,因为很多操作,如将产品添加到购物车和付款,都将在用户单击用户界面上的某个按钮时发生。我将示例保持简短简单,以专注于我们讨论的核心。
我将把剩余的代码,即处理“相关产品”的代码,放在另一个 IIFE 中,它可能存在于完全不同的 JS 文件中。
(function(){ function getRelatedProducts() { var relatedProducts = ["Sony Headphone", "Phone cover", "Tempered glass"]; return relatedProducts; } function suggestMeAHeadphone() { var relatedProducts = getRelatedProducts(); var productName = relatedProducts[0]; console.log("We suggest you to buy " + productName); return productName; } var headPhone = suggestMeAHeadphone(); }()); // Output: We suggest you to buy Sony Headphone
通过使用 IIFE 实现局部作用域,我们的变量 productName
现在是安全的。尽管我在第二个 IIFE 中使用了 var
关键字在 productName
前面,但如果省略 var
,则会创建一个新的全局变量 productName
,而第一个 IIFE 中的同一个变量仍然是安全的。如果您忽略了我所展示的电子商务示例的质量,那么您现在应该能够理解 IIFE 及其在避免全局作用域污染方面的用途。大多数现代 JavaScript 库,如 jQuery、Backbone,都使用 IIFE 模式将所有代码保留在局部作用域中。
就是这样!
我希望您喜欢阅读这篇文章。这是关于管理 JavaScript 作用域的第一个入门帖子。在下一篇文章中,我将向您介绍 模块和揭示模块模式,以实现私有变量和函数的实现。
您有任何疑问,或者有不清楚的地方吗?请留下您的评论。我将用一个 IIFE 来结束这篇文章,并祝您一切顺利。
(function() { alert("Happy JavaScripting!!"); }());
你可能还对以下内容感兴趣