Javascript - 自执行函数:如果我可以创建不带自执行函数的本地作用域,为什么还要使用它们?

Javascript - self-executing functions : why to use them if I can create local scope with not self-executing functions?

我知道这里和其他地方有很多关于自执行函数的帖子,但阅读帖子后我仍然有一些疑问。

  1. 为什么我要将自执行函数分配给变量?如果看起来他们无论如何都会执行自己。

    var myFunc=(function() {
     console.log('Hello World');
    })();
    
  2. 我读了很多,使用自执行函数的原因是为了保持变量私有。如果我有一个非自执行函数,那么我在该函数中定义的所有内容都将是私有的?!

    (function() {
     var name="my Name"
     console.log(name);
    })();
    
    vs.
    
     function() {
     var name="my Name"
     console.log(name);
     };
     //its the same
    

所以我不太明白自执行函数是如何保持局部范围的(因为你可以使用非自执行函数来做到这一点)所以我看到的唯一原因是当你想自动执行时使用它们页面加载示例。

谢谢!

还有一个问题:

var test=(function myFunc(){
      var name="Hello World"
      return {
        test1: function(){
          return name;
        },
        test2:function(){
          return name+"1"
        }
      }
    })()

    test.test1()

vs

    var test=function myFunc(){
      var name="Hello World"
      return {
        test1: function(){
          return name;
        },
        test2:function(){
          return name+"1"
        }
      }
    }

    test.test1()

--> 这里到底发生了什么,因为 IIFE 我实际上可以执行 test.test1() 而不是使用常规函数?

  1. 如果那个 IIFE 没有 return 任何东西,那么将它分配给任何东西确实是绝对没有用的。虽然当然可能有 IIFE 的例子 returning 你以后想用的东西;在这种情况下,IIFE 是一个私有范围,用于设置一些对象,例如:

    var foo = (function () {
        var bar = 'something';
        // here be dragons
        return baz;
    })();
    

    这为您提供了 assemble baz 的私有作用域,而不会不必要地将临时变量泄漏到全局作用域中。

  2. 这些示例没有区别,只是第二个示例不执行,因此从不执行任何操作。范围界定和范围界定的目的没有改变。

当你想保持你的范围被包含时,你通常将你的函数包装在一个匿名函数中。这也是仍然很流行的模块模式的一部分:

https://toddmotto.com/mastering-the-module-pattern/

然后您可以将 IIFE 的结果分配给一个变量,这样您的作用域就只能通过调用该变量来访问。

myScope.myLocallyScopedProperty or myScope[myLocallyScopedProperty]

您的其他函数需要手动调用,并且也可以从任何地方访问。

我建议阅读 Todd Moto 的文章,它解释了很多。

首先,简而言之,这些不是自动执行的功能。 (那将是一个 递归 函数。)这些是内联调用的函数表达式 (IIFE)。 函数不调用自身,表达式调用函数。

why would I ever assign a self-executing function to a variable?

那不是该代码的作用。它将调用 IIFE 的 result 分配给变量。您可以在需要该结果时使用它,例如:

var x = (function() {
    var n = 0;
    return {
        increment: function() { return ++n; }
    };
})();
console.log(typeof x); // "object"
console.log(x.increment()); // 1
console.log(x.increment()); // 2

x 不接收 IIFE,它接收 IIFE returns 的内容——在本例中,是一个带有函数的对象。

I read a lot that the reason to use self-executing functions is to keep variables private. If I have a not self-executing function, everything I define inside that function is gonna be private anyways?!

是的,没错。当您只需要执行 IIFE 中的操作 一次 时,您可以使用 IIFE。否则,绝对可以定义函数,为其命名,然后在任何需要的地方重用它。函数内部的变量确实是私有的(除非以某种方式暴露),并且特定于对函数的每次调用。

In the form of IIFE (or immediately invoked function expressions), they can then be used to create plugins or used as namespaces and attached to window / jquery / or other global level object for use later.


当你命名一个函数时,比如将匿名函数分配给一个变量,你可以稍后通过用括号调用变量来使用它,在你的例子中,用

定义了 myFunc
var myFunc=(function() {
 console.log('Hello World');
}); 

稍后在代码中将其用作myFunc()

在您的示例中,您通过立即调用将函数的输出直接存储在变量中,并且没有要存储的输出。

因此,如果您稍后写入 console.log(myFunc);,则会有 undefined 作为输出。


下面提到的代码示例中更好的 IIFE 示例。

(function() {
 var name="my Name"
 console.log(name);
})();

它执行,执行 console.log 就这样。在您的案例中,不向命名空间或全局对象添加任何内容。


你的最后一个例子定义了一个函数,然后没有执行它,并且由于它没有分配命名变量或名称,它给出了语法错误并且不能在以后的代码中使用。所以,下面的例子是无用的。

