理解js中的作用域以及初探闭包


前言

对于js中的闭包,无论是老司机还是小白,我想,见得不能再多了,然而有时三言两语却很难说得明白,反正在我初学时是这样的,脑子里虽有概念,但是却道不出个所以然来,在面试中经常会被用来吊自己的胃口,考察基础,虽然网上自己也看过不少相关闭包的文章,帖子,但貌似这玩意,越看越复杂,满满逼格高,生涉难懂的专业词汇常常把自己带到沟里去了,越看越迷糊,其实终归结底,用杨绛先生的一句话就是:“你的问题在于代码写得太少,书读得不够多”,其实在我看来前者是主要的,是后者的检验, 自知目标搬砖20年(还差19年..),其实,闭包的应用,在我们不经意间就使用了,是无处不在的,尽管知道闭包是个麻烦的玩意,自己也经常吃回头草看看这家伙的,所谓出去混,迟早是要还的,今天,我就对闭包的一点理解作一点点总结,关于闭包,我也一直在学习当中…话说多了,都是故事,直接撸起袖子,开始干吧

正文从这里开始~

理解上下文和作用域

其实上下文与作用域是两个不同的概念,有时我自己也经常混淆,把它们视为是同一个东西,我们知道函数的每次调用都会有与之紧密相连的作用域和上下文,从本质上说,作用域其实是基于函数的,而上下文基于对象的,也就是说作用域是涉及到它所被调用函数中的变量访问,而调用方法和访问属性又存在着不同的调用场景(4种调用场景,函数调用,方法调用,构造器函数调用,call(),apply()间接调用),而上下文始终是this所代表的值,它是拥有控制当前执行代码的对象的引用

变量作用域

在javascript中,作用域是执行代码的上下文(方法调用中this所代表的值),作用域有三种类型:全局作用域(Global scope),局部作用域(Local/Function scope,函数作用域)和eval作用域,在函数内部使用var定义的代码,其作用域都是局部的,且只对该函数的其他表达式是可见的,包括嵌套子函数中的代码,局部变量只能在它被调用的作用域范围内进行读和写的操作,在全局作用域内定义的变量从任何地方都是可以访问的,因为它是作用域链中的最高层中的最后一个,在整个范围内都是可见的,注意在Es6之前是没有块级作用域的,而Es6后是有的,也就是说Esif,while,switch,for语句是有了块级作用域的,可以使用let关键字声明变量,修正了var关键字的缺点,注意let使用规则
看如下代码所示:

		  * 全局变量与局部变量
		  *
		  * @global variable {variable="itclab"}
		  * @function myFun
		  * @local variable {variable="itclanCode",variable=24}
		  * @function otherFun
		  * @eval作用域 evalfun
  */
	  var variable = "itclan";//全局变量
	  console.log("全局variable","=",variable); // 全局variable = itclan
	  // 函数表达式
	  var myFun = function(){
	  	 var variable = "itclanCode"; //局部变量
	  	 console.log("局部variable","=",variable); // 局部variable = itclanCode
	  	 var otherFun = function(){
	  	 	 var variable = 24;   //局部变量
	  	 	 console.log("局部variable","=",variable);  // 局部variable = 24
	  	 }
	  	 otherFun();
	  }
	  myFun();
	  eval("var evalfun = 20;console.log('evalfun作用域','=',evalfun)");// evalfun作用域 = 20

注意

  • 函数可以嵌套函数,并可以无限的嵌套下去,也就是可以创建无数的函数作用域和eval作用域,而javascript坏境只是用一个全局作用域
  • 全局作用域(global scope)是作用域链中的最后一层
  • 包含函数的函数,会创建堆栈执行的作用域,这些链接在一起的栈通常被称为作用域链(也就是后面会提到闭包产生的本质原因)

什么是执行坏境

