当函数可以持有并访问作用域,就产生了必包,即使函数在作用域外执行;

function foo(){
   var a = 2;
   function bar(){
      console.log(a);
   }
   return bar;
}
var baz = foo();
baz(); // 这就是必包!

函数bar可以访问foo的内部作用域,又将bar引用的函数对象本身当作返回值。

foo()执行后,bar作为返回值赋值给baz,实质是外部baz引用内部bar函数。通常在foo执行完毕后会在作用域中被销毁,因为引擎会使用垃圾回收机制释放内存,但由于必包阻止了回收(示例中bar在使用自身)。而bar在foo作用域中,所以foo一直存在,后续的bar可以一直访问foo作用域。bar一直持有该作用域,就是必包。

只要函数在该作用域外被传递就会产生必包,如:

function wait(messages){
	setTimeout(function timer(){
		console.log(messages);
	}, 1000);
}
wait("hello, colsure!");

循环和必包

来看个示例

for (var i = 0; i <= 5; i++){
   setTimeout(function timer(){
      console.log(i);
   }, i * 1000)
}

我们希望每隔1秒输出0 – 6的数字。但由于i是全局变量,实际是每秒输出i的最后结果6(6个6);要按预期输出,就需要在每次循环时产生新的作用域。我们可以使用自执行匿名函数(IIFE)创建作用域。

for (var i = 0; i <= 5; i++){
   (function (){
      setTimeout(function timer(){
         console.log(i);
      }, i * 1000)
   })();
}

现在每次循环已经创建了新的作用域,但是作用域都是空的,所以需要在为每个作用域加入变量声明:

for (var i = 0; i <= 5; i++){
   (function (){
      var j = i;
      setTimeout(function timer(){
         console.log(j);
      }, j * 1000)
   })();
}

可以按照预期输出1,2,3,4,5,6了;代码再修改下就更完美了。

for (var i = 0; i <= 5; i++){
   (function (j){
      setTimeout(function timer(){
         console.log(j);
      }, j * 1000)
   })(i);
}

如果用块级作用域就更完美了。

"use strict";
for (let i = 0; i <= 5; i++){
   setTimeout(function timer(){
      console.log(i);
   }, i * 1000);
}