`

使用闭包构造模块(基础篇)——Object-Oriented Javascript之三

 
阅读更多
为什么要模块化js?
如果你问我这个问题,我会这样回答:
如果你从未被全局变量坑过,请绕道;
如果你从未遭受过维护大段大段的代码的噩梦,那我祝你新春愉快,早点平安回家;
如果你从未纠结过如何优雅地组织代码,那么请回头是岸,不要再往下看。
 
模块的基本思想是,将复杂零散的东西,构造成一个简单、独立的整体。台式电脑,笔记本电脑,IPAD,都是整合电子计算元件的经典“模块”,你无须理会他们内部使用了多少个D触发器,使用了多少个二极管,你只需去享受鼠标键盘或者触屏带给你的舒适体验。台式电脑,笔记本电脑,IPAD,尽管都是同样电子产品的模块,但是却能一个比一个简单,一个比一个更受人喜爱。模块化是想象力和创造力的活,只要仍有追求,总能创造出更优美的模块(一不小心,这个观点貌似拔得太高了)。本文介绍了笔者最喜爱的模块化套路,希望能得到读者的批评指点,也希望能够激发出更多人模块化的灵感。
 
下面转入正题——使用闭包构造模块。 
 目录:
理解:三种作用域
理解:闭包=作用域私有性+作用域持久性
实践:使用闭包构造一个简单模块,一个单例工厂
 

理解:三种作用域

javascript的作用域有三种,一种是global, 一种是local,一种是closure。当你使用chrome debug的时候,就能清晰看到,各个作用域下分别可以看到什么变量。第一种就不多解释了,在浏览器上,global作用域其实就是window。而第二种和第三种,个人认为都是函数作用域。closure作用域指的是,不是在当前执行的函数的函数作用域找到了变量,而是从父函数作用域中找到了变量,即使父函数已经执行完毕。
<!DOCTYPE HTML>
<HTML>
 <HEAD>
  <TITLE>3种作用域</TITLE>
 </HEAD>

 <BODY>
  <script>
	var gLanuageName = "javascript";//global作用域
	function func(){
		var a = 2;
		if(2 === a){
			var b = 3;
		}
		alert(b);//function作用域找到了b

		var funcDouble = function(){
			return 2 * a;//closure作用域找到了a
		};
		
		(function(){
			alert(gLanuageName);//global作用域找到了gLanuageName
		})();

		return funcDouble;
	}
	var funcDouble = func();
	alert(funcDouble());//funcDouble是func的子函数,func执行完之后,子函数一样可以访问a变量。
	alert(gLanuageName);//global作用域找到gLanuageName

  </script>
 </BODY>
</HTML>
 

理解函数作用域

阅读下面的程序,你认为结果是什么? undefined? 3?
<script>
	function func(){
		var a = 2;
		if(2 === a){
			var b = 3;
		}
		alert(b);
	}
	func();
  </script>
 
看上面的代码,如果把javascript的作用域理解为java的作用域的话,那么b是undefined。但是,运行程序的结果是,b==3。为什么b是3,而不是undefined?因为js的作用域是函数作用域,for,if,swtich关键字后面的{}不是一个作用域,function()后面{}才是一个作用域。程序中var b=3和alert(b)在同一个函数作用域中,所以alert(b)是3。
 

javascript的作用域是链式作用域

如果在当前作用域找不到某个变量,那么会到父作用域中查找该变量,直到最外层作用域位置。
<script>
	var gLanuageName = "javascript";//global作用域
	function func(){
		var a = 2;
		if(2 === a){
			var b = 3;
		}
		alert(b);//function作用域找到了b

		var funcDouble = function(){
			return 2 * a;//closure作用域找到了a
		};
		
		(function(){
			alert(gLanuageName);//global作用域找到了gLanuageName
		})();

		return funcDouble;
	}
	var funcDouble = func();
	alert(funcDouble());
	alert(gLanuageName);//global作用域找到gLanuageName

</script>
 
下面分析上面代码执行过程中,链式查找的过程。
1) alert(b);
首先在当前函数作用域查找变量b,结果找到,返回;
2) return 2 * a;
首先在当前函数作用域查找变量b,没有找到;然后到父函数作用域中查找,结果找到,返回;
3) (function(){alert(gLanuageName);})();
首先在当前匿名函数作用域中查找变量gLanuageName,没有找打;
然后到上一层的父亲函数作用域中查找,没有找到;
然后到上一层的global作用域中查找,结果找到,返回。
 
显然,向上查找的层次越多,耗的时间越长。例如查找gLanuageName就查找了3个作用域才最终找到了变量。为了提升效率,一般都会将当前作用域常用的变量,放到当前作用域中。例如window,undefined等。就本例子而言,可以把gLanuageName放到func(){}作用域中,减少一次查找作用域的消耗。
 
我觉得js做得更好一点话,可以把作用域做成变量的形式,那样开发人员直接访问作用域,免除链式查找的消耗。例如global['gLanuageName'],closure.funcName.['a']。在浏览器,global就是相当于window。
 

理解: 闭包=作用域私有性+作用域持久性

什么是闭包?每当有人这么问我,我就心里发虚。这是一个神奇的概念,失败的概念。
我的理解是,闭包=作用域的持久性+作用域的私有性。
 
作用域的持久性
当函数执行完毕后,如果作用域中的对象(变量/函数)仍然被持有,那么作用域是不会伴随函数的执行完毕而消失。闭包与其说是闭包,不如说是作用域的持久性。
 
作用域的私有性
私有性很好理解,其实是由JS的作用域是链式查找决定的。链式查找的方式,决定了并不是每个函数都能访问到所有的作用域下的变量。如下面的程序所示,scopeB、scopeC可以链式查找到ScopeA,可以访问a变量;但是scopeB无法链式查找到ScopeC,也就是说在scopeB函数内部无法访问c变量,ScopeB下面的变量对ScopeC不可见,是“私有”的。
<script>
    function scopeA(){
          var a = 1;
          function scopeB(){
                var b =2;
                var _a = a;
                var getA = function(){
                      return _a;
                }
          }
          function scopeC(){
                 var c = 3;
            }
    }
</script>
 
闭包=作用域的持久性 + 作用域的私有性 => 模块封装
持久性允许我们持久地保存某些变量,私有私有性允许我们只把变量暴漏给某些函数,二者一结合,构成了面向对象“封装”的基石。闭包就是实现模块封装的利器!

实践: 使用闭包构造一个模块,一个单例工厂

下面给出一个Counter的例子。该Counter初始值为0,可以通过add实现+1,通过sub实现-1,通过get获取当前值,内部的状态数据 i,通过闭包完美地封装了起来,外部程序除了能够操作sub,add,get之外,无法操控i本身,确保了模块的安全和稳定。
Counter程序
<!DOCTYPE HTML>
<HTML>
 <HEAD>
  <TITLE>Counter</TITLE>
 </HEAD>

 <BODY>
<script>
		var oCounter = (function(){
			var i = 0;
			var get = function(){
				return i;
			}
			var add = function(){
				i++;
				return i;
			}
			var sub = function(){
				if(i-1<0){
					alert("counter is zero. Cannot perform subtraction.");
					return i;
				}
				i--;
				return i;
			}
			var o = {
				get : get,
				add : add,
				sub : sub
      			};
			return o;
		})();
		oCounter.add();
		alert(oCounter.get());
		oCounter.sub();
		alert(oCounter.get());
		oCounter.sub();
		alert(oCounter.get());
</script>
 </BODY>
</HTML>
  
使用闭包定义模块有个简单的套路:
1.  定义一个函数,作为”私有“作用域
2.  在函数中,使用var定义一些”私有“的变量/函数
3.  函数返回一个引用这些”私有“变量/函数的object或者function。
按照这个套路,可以满足大部分的模块化需求。
 
Counter程序除了闭包,值得一提的是,函数返回的对象o。对象o清晰地描述了对外公开的接口,并且隐藏了具体的实现,也意味着可以轻易地替换实现。如果后面需求发生了变化,sub可以将i减少到负数,那么我们可以再添加一个sub2方法实现,修改对象o为
var o = {
    get : get,
    add : add,
    sub : sub2
}; 
这样,使用oCounter的client代码无需改变,是不是有点JAVA面向接口编程的味道呢?o其实充当了一个接口的具体实现。设计一个模块的时候,最好能设计好“接口对象”,以后即使模块实现改变,也无需改变使用模块的client代码。这里只是提供了一种”面向接口“的简单思路,并不是固定的。
 
如果你觉得上面的代码不够面向接口,不够面向对象,请看以下代码,实现了单例工厂模式,根据不同参数返回了不同的Counter实现。
 
 
<!DOCTYPE HTML>
<HTML>
 <HEAD>
  <TITLE> Counter 单例工厂 </TITLE>
 </HEAD>

 <BODY>
<script>
		//单例工厂
		var oCounterFactory = (function(){
			var map = {};
			var oCounter = null;
			
			//获取单例,通过参数返回为不同的Counter实现,couterType为"Counter1"或者"Counter2"
			function getSingletonCounter(couterName){
        			//延迟加载
				if(null == map[couterName]){
          				//这里有点类似反射
					map[couterName] = eval('create' + couterName + '()');
				}
				return map[couterName];
			}

			//具体实现1
			function createCounter1(){
				alert("create oCounter1");
				var i = 0;
				var get = function(){
					return i;
				}
				var add = function(){
					i++;
					return i;
				}
				var sub = function(){
					if(i-1<0){
						alert("counter is zero. Cannot perform subtraction.");
						return i;
					}
					i--;
					return i;
				}
				
				var o = {
					get : get,
					add : add,
					sub : sub
				};

				return o;
			}
			//具体实现2
			function createCounter2(){
				alert("create oCounter2");
				var i = 0;
				var get = function(){
					return i;
				}
				var add = function(){
					i++;
					return i;
				}
				var sub2 = function(){
					i--;
					return i;
				}
				
				var o = {
					get : get,
					add : add,
					sub : sub2
				};

				return o;
			}
		
			var oCounterFactoryRtn = {
				getSingletonCounter : getSingletonCounter
			};
			return oCounterFactoryRtn;
		 })();
				
			
		alert("oCounter1 add : " + oCounterFactory.getSingletonCounter("Counter1").add());
		alert("oCounter1 add : " + oCounterFactory.getSingletonCounter("Counter1").add());
		alert("oCounter1 add : " + oCounterFactory.getSingletonCounter("Counter1").add());
		alert("oCounter2 add : " + oCounterFactory.getSingletonCounter("Counter2").add());
		alert("oCounter2 add : " + oCounterFactory.getSingletonCounter("Counter2").add());
		alert("oCounter2 add : " + oCounterFactory.getSingletonCounter("Counter2").add());
</script>
 </BODY>
</HTML>
  不知道看官有没有体会到闭包的强大。JS虽然没有class关键字,但是依然可以很好地封装模块,甚至应用设计模式。
 

>>下一篇 使用闭包构造模块(优化篇)——Object-Oriented Javascript之四

2
0
分享到:
评论
8 楼 470275283 2015-03-17  
lazy_ 写道
Doc_xu 写道
   <script> 
        function func(){ 
            var a = 2; 
            if(2 === a){ 
                var b = 3; 
            } 
            alert(b); 
        } 
        func(); 
      </script>   这段的结果经测试为3,博主确定没有弄错?

谢谢指正。是我的错误。写文章的时候搞错了,现在改过来了。


放在一个内部方法里面就表达出LZ的意思了吧
7 楼 javabang 2013-02-19  
学习了一下,受益匪浅,希望楼主多写点。
6 楼 jiangwenxian 2013-02-19  
第一次接触这一块,学习了。
5 楼 lazy_ 2013-02-19  
Doc_xu 写道
   <script> 
        function func(){ 
            var a = 2; 
            if(2 === a){ 
                var b = 3; 
            } 
            alert(b); 
        } 
        func(); 
      </script>   这段的结果经测试为3,博主确定没有弄错?

谢谢指正。是我的错误。写文章的时候搞错了,现在改过来了。
4 楼 Doc_xu 2013-02-19  
   <script> 
        function func(){ 
            var a = 2; 
            if(2 === a){ 
                var b = 3; 
            } 
            alert(b); 
        } 
        func(); 
      </script>   这段的结果经测试为3,博主确定没有弄错?
3 楼 tcxq42bb 2013-02-06  
挺好的,感谢楼主分享,期待你的提高篇
2 楼 lazy_ 2013-02-05  
shizhangliao 写道
讲得很好,学习了希望有LZ出更多关于JS的文章。

谢谢您的支持,这个系列肯定会超过10篇文章的。人好少啊,大概都回家了吧。之前发一篇博客都能有500的访问量。,现在手上有一章闭包的提高篇,但是人太少了,都不知道发不发好,好想有人能指出自己的不足之处,或者提出更好的实践思路。
1 楼 shizhangliao 2013-02-04  
讲得很好,学习了希望有LZ出更多关于JS的文章。

相关推荐

    Object-Oriented JavaScript

    ### Object-Oriented JavaScript #### 知识点一:面向对象编程在JavaScript中的应用 - **定义**:面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它将程序设计围绕“对象”进行组织。在...

    Object-oriented-javascript

    在标题“Object-oriented-javascript”和描述“关于javascipt的一本很不错的书,主要是从初级开始的,面向对象的书。”中,我们可以提取出关于面向对象JavaScript编程的知识点。这本书由Stoyan Stefanov所著,出版于...

    No.Starch.The.Principles.of.Object-Oriented.JavaScript

    JavaScript,作为互联网上最广泛使用的脚本语言,其面向对象特性是开发者必须掌握的核心技能之一。这本书籍为读者提供了全面且深入的理解,帮助他们更好地利用这些特性来构建高效、可维护的代码。 首先,我们要理解...

    object-oriented-javascript

    ### 面向对象的 JavaScript (Object-Oriented JavaScript) #### 概述 面向对象的 JavaScript(简称 OOJS)是一种编程范式,它利用 JavaScript 的特性来实现面向对象的编程方式。面向对象编程(OOP)是现代软件工程...

    The Principles of Object Oriented.JavaScript

    面向对象编程(Object-Oriented Programming,简称OOP)是一种广泛应用于软件开发的方法论,它通过将数据和处理这些数据的方法组织在一起,形成“对象”,从而实现对复杂系统的抽象和管理。《面向对象的JavaScript...

    Object_Oriented_Javascript

    ### Object_Oriented_Javascript #### 重要概念与知识点概览 **JavaScript**作为一种流行的编程语言,在Web开发中占据着核心地位。随着技术的发展,它不仅限于浏览器环境中的脚本编写,还扩展到了服务器端(如Node...

    python闭包深入(csdn)————程序.pdf

    闭包可能导致的问题之一是在循环中使用循环变量。例如,在`my_func`中,所有内部函数`func`都引用同一个`i`,因为它们在返回前都被创建。所以,无论何时调用这些函数,它们都会返回循环结束时的`i`值,而不是循环...

    Object-Oriented-[removed]面向对象JavaScript的一系列练习

    面向对象编程(Object-Oriented Programming,简称OOP)是...在"Object-Oriented-JavaScript-master"这个资源中,很可能是包含了一系列针对这些概念的练习和示例,通过实践来加深理解和掌握面向对象的JavaScript编程。

    Object-Oriented-JS-Exercises

    面向对象编程(Object-Oriented Programming,简称OOP)是软件开发中的一种重要思想,它在JavaScript中的应用为开发者提供了更高效、结构化的代码组织方式。JavaScript作为一种动态类型的脚本语言,起初并不是设计来...

    Object.Oriented.JavaScript.2008

    **JavaScript面向对象编程详解** JavaScript,作为Web开发中的主要脚本语言...通过阅读《Object.Oriented.JavaScript.2008》这本书,开发者能够深入理解JavaScript的面向对象编程机制,为实际项目开发打下坚实的基础。

    JavaScript面向对象编程指南

    原书名: Object-Oriented JavaScript: Create scalable, reusable high-quality JavaScript applications and libraries. JavaScript作为一门浏览器语言的核心思想;  面向对象编程的基础知识及其在JavaScript中...

    object-oriented-[removed]Udacity类的项目

    在本项目中,“object-oriented-[removed]Udacity类的项目”主要关注的是面向对象编程的概念,特别是使用JavaScript语言来实现。面向对象编程(Object-Oriented Programming, OOP)是一种编程范式,它基于“对象”的...

    Object-Oriented-Design-Patterns:简要了解一些简单的设计模式

    在JavaScript中,可以使用闭包来实现单例,避免多次实例化同一对象,节省资源,如缓存服务或管理全局状态。 2. **工厂模式**:用于创建对象,但不暴露创建逻辑,而是通过接口提供对象。这样可以隐藏具体类的实现...

    Oriented.JavaScript.Create.scalable.reusable.high-quality.JavaScript

    因此,面向对象的JavaScript(Object-Oriented JavaScript,简称OOJS)作为一种更高级的编程范式,为开发者提供了更加灵活、高效且易于维护的解决方案。 #### 面向对象的概念与优势 面向对象编程(Object-Oriented...

    JavaScript基础篇(6)之函数表达式闭包

    ### JavaScript基础知识点概述 #### 一、函数表达式与函数声明 JavaScript中定义函数主要分为函数声明和函数表达式两种形式。函数声明一般会使用`function`关键字直接声明一个函数,如`function fn() {}`,而函数...

    object-literal-gc.rar_objects

    在JavaScript编程语言中,"对象字面量"(Object Literal)是一种创建对象的简洁方式,类似于其他编程语言中的字典或映射结构。这个压缩包文件`object-literal-gc.rar_objects`及其包含的文件,如`7.3-10.js`、`7.3-...

    深入理解javascript原型和闭包

    深入理解javascript原型和闭包(01)——一切都是对象 深入理解javascript原型和闭包(02)——函数和对象的关系

    (Swift)闭包构造函数

    总结来说,Swift的闭包构造函数是实现灵活和可扩展代码的关键特性之一。通过将闭包作为构造函数的一部分,我们可以创建可以根据具体需求动态调整行为的类和结构体,从而提高代码的复用性和可维护性。

Global site tag (gtag.js) - Google Analytics