所谓执行坏境,它定义了变量或函数有访问的其他数据的能力,它决定了各自的行为,它的侧重点在于函数的作用域,而并不是所要纠结的上下文,一旦函数一声明定义,就会自动的分配产生了作用域,有着自己的执行坏境,执行坏境可以分为创建与执行两个阶段,在创建阶段,js解析器首先会创建一个变量对象(活动对象),它由定义在执行坏境中的变量,函数声明和参数组成,在这个阶段,系统会自动的产生一个this对象,作用域链会被初始化,随之,this的值也会被确定,第二阶段,也就是代码执行,代码会被解释执行,你会发现,每个执行坏境都有一个与之关联的变量对象,执行坏境中所有定义的变量和函数都保存在这个对象中,注意,我们是无法手动的访问这个对象的,只有js解析器才能够访问它,其实也就是this,尽管很抽象,但是理解它还是蛮重要的

作用域链(词法作用域)

javascript查找与变量相关联的值时,会遵循一定的规则,也就是沿着作用域链从当前函数作用域内逐级的向上查找,直到顶层全局作用域结束,若找到则返回该值,若无则返回undefined,这个链条是基于作用域的层次结构的,一旦当代码在坏境中执行时,会自动的创建一个变量对象的作用域链,其作用域链的用途也就是保证对执行坏境的全局变量和具有访问权限函数内的局部变量定制特殊的规则,由内到外有序的对变量或者函数进行访问,作用域链包含了在坏境栈中的每个执行坏境对应的变量对象,通过作用域链可以决定变量的访问与标识符的解析,如下代码所示:

		       * 作用域链变量的访问
			   *
			   * @global variable {name="随笔川迹"}
			   * @function fun1,fun2
			   * @local variable {oTherName="哇嘎嘎",AliasName = "川川"}
			   * @return {fun2,name,oTherName,AliasName}
			   * @return fun2,fun1函数的返回结果值为fun2的值
			   *
			   *
		   */
		   var name = "随笔川迹";   // 全局变量
		   var fun1 = function(){
   var oTherName = "哇嘎嘎";// 局部变量
   var fun2 = function(){
   	   var AliasName = "川川";  // 局部变量
   	   AliasName = oTherName;
   	   oTherName = AliasName;
   	   return {name,oTherName,AliasName};
   }
   console.log(fun2());
   return fun2();
		   }
		   //console.log(fun2()); // 若在全局作用域调用访问fun()会失败,显示fun2 is not defined
		   console.log(fun1(),"name is","=",name)

当我们分析这段代码时,首先全局范围全局变量name,函数fun1嵌套fun2函数,fun1,fun2函数内局部变量分别为:oTherName,AliasName当在函数fun2内,并未声明name变量,便在该函数fun2内进行了访问,这是如何找到的?javascript首先在当前fun2函数作用域内查找一个名为name的变量,但是在fun2并未找到,于是它会查找它的父函数fun1的作用域内进行查找,但是发现仍然没有找到,于是在往外进行查找,结果在全局作用域范围内查找了name的值,于是找到了便把该值进行返回,若是在全局作用域内还未找到则会返回undefined,注意在函数fun2作用域内,name,oTherName,AliasName都是可以访问的,而在函数fun1函数作用域内是访问不了oTherName的,因为它脱离了fun1的函数的作用域嘛,我们知道在函数外是无法访问函数里面的的变量的,访问变量由内向外进行查找是可以的,但是反之则就不行,从上图的箭头分析图可知,内部坏境中,是可以通过作用域链访问它所有的外部坏境,但是在外部坏境是无法访问内部坏境中的任何变量和函数,这点很重要,我们在函数嵌套函数,并且进行函数调用时,要格外注意,如果在编程当中出现这种函数is not defined那么就是牵扯到函数作用域的问题了,在函数外是无法访问函数内的变量或者函数的,当然这种问题是可以解决的,也就是后面提到的闭包,其实上面我们的代码中就已经无形用了闭包,匿名函数fun1,fun2就是个闭包,嵌套函数与被嵌套坏境的连接是线性的,有次序的,对于标识符(也就是变量或者函数名查找)是从当前函数作用域开始,沿着作用域链逐级的向上查找,直到最顶端全局变量坏境,若找到该值则返回,若无则返回undefined
注意:理解作用域以及作用域链对理解原型链是很有帮助的,其实他们区别并不是很大,两者都是通过位置体系(上下嵌套关系)和分层体系来查找值的方法,进而可以对变量或者函数进行读和写的操作,如下代码所示:

	  var x = 5;
	  var fun1 = function(){
	  	 var y = 10;
	  	 var fun2 = function(){
	  	 	 var z = 20;
	  	 	 return z+y+x;
	  	 }
	  	 fun2();
	  	 return fun2();
	  }
	  console.log("x+y+z的和=",fun1()); //x+y+z的和= 35

