夯实基础--你所不知道的当即执行函数

基础回顾

当即调用的函数表达式是咱们常用的函数编码模式之一,也被称之为 IIFE 。javascript

在咱们了解 IIFE 是什么以及为何须要它以前,咱们须要快速回顾一下关于 Javascript 函数中的基本概念。java

函数声明

function say() {
    console.log('Hello World');
}

say(); // print: Hello World
复制代码
  • 咱们在 1-3 行定义了一个名为 say 的函数,
  • 第 5 行使用函数名以及一对圆括号对函数进行调用。

这种建立函数的方式称之为:函数声明。git

一般,刚接触 Javascript 的开发人员使用这种语法没有问题, 由于它很是相似于其余流行的编程语言中的函数或方法。github

咱们须要注意的是,函数声明老是以 function 关键字开头,后面紧跟着函数的名称, 而且名称不可省略。express

函数表达式

let msg = 'Hello World';
let say = function() {
  console.log(msg);
}

say(); // print:Hello World
复制代码
  • 第1行声明了 msg 变量并为其分配了一个字符串值
  • 第 2-4 行声明 say 变量并为其赋予函数类型的值
  • 第 6 行使用变量名以及一对圆括号对函数进行调用

在上面的例子中,咱们为变量 say 赋予了函数类型的值, 这种赋值运算符右侧的函数一般称为: 函数表达式。编程

函数表达式在 Javascript 中无处不在,咱们可能编写的大多数回调函数一般都是函数表达式。浏览器

所以,咱们要知道一个重要的概念,在 Javascript 中函数几乎与其余任何值同样, 既能够位于赋值运算符的右侧,也能够做为参数传递给其余函数。安全

匿名函数

顾名思义,匿名函数就是没有名字的函数。闭包

let say = function() {
  console.log('Hello World');
}
复制代码

咱们建立了一个函数表达式,而它其实也是匿名函数, 由于 function 关键字后面没有名字。编程语言

具备名称的函数表达式

函数表达式一样能够有函数名称,此时函数名称只是函数体内的一个本地变量,没法在外部调用, 它的最主要的做用是在递归中使用。

let fn = function say() {
    console.log('Hello World');
}
say(); // print error :say is not defined
复制代码

开始 IIFE

咱们已经简单回顾了函数定义和函数表达式,如今让咱们直接进入 IIFE 的秘密世界。

咱们但愿获得的是一个能够当即执行的函数,那么咱们声明一个函数, 而后经过括号()来执行不就好了么,咱们来试一下

它们有几种风格。让咱们首先看到一个很是容易理解的变体。

!function() {
    console.log("Hello from IIFE!");
}();
复制代码

当帮这段代码复制到浏览器的控制台中尝试时,会在控制台中打印咱们想要输出的内容。

接下来,咱们来理解这段不是那么直观的代码:

在 Javascript 语言中,当 function 关键字视为有效语句中的第一个单词时, Javascript 引擎会将其当作函数声明来处理。 所以,为了防止这种状况的发生,咱们在第 1 行代码中的 function 关键字前加上 “!”前缀。 此时 Javascript 会将这段代码视为函数表达式。

在代码的第 3 行,经过一对圆括号对函数表达式进行调用。

因此,咱们得出结论,什么是 IIFE:函数表达式,在建立后当即执行,就成为 IIFE。

经过将“!”替换为“+”,“-”,“~”甚至“void”,“typeof”甚至“delete”。

咱们能够在浏览器的控制台上,尽情的尝试~

!function(){console.log('!')}();
+function(){console.log('+')}();
-function(){console.log('-')}();
~function(){console.log('~')}();
void function(){console.log('void')}();
typeof function(){console.log('typeof')}();
delete function(){console.log('delete')}();
复制代码

上面例子中,关键字 function 前的“!、+、-、~、void、typeof、delete”所作的事, 就是将函数转换为函数表达式而不是函数声明。

这种方式构造的 IIFE 很容易理解,接下来咱们介绍其余更传统和更普遍使用的 IIFE 风格。

经典 IIFE 风格

(function(){
    console.log("I'm not IIFE")
})
复制代码

在上面的代码中,函数表达式包含在第 1-3 行的括号中。 它还不是 IIFE,由于函数表达式永远不会被执行。 如今要将该代码转换为 IIFE,咱们有如下两种风格变化:

//方法1
(function(){
    console.log("I'm IIFE")
})()
//方法2
(function(){
    console.log("I'm IIFE")
}())
复制代码

如今咱们生成了 2 个 IIFE,可能很难注意到这两者的区别,解释一下:

  1. 在方法1中和方法2中,咱们都是用一对括号()将函数体包裹起来,使之变成函数表达式。
  2. 在方法1中,用于调用函数表达式的括号(),包含在外括号内。
  3. 在方法2中,用于调用函数表达式的括号(),在外括号外。

这两种方法都被普遍的使用,咱们能够根据本身的喜爱使用其中任何一个。

如今让咱们再看一个有效的例子,以及两个无效的例子。 咱们将从如今开始命名咱们的 IIFE,由于使用匿名函数一般不是一个好主意。

//有效的IIFE
(function initAppIIFE() {
    //全部你的神奇代码
}());

//如下两个是无效的IIFE示例
function nonWorkingIIFE() {
    //如今你知道为何你须要我周围的那些括号!
    //若是没有这些括号,我是一个函数声明,而不是一个表达式。
    //你会收到语法错误!
}();

function () {
     //这里也会出现语法错误!
}();
复制代码

