为什么要模块化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 #### 知识点一:面向对象编程在JavaScript中的应用 - **定义**:面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它将程序设计围绕“对象”进行组织。在...
在标题“Object-oriented-javascript”和描述“关于javascipt的一本很不错的书,主要是从初级开始的,面向对象的书。”中,我们可以提取出关于面向对象JavaScript编程的知识点。这本书由Stoyan Stefanov所著,出版于...
JavaScript,作为互联网上最广泛使用的脚本语言,其面向对象特性是开发者必须掌握的核心技能之一。这本书籍为读者提供了全面且深入的理解,帮助他们更好地利用这些特性来构建高效、可维护的代码。 首先,我们要理解...
### 面向对象的 JavaScript (Object-Oriented JavaScript) #### 概述 面向对象的 JavaScript(简称 OOJS)是一种编程范式,它利用 JavaScript 的特性来实现面向对象的编程方式。面向对象编程(OOP)是现代软件工程...
面向对象编程(Object-Oriented Programming,简称OOP)是一种广泛应用于软件开发的方法论,它通过将数据和处理这些数据的方法组织在一起,形成“对象”,从而实现对复杂系统的抽象和管理。《面向对象的JavaScript...
### Object_Oriented_Javascript #### 重要概念与知识点概览 **JavaScript**作为一种流行的编程语言,在Web开发中占据着核心地位。随着技术的发展,它不仅限于浏览器环境中的脚本编写,还扩展到了服务器端(如Node...
闭包可能导致的问题之一是在循环中使用循环变量。例如,在`my_func`中,所有内部函数`func`都引用同一个`i`,因为它们在返回前都被创建。所以,无论何时调用这些函数,它们都会返回循环结束时的`i`值,而不是循环...
面向对象编程(Object-Oriented Programming,简称OOP)是...在"Object-Oriented-JavaScript-master"这个资源中,很可能是包含了一系列针对这些概念的练习和示例,通过实践来加深理解和掌握面向对象的JavaScript编程。
面向对象编程(Object-Oriented Programming,简称OOP)是软件开发中的一种重要思想,它在JavaScript中的应用为开发者提供了更高效、结构化的代码组织方式。JavaScript作为一种动态类型的脚本语言,起初并不是设计来...
**JavaScript面向对象编程详解** JavaScript,作为Web开发中的主要脚本语言...通过阅读《Object.Oriented.JavaScript.2008》这本书,开发者能够深入理解JavaScript的面向对象编程机制,为实际项目开发打下坚实的基础。
原书名: Object-Oriented JavaScript: Create scalable, reusable high-quality JavaScript applications and libraries. JavaScript作为一门浏览器语言的核心思想; 面向对象编程的基础知识及其在JavaScript中...
在本项目中,“object-oriented-[removed]Udacity类的项目”主要关注的是面向对象编程的概念,特别是使用JavaScript语言来实现。面向对象编程(Object-Oriented Programming, OOP)是一种编程范式,它基于“对象”的...
在JavaScript中,可以使用闭包来实现单例,避免多次实例化同一对象,节省资源,如缓存服务或管理全局状态。 2. **工厂模式**:用于创建对象,但不暴露创建逻辑,而是通过接口提供对象。这样可以隐藏具体类的实现...
因此,面向对象的JavaScript(Object-Oriented JavaScript,简称OOJS)作为一种更高级的编程范式,为开发者提供了更加灵活、高效且易于维护的解决方案。 #### 面向对象的概念与优势 面向对象编程(Object-Oriented...
### JavaScript基础知识点概述 #### 一、函数表达式与函数声明 JavaScript中定义函数主要分为函数声明和函数表达式两种形式。函数声明一般会使用`function`关键字直接声明一个函数,如`function fn() {}`,而函数...
在JavaScript编程语言中,"对象字面量"(Object Literal)是一种创建对象的简洁方式,类似于其他编程语言中的字典或映射结构。这个压缩包文件`object-literal-gc.rar_objects`及其包含的文件,如`7.3-10.js`、`7.3-...
深入理解javascript原型和闭包(01)——一切都是对象 深入理解javascript原型和闭包(02)——函数和对象的关系
总结来说,Swift的闭包构造函数是实现灵活和可扩展代码的关键特性之一。通过将闭包作为构造函数的一部分,我们可以创建可以根据具体需求动态调整行为的类和结构体,从而提高代码的复用性和可维护性。