javascript没有块级作用域

在Es6之前,如if,for,while,switch逻辑语句是无法创建作用域,也就是它后面的双大括号并没有域的作用,这才得式变量可以相互覆盖,解决办法,你可以使用es6的let关键字声明变量,注意let的使用,如下代码所示

          var str = "itclan";     // 全局变量
	      console.log(str);       // itclan
	      if(true){               // if逻辑语句
	      	 str = "itclanCode";
	      	 console.log(str);    // itclanCode
	      	 for(var i = 0;i<=2;i++){
	      	 	str = i;
	      	 	console.log(str); // 0,1,2
	      	 }
	           console.log(str);    // 2
	      }
	      console.log(str);       // 2 因此,代码在执行过程中,从上到下,str是变化的,因为在Es6之前,没有块级作用域,只有全局作用域,函数作用域,eval()作用域 `注意` 在函数中应用`var`声明变量,避免作用域的陷阱 javascript会将缺少`var`的变量声明,即便在函数或者封装在函数中,都会视为全局变量作用域,而非局部作用域,我们是不应该出现这种不要var声明的,这样会造成全局变量的污染,易混淆,如下代码所示
		 * 如果不使用var来声明变量,那么,该变量实际上是在全局作用域中定义,而不是局部作用域中定义(它本是在局部作用域中定义)
		 *
		 * @descortion:这样很容易产生误解,应当杜绝这么干
		 * @在函数内定义的变量应用var,当然要在函数内部创建或更改全局作用域内的属性就另当别论了的
		 *
		 *
	     */
	     var fun1Exp = function(){
	     	 var fun2Exp = function(){
	     	 	 name = "污葵"; // 没有使用var,它相相当于window.name
	     	 }
	     	 fun2Exp();
	     }
	     fun1Exp();
	     console.log({name});
	     // 相反,使用var的情况
	     var fun3Exp = function(){
	     	 var fun4Exp = function(){
	     	 	 var age = 20; //使用var,局部变量
	     	 }
	     	 fun4Exp();
	     }
	     fun3Exp();
	     console.log(age);  //Uncaught ReferenceError: age is not defined,报错的原因,age在fun4Exp函数作用域中,在函数外是访问不了函数内部的变量的

作用域是在函数定义时就确定的,而非调用时确定

因为函数决定作用域,又因为函数也是对象,也是一种数据类型,一样可以像基本数据类型值一样被作为值来传递,作用域就是根据函数定义时的位置确定的,而与该函数在哪里被调用无关,其实就是词法作用域,作用域链是在调用函数之前创建,也是这样,我们就可以创建闭包,我们常常是这么做的,让函数向全局作用域返回一个嵌套函数,但该函数仍然能够通过作用域访问它父函数的作用域,作用域链是在定义时确定的,并在函数内部传递代码不会改变作用域
如下代码所示:

		  * 作用域链是在函数定义时位置确定的,而非函数调用位置,在函数内部传递代码不会改变作用域链
		  *
		  * @funtion expression parentFun
		  * @local variable localVal
		  * @return parentFun的返回值为一个匿名函数,访问该匿名函数外的变量
		  *
  */
	  var parentFun = function(){
	  	  var localVar = "itclan是个有温度的公众号";
	  	  return function(){  // 返回一个匿名函数
	  	  	  console.log(localVar);
	  	  }
	  }
	  var nestedFun =  parentFun();//nestedFun引用parentFun函数,把函数parentFun函数的返回值赋值给变量nestedFun
	  nestedFun();   // 输出itclan是个有温度的公众号,因为返回的函数可以通过作用域链访问到localVar变量

产生闭包的根本原因是作用域链