function() {
 var name="my Name"
 console.log(name);
 };

您又添加了两个 var test = function myFunc 的示例。第一个可以与 test.test1() 一起正常工作。对于第二个,你需要先将 test 作为一个函数求值,然后调用它的函数,比如 test().test1().

1:将 IIFE 分配给局部变量对于类似这样的事情是有意义的:

var getID = (function () {
  var id = 0;
  return function () { return id++; };
})();

通过这种方式,您可以获得新的 ID,而不会 运行 从代码中的其他任何地方重置内部计数器的风险,除非重新声明变量。

2:基本上你通过创建一个函数来创建范围。但是,如果您不执行它,那么它什么也不会做。所以如果你有:

function () { return 'foo' };

如果没有赋值给变量或者没有名字,你想怎么调用呢?它本身不会做任何事情,因为它没有被调用。类似的东西是死代码,可以安全地删除。

你的第一件事毫无意义:

var myFunc = =(function() {
       console.log('Hello World');
    })();

myFunc 不是函数,它是 undefined

在我看来,自执行函数的意义在于打包一些必须立即执行的代码。

var p1=1, p2=2, obj = {
   prop: (function(x,y){ return x+y;})(p1, p2)
}

避免覆盖已经定义的 functions/objects 以防您的脚本将被插入到一个已经存在的应用程序中,并且如果您愿意,还可以创建一种私有方法:

function aFunction() {
  console.log('a code');
}
(function(w) {
  function aFunction() {
    console.log('b code');
  }
  w.run = function() {
    aFunction();
  };
})(window)
aFunction();
run();

我猜你在这里错过了什么。只是为了弄清楚基本的事情——如果你将一个自执行函数分配给一个变量,此时执行的函数的实际 return value 被分配给变量而不是函数本身。

var myFunc = (function() {
    console.log('Hello World');
})();
myFunc(); // will throw error: TypeError: myFunc is not a function
myFunc === undefined

var myFunc = (function() {
    console.log('Hello World');
    return 'japp';
})();
myFunc(); // will throw error: TypeError: myFunc is not a function
myFunc === 'japp'

那么为什么会出现这种模式?

IIFE 对

非常有用
  • 限制范围 如果你声明

    var test = 'test'; // var test actually will be assigned as a property of the current context (this)
    window.test = 'test'; // so you pollute the global namespace which is not a good practice
    

    所以这样会好很多

    (function() {
        var test = 'test';
    })();
    
  • 使用 IIF 的另一个非常好的事情是您可以使用 "privates"

    实现设计
    var myFunc;
    (function() {
        var i = 0; // i is available for myFunc with private access
        myFunc = function() { console.log( ++i ) };
    })();
    myFunc(); // logs 1
    myFunc(); // logs 2
    

您使用自执行函数仅在范围外公开您需要的内容。我想我有一个比较清楚的例子:

let myObject = (function(){
  let privateVariable = "I'm private";
  function privateMethod() {
    //private method
  };
  function methodToExpose() {
    //this method I will expose
  }

  //what is returned here, is what is public
  return {
    PublicMethod: methodToExpose
    //other public properties
  }
}());

所以,该函数立即执行,发生的事情是我有一个由我从函数返回的内容定义的对象。

我可以给你的另一个例子是在闭包中保留当前作用域的变量,但你不会真正使用它那么多,因为我们现在有 let。一个实际例子:

<span id="1">old</span>
<span id="2">old</span>
<span id="3">old</span>
<span id="4">old</span>
<script>
var toPrint = "";
for (var i = 1; i <= 4; i++) {
  toPrint = "new: " + i;
  document.getElementById(i.toString()).addEventListener('click', function(event){ event.target.innerHTML = toPrint; })
}

</script>

当您点击跨度时,该值将替换为值... "new: 4"! 那是因为当你完成执行时,这就是 toPrint 的值。分配给 click 事件的函数检索 toPrint,并且在检索它时,它是 "new: 4"。我们用闭包解决这个问题:

<span id="1">old</span>
<span id="2">old</span>
<span id="3">old</span>
<span id="4">old</span>
<script>
var toPrint = "";
for (var i = 1; i <= 4; i++) {
  toPrint = "new: " + i;
  document.getElementById(i.toString()).addEventListener('click', function(event){ 
    var currPrint = toPrint;
    return function(event){ event.target.innerHTML = currPrint ; };
  }())
}

</script>

通过使用自执行函数,我们将 toPrint 的当前值保存在局部范围内的 currPrint 变量中。当我们稍后点击一个 span 时,分配给点击的函数甚至会使用变量 currPrint,它包含 toPrint 在函数被分配时的值,而不是 toPrint 在完成执行时的值。

请注意,这也可以通过使用 let 而不是 var 来解决,但它仍然是自执行函数的一个示例:)