咱们得出一个结论:

只有函数表达式可以建立 IIFE ,函数声明永远不会用于建立 IIFE。

IIFE 和私有变量

IIFE 很是擅长的一件事就是可以为 IIFE 建立私有做用域。

在 IIFE 内声明的任何变量对外界都不可见。

来看一个例子:

(function IIFE_initApp() {
    // IIFE 的私有变量,外部没法访问
    let lives;
    let weapons;
    init();

    // IIFE 的私有函数,外部没法访问
    function init() {
        lives = 5;
        weapons = 10;
    }
}());
复制代码

当咱们须要建立一堆变量和函数时,咱们定义一个 IIFE 并在 IIFE 内建立咱们所需的变量和函数, 这样既不会污染全局空间,也能够保护本身的代码不被他人之外的影响。

具备返回值的 IIFE

若是咱们不须要来自 IIFE 的返回值,那么咱们可使用咱们最早介绍的经过一元运算符(!、+、-、~等)来实现的IIFE。

~function IIFE_initApp() {
    //...
}
复制代码

但 IIFE 的另外一个很是重要且强大的功能是它们能够返回能够分配给变量的值。

let result = (function() {
    return "From IIFE";
}());

console.log(result); // print:"From IIFE"
复制代码

在上面的代码中,咱们建立了一个 IIFE 当即执行,并将返回值传递给变量 result

这是一个很是强大的功能,咱们将在后文介绍模块的时候来使用它。

具备参数的 IIFE

IIFE 不只能够有返回值,也能够在调用时进行参数传递,咱们来看一下:

(function IIFE(msg, times) {
    for (let i = 1; i <= times; i++) {
        console.log(msg);
    }
}("Hello!", 5));
复制代码

在这个例子中,咱们建立了一个 IIFE 而且给予了两个参数 msg 和 times。

这是一个很是强大的功能,咱们常常在 Jquery 或其余库中看到这种使用方式。

(function($, global, document) {
    // use $ for jQuery, global for window
}(jQuery, window, document));
复制代码

在这个例子中,咱们将 jQuery、window 和 document 做为参数传递给 IIFE, 在 IIFE中的代码使用 $、global 和 document 做为形参变量来接收这三个实参。

这种传递参数的有点以下:

  1. JS引擎在查找变量时,从当前做用域开始查找,若是找不到就会向上一级继续查找,直至抵达最外层的全局做用域。 从性能上考虑,所需的变量存在于当前做用域时,变量查找时间要小于变量存在于上层做用域。
  2. 在将代码发布线上时,咱们会对JS代码进行压缩,安全的缩小函数中声明的参数名称。若是咱们没有将 jQuery、window、document 做为参数传递至 IIFE 内,则在对JS代码进行压缩时,全部使用到这些参数的地方,不会对这些参数引用进行压缩。

经典的 JavaScript 模块模式

下面咱们经过 IIFE 来实现一个模块模式的例子。

咱们来实现一个经典的 Sequence 对象,咱们分为两步编写此代码,以便逐步了解正在发生的事情。

let Sequence = (function sequenceIIFE() {
    // IIFE 中的私有变量
    let current = 0;
   
    // 经过 IIFE 返回一个空对象
    return {};
}());

console.log(typeof Sequence); // print:"object"
复制代码

在上面的例子中,咱们建立了一个返回空对象的 IIFE,而且在 IIFE 内建立了一个私有变量 current。

接下来,咱们作一些改进,在 IIFE 返回的空对象中添加一些函数。

let Sequence = (function sequenceIIFE() {
    // IIFE 中的私有变量
    let current = 0;
   
    // 经过 IIFE 返回一个空对象
    return {
        getCurrentValue: function() {
            return current;
        },
        getNextValue: function() {
            current = current + 1;
            return current;
        }
    };
    
}());

console.log(Sequence.getNextValue()); // 1
console.log(Sequence.getNextValue()); // 2
console.log(Sequence.getCurrentValue()); // 2
复制代码

在这个例子中,咱们在 IIFE 返回的对象中,添加了两个函数 getNextValue 和 getCurrentValue。

getCurrentValue 用于将当前的 current 的值返回。

getNextValue 用于将 current 的值递增 1 ,而且返回当前 current 的值。

因为 IIFE 中的变量 current 是私有的,外部没法访问的,由于只有经过 getCurrentValue 或 getNextValue 函数才能访问它的值。

这是一个很是强大的 Javascript 模块模式,它结合了 IIFE 和闭包的强大功能。

何时能够省略括号

用于包裹函数体的括号是用于强制将咱们正在操做的函数体变为函数表达式。

可是当 Javascript 引擎能够确认这是一个函数表达式时,咱们就不须要包裹在函数外的括号了。

let result = function() {
    return 'From IIFE'
}();
console.log(result);
复制代码

在上面的示例中,function 关键字不是语句中的第一个单词。所以 Javascript 引擎不会将其视为函数声明。

即便如此,依然仍是建议在函数体外包裹一对括号。

使用括号能够经过在第一行上对读者进行风格上的暗示,暗示该函数将成为 IIFE , 而没必要要滚动到函数的最后一行才能肯定是不是 IIFE ,所以使用括号能够提升代码的可读性。

参考


预计每周1-2篇文章,持续更新,欢迎各位同窗点赞+关注

后续内容参见写做计划

写做不易,若是以为稍有收获,欢迎~点赞~关注~

本文同步首发与github,欢迎在issues与我互动,欢迎Watch & Star ★

相关文章
相关标签/搜索