在通过上面的了解变量的作用域和作用域链后,相信你理解闭包就不难了,如下代码所示:

		  * 闭包是由作用域链引用的
		  *
		  * @function expression countNum 匿名函数
		  * @local variable count
		  * @return 匿名函数
		  *
  */
	  var countNum = function(){
	  	  var count = 0;
	  	  return function(){  //调用countNum的时候返回嵌套的子函数
	  	  	 return ++count;// count在作用域链内定义,父函数里
	  	  };

	  }(); // 匿名函数的立即调用,返回嵌套函数
	  // countNum(),上面的匿名函数后若不加括号调用,则返回的结果将是return 后面的函数的整体代码
	  console.log(countNum());   // 1
	  console.log(countNum());   // 2
	  console.log(countNum());   // 3

当每次调用countNum函数时,嵌套在该函数内的匿名函数是可以访问父函数(这里指的是countNum的)作用域的,其实这就是所谓的闭包,作用链就是闭包的桥梁,用来连接内部函数与外部函数的关系,从而达到外部函数访问内部函数局部变量或者函数的目的,其中被嵌套函数就可以称为是一个闭包

小结

  • 产生闭包的原因是由作用域链引起的
  • 函数嵌套函数,被嵌套的函数就可以称为闭包
  • 子函数可以使用父函数的变量(访问其他函数内部的局部变量)
  • 让变量始终保存在内存中,避免自动垃圾回收(其实上面的例子中就已经用到了的)
  • 对外提供公有属性和方法

总结:

整篇文章从理解上文和作用域开始,以及什么是执行坏境,其产生闭包的原因是作用域链,并知道在Es6之前是没有块级作用域的概念的,并且作用域是在函数定义时就确定的,而非函数调用确定,在我的理解中编程其实很大一部分就是对数据进行读和写的操作,其中读可以理解对定义变量数据的访问,而写可以理解赋值,引用,变更,改写操作,当然js中不像其他后台语言的存储数据类型那般复杂,基本就是基本数据类型和对象了,理解作用域以及作用域链对理解闭包是相当的重要,对后续的原型链以及继承都是相关联的,其实也不必抓着什么执行坏境和上下文这些相对抽象的概念不放,我们只有在平时的使用当中,稍稍留意就行,在应用中结合理论进行验证,当然闭包的内容远不及此..如果你想听对应的音频,可以关注微信公众号itclanCoder,更过内容直接期待

以下是本篇提点概要

  • 理解上下文和作用域,作用域是基于函数的,而上下文是基于对象的,虽然说函数也是对象,但是这里更多的是指对象直接量的表示法,上下文始终围绕着this所代表的值,它是拥有控制当前执行代码对象的引用
  • 变量的作用域,在Es6之前没有块级作用域,而Es6有了块级作用域,也就是if,,while,switch,for,若使用let关键字,则具备块级作用域,也就是说定义在双大括号内的变量,在双大括号内的才起作用,一旦离开该范围,就不起作用了
  • 什么是执行坏境,定义了变量或函数有访问的其他数据的能力,它决定了各自的行为,它的侧重点在于函数的作用域,而并不是所要纠结的上下文,分为创建坏境和执行坏境
  • 作用域链(词法作用域),当查找与变量相关联的值时,会遵循一定的规则,也就是沿着作用域链从当前函数作用域内逐级的向上查找,直到顶层全局作用域结束,若找到则返回该值,若无则返回undefined
  • javascript没有块级作用域,往往很多时候使用匿名函数自执行来模拟块级作用域
  • 作用域是在函数定义时就确定的,而非调用时确定,作用域就是根据函数定义时的位置确定的,而与该函数在哪里被调用无关,其实就是词法作用域
  • 产生闭包的根本原因是作用域链,见上小结

原文:大专栏  理解js中的作用域以及初探闭包

原文地址:https://www.cnblogs.com/chinatrump/p/11584705.html

时间: 2024-12-17 03:56:16

理解js中的作用域以及初探闭包的相关文章

聊一下JS中的作用域scope和闭包closure

scope和closure是javascript中两个非常关键的概念,前者JS用多了还比较好理解,closure就不一样了.我就被这个概念困扰了很久,无论看别人如何解释,就是不通.不过理越辩越明,代码写的多了,小程序测试的多了,再回过头看看别人写的帖子,也就渐渐明白了闭包的含义了.咱不是啥大牛,所以不搞的那么专业了,唯一的想法就是试图让你明白什么是作用域,什么是闭包.如果看了这个帖子你还不明白,那么多写个把月代码回过头再看,相信你一定会有收获:如果看这个帖子让你收获到了一些东西,告诉我,还是非常

