立即执行函数与闭包

一.作用域

作用域就是变量与函数的可访问范围。在JavaScript中,变量的作用域有全局作用域和局部作用域两种。

  • 全局作用域
    在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下 3 种情形拥有全局作用域。
    (1).最外层函数和在最外层函数外面定义的变量拥有全局作用域
    (2).所有未定义直接赋值的变量自动声明为拥有全局作用域
    (3).所有window对象的属性拥有全局作用域 。例如 window. name、window.location、window.top 等。
  • 局部作用域
    和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所有在一些地方也会看到有人把这种作用域称为函数作用域。

二.作用域链

(1).[[scope]]:每个javascript函数都是一个对象,对象中有些属性我们可以访问,但有些属性不可以,这些不可以访问的属性仅供javascript引擎存取,[[scope]]就是其中一个。
[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
(2).作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式连接,我们称这个链式连接为作用域链。

(3).运行期上下文:当函数执行时,会创建一个成为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会创建多个执行期上下文,当函数执行完毕,它所产生的执行期上下文会被销毁。注:只是销毁它产生的执行其上下文。并不是一整条作用域链。
(4).查找变量:从作用域链的顶端依次向下查找。
该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。

三.作用域链的详细解释

例一:

  function func() {
        var num = arguments[0];
        alert(num);
    }
    func(2);

在函数func创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,如下图所示(注意:图片只例举了全部变量中的一部分):
作用域链图片一
当函数func执行时,它自己会生成一个执行期上下文。
当执行期上下文被创建时,它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中共同组成了一个新的对象,叫“活动对象(activation object)”。该对象包含了函数的所有局部变量、命名参数、参数集合以及this。然后此对象会被推入作用域链的前端。即这个执行期上下文会被安排在作用域链的第1位,GO则会变为第2位。当执行期上下文被销毁,活动对象也随之销毁。新的作用域链如下图所示:

在这里插入图片描述
例二:
如果函数a中嵌套函数b。
那么函数b的创建是在函数a创建的基础上的。那么b创建时的作用域链就是a执行时的作用域链。b执行时的作用域链就是b的AO在作用域链的第1位,a的AO在作用域链的第2位,GO在作用域链的第3位。
假设函数b的执行在函数a外部,则a执行完毕后会立即销毁它的作用域链的第1位与a的AO的连线。但b执行时它的作用域链的第2,3位是函数a的劳动果实,它们之间依然存在联系。函数b仍然可以访问函数a中的变量。也就是说a的AO并没有真正被销毁。这就产生了闭包。

四.立即执行函数

1.Javascript提供的唯一一个可以在函数执行完后可以立即销毁函数的方式。
注:函数定义在全局环境之内,它的GO作用域永远不会被销毁,它始终处于等待执行的状态。
注:一旦立即执行函数执行后,就会失去对原来函数的索引。即一次执行完毕,函数名在控制台的状态为未定义。
2.方式:

  • ( function(){}() ) 【W3C建议这一种】
  • ( function(){})()

3.立即执行函数也可以有返回值
例如:

    var   num=(  function(a,b,c){
        d=a  +  b + c*2;
        return   d;
    }(1,2,3))
4.立即执行函数深层的理解:立即执行函数为什么会是这样的形式?

(1).js中有这样的一个概念:只有表达式才能被执行符号执行
(执行符号包括"()")
例如:

     function  test(){  console.log("a");  }

这条语句是函数声明,并不是函数表达式。
当将这条语句变为以下时函数能被正常调用。

      function  test(){  console.log("a");  }
      test();

但当这条语句变为以下时控制台会报错。

      function  test(){  console.log("a");  }();

原因就是test是一个表达式,而 function test(){ console.log(“a”); }不是一个表达式。
(2).如何让 function test(){ console.log(“a”); }变为一个表达式呢?
我们可以在 function test(){ console.log(“a”); }之前加上运算符"+" ,"-","!"等让
function test(){ console.log(“a”); }变为一个表达式。

+function  test(){  console.log("a");  }();
-function  test(){  console.log("a");  }();
! function  test(){  console.log("a");  }();

这三种形式也都成为了立即执行函数。执行一次后,再在控制台上输入函数名称,会显示出错。
(3).js中也有这样一个概念能被执行符号执行的表达式会自动放弃函数名称。
因此我们写函数名称实际并无作用。即以下也是立即执行函数。

+function(){  console.log("a");  }();
-function (){  console.log("a");  }();
! function(){  console.log("a");  }();

既然"+","-","!“等可以让函数变为立即执行函数,那”()"自然也可以。即以下也就是立即执行函数。

 (function(){  console.log("a");})();

既然上式为立即执行函数,那么下面这个函数又要如何理解?

  (function(){  console.log("a");} ());

我们将最外层的一对花括号当作运算符的话,里面的整体就可以当作表达式。而function(){ console.log(“a”);也就变成了表达式,可以被其后紧跟的执行符号立即执行。
这两种立即执行函数的形式是最常见的。还有一些与立即执行函数原理相同的函数同样也可以实现立即函数所要实现的功能。

五.闭包

(1).当内部函数被保存到外部环境时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露。
(2).闭包的作用:

  • 实现公有变量
    例如:不依赖于外部变量,并且能反复执行的函数累加器。

       function  add(){
      		var   count=0;
      		function  a(){
      			console.log(++count);
      		}
      		return  a;
      	}
      	var  myAdd=add();
      	myAdd();
      	myAdd();
      	myAdd();
      	myAdd();
      	myAdd();
    
  • 可以做缓存(存储结构)
    原理:多个函数和一个函数a()形成闭包,这多个函数之间的变量可以共用。也就是说这个函数里的变量被修改,如果这个变量出生于a()中,则它的值在被修改后会被更新并保存下来。等下一个函数再对这个变量进行操作时,操作的就是已被更改的新变量。
    例如:eater函数

           function   test(){
      		var  food="apple";
      		var  obj={
      			eatFood:function(){
      				if(food!=""){
      					console.log("I  am   eating   "+food);
      					food="";
      				}
      				else{
      					console.log("There  is  nothing!Empty!");
      				}
      			},
      			pushFood:function(myFood){
      				food=myFood;
      			    console.log("There  is "+food);
      			}
      		}
      		return  obj;
      	}
      	var  person=test();
      	person.eatFood();
      	person.pushFood("banana");
      	person.eatFood();
    
  • 可以实现封装
    例如:Person();

  • 模块化开发,防止污染全局变量

(3).做个小练习
例一:
要求:打印出0-9。

    function  test(){
			var  arr=[];
			for(var i=0;i<10;i++){
				arr[i]=function(){
					document.write(i);
				}
			}
			return   arr;
		}
		var  myArr=test();
		for(var  j=0;j<10;j++){
			myArr[j]();
		}

但页面中会显示10个数字"10"。

  • 为什么会有这样的结果呢?
    在赋值语句中,当通过循环赋值把一个函数体或函数引用赋值给数组的当前位时,数组的当前位是很快会被索取出来的,但函数体并不是立刻执行,这只是函数定义,因此数组当前位"i"的值与函数体的内容无关。即使函数体中的内容是要在控制台打印出与"i"有关的内容,也与数组的当前位"i"毫无关系。函数体执行时才真正考虑函数体内的内容。

    对于上面的这个函数而言,

                 function(){
      				document.write(i);
      			}
    

    的执行是在函数test()执行完之后,才在函数外部执行十次,这十次的作用域链的2,3位都是直接获取函数test()的劳动成果。它们在执行时,先搜索自己的内部环境,没有"i"变量的定义,然后就开始查询作用域链的第二位,即函数test()的内部环境,发现"i"已经成为了10,执行相同的操作十次便会出现10个数字"10"。

  • 唯一解决方法
    立即执行函数

    解决方法:

    将每个循环内的语句变成一个函数体。也就是由原来循环要执行十次的*

                arr[i]=function(){
      				document.write(i);
      			}
    

    变成要执行十次的

              (function(j){
      			arr[j]=function(){
      				document.write(j);
      			}  })(i)
    

    原理

       **第一个是赋值语句,第二个是函数体。变为了函数体,就是给赋值语句的外部又添了一个执行环境。实参"i"的值传给形参"j"。j的值也会从0变到9。当函数外部要执行myArr [j] ();时,**
                
                function(){
      				document.write(j);
      			}
    

    会在离自己最近的上层环境中先查询,有"j"便把"j"的值打印出来,执行完后销毁这一个函数体,再执行下一个函数体。这十个函数体中的"j"都不相同,因此自然便能打印出不相同的数值。

    注意:

    循环体中如果要给每个数组成员绑定事件,这些事件的执行也会在函数外部。因此这样也会产生闭包问题。比如:

    <ul>
      	<li>a</li>
      	<li>a</li>
      	<li>a</li>
      	<li>a</li>
      </ul>
     <script>
      var   liCollection=document.getElementsByTagName("li");
      	function  mytest(){
      		for(var  i=0;i<liCollection.length;i++){
      				liCollection[i].addEventListener("click",function(){
      					console.log(i);
      				},false);
      		}
      	}
      	mytest();
      	</script>
    

    实际上就相当于

     <ul>
      	<li>a</li>.addEventListener("click",function(){
      					console.log(i);
      				},false);
      	<li>a</li>.addEventListener("click",function(){
      					console.log(i);
      				},false);
      	<li>a</li>.addEventListener("click",function(){
      					console.log(i);
      				},false);
      	<li>a</li>.addEventListener("click",function(){
      					console.log(i);
      				},false);
      </ul>
    

    此时的"i"会变成liCollection.length。产生了闭包。解决方法同例一。

猜你喜欢

转载自blog.csdn.net/qq_44875145/article/details/97645468
今日推荐