理解js中的作用域,作用域链以及闭包

作用域变量作用域的类型:全局变量和局部变量全局作用域对于最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的 <script> var outerVar = "outer"; function fn(){ console.log(outerVar); } fn();//result:outer </script> 局部作用域和全局用域相反,局部作用域一般只在固定的代码片段内可访问到,对于函数外部是无法访问的 <script> fu

JS中的作用域和闭包

作用域:在编程语言中,作用域控制着变量与参数的可见性及生命周期.JS确实有函数作用域,那意味着定义在函数中的参数和变量在函数外部是不可见的,而且在一个函数中的任何位置定义的变量在该函数中的任何地方都是可见的. var a = 1; var fs = function (){ var b = 2; var c = 4 var fun = function (){ var c = 3; alert(a) //输出1 alert(b) //输出2 alert(c) //输出3 } fun(); } f

终于理解JS中的闭包了

之前看到一个观点是  闭包是走向高级Javascript的必经之路,之前看过很多关于闭包的讲解帖子,一直没有理解透彻,模棱两可. 现在终于可以讲出来了. 检验自己有没有掌握一个知识,最好的方式是讲给一个不懂的人 ,给Ta讲懂了.我做到了. 请有心读者检阅我的知识点有么有错误. 一:什么闭包 首先要理解 js特殊的作用域机制:只能按照作用域链向上访问,而不能访问Ta下级域中的变量. 闭包:就是能够读取其他函数内部变量的函数. (*^__^*) 一切函数某种环境下都可以当做闭包. 手写一个demo:

JS详细图解作用域链与闭包

JS详细图解作用域链与闭包 攻克闭包难题 初学JavaScript的时候,我在学习闭包上,走了很多弯路.而这次重新回过头来对基础知识进行梳理,要讲清楚闭包,也是一个非常大的挑战. 闭包有多重要?如果你是初入前端的朋友,我没有办法直观的告诉你闭包在实际开发中的无处不在,但是我可以告诉你,前端面试,必问闭包.面试官们常常用对闭包的了解程度来判定面试者的基础水平,保守估计,10个前端面试者,至少5个都死在闭包上. 可是为什么,闭包如此重要,还是有那么多人没有搞清楚呢?是因为大家不愿意学习吗?还真不是,

理解JS中的call、apply、bind方法

理解JS中的call.apply.bind方法(*****************************************************************) 在JavaScript中,call.apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向. call.apply.bind方法的共同点和区别:apply . call .bind 三者都是用来改变函数的this对象的指向的:apply . call .bind 三者

JavaScript中的作用域链和闭包

JavaScript中的作用域链和闭包 2012-06-29 11:41 1878人阅读 评论(46) 收藏 举报 JavaScript中出现了一个以前没学过的概念——闭包.何为闭包?从表面理解即封闭的包,与作用域有关.所以,说闭包以前先说说作用域. 作用域(scope) 通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突. 全局作用域(Global Scope) 在代码中任何地方都能访问到的对象拥

PHP和JS中变量作用域

一,PHP中变量作用域 对于大多数PHP的变量只有一个作用域.在用户自定义函数里采用局部变量作用域.所有的函数内使用的变量被设置为局部变量.例如: <?php $a=1; function test() { echo $a; } test(); ?> 这段程序不会输出任何的东西因为echo语句要输出局部变量 $a ,而函数内的 $a 从未被赋过值.你可能注意到和C语言有点小小的区别,C中全局变量可以直接在函数内引用,除非它被一个局部变量所覆盖.因为这使得人们可能不注意修改了全局变量的值.在PH

js中的作用域链

js中的执行环境: 所谓执行环境(有时也称环境)它是JavaScript中最为重要的一个概念.执行环境定义了变量或函数有权访问的其他数据 ,决定了它们各自的行为.而每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中. js中的作用域链: 每个函数都有自己的执行环境,当代码在执行环境中执行时,就会创建变量对象的作用域链.作用域链保证了对执行环境有权访问所有变量和函数的有序访问.作用域链的前端,始终都是当前执行的代码所在的环境的变量对象,如果环境是一个函数,那么它的