`

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

 
阅读更多

    上一篇博客(使用闭包构造模块(基础篇)——Object-Oriented Javascript之三)介绍了闭包构造模块的基础知识,这一篇着重介绍“优化”。这里“优化”指的是性能、可维护性。你可以不依照这篇文章推荐的实践方法,也可以写出具备相当功能的程序,但是程序可能在性能、可维护性上有缺陷。希望本文能够带给读者一些小小的优化技巧,如有发现错误之处或有更好建议,盼能回复,不尽感谢。

 

目录

利用闭包缓存数据,提升性能

循环内利用匿名函数闭包缓存变化的数据

通过“先引用,再使用”,弱化模块间的依赖

 

 

利用闭包缓存数据,提升性能

 

为了说明这个观点,我使用下面的一个程序来说明。程序的功能是为所有string对象,增加一个将html转义字符替换为普通字符的方法,例如将&lt;替换为<。
下面展示了2个不同的实现decodeHtml 和decodeHtml2,你能看出区别吗?请看程序
<!DOCTYPE HTML>
<HTML>
 <HEAD>
  <TITLE> New Document </TITLE>
 </HEAD>

 <BODY>
  <script>
	String.prototype.decodeHtml = function(){
		var decodeMapping = {
			quot : '"',
			lt: '<',
			gt: '>'
		};
		//this是具体的string字符串对象
		return this.replace(/&(.+?);/g,function($0,$1){
			if(typeof decodeMapping[$1] === 'string'){
				return decodeMapping[$1];
			}else{
				return $0;
			}
		});
	}

	String.prototype.decodeHtml2 = (function(){
		var decodeMapping = {
			quot : '"',
			lt: '<',
			gt: '>'
		};
		var func = function(){
			//this是具体的string字符串对象
			return this.replace(/&(.+?);/g,function($0,$1){
						if(typeof decodeMapping[$1] === 'string'){
							return decodeMapping[$1];
						}else{
							return $0;
						}
					});
		}
		return func;
	})();
	var s = "&lt;html&gt;&lt;/html&gt;";
	alert(s.decodeHtml());//<html></html>
	alert(s.decodeHtml2());//<html></html>
  </script>
 </BODY>
</HTML>
 
decodeHtml 和 decodeHtml2 的区别在于,decodeHtml 每次被调用都要执行一次 var decodeMapping = {quot : '"',lt: '<',gt: '>'};而decodeHtml2 每次调用都使用闭包“缓存”起来的decodeMapping ,每次被调用都不需要执行var decodeMapping = {quot : '"',lt: '<',gt: '>'};,因此decodeHtml2 的效率相对前者更高。
 

循环内利用匿名函数闭包缓存变化的数据

先出个小题目,如果没做过这题目的人,很可能会做错(包括我,我当时也搞错了)。
题目:有多个<a/>按钮,对每个a按钮实现click事件,弹出当前<a/>在所有<a/>中出现的顺序,例如第1个<a/>点击后弹出1,第二个<a/>弹出2,依次类推。
第一次我写的程序是这样的:
 
<!DOCTYPE HTML>
<HTML>
 <HEAD>
  <TITLE>循环中使用闭包的陷阱</TITLE>
 </HEAD>

 <BODY>
  <a href="###">1</a>
  <a href="###">2</a>
  <a href="###">3</a>
  <script>
	var doms = document.getElementsByTagName("A");
	for(var i=0;i<doms.length;i++){
			doms[i].onclick = function(){
				alert(i+1);
			}		
	}
  </script>
 </BODY>
</HTML>
 
运行一下,发现所有的a按钮点击后都提示4。为什么呢?
审查一下代码,当点击第一个a按钮的时候,触发回调函数,执行alert(i+1);,JS通过链式作用域找到i的值等于4,所以返回4。为什么JS通过链式作用域找到i的值等于4?因为for循环执行完后,外层作用域中的i就是等于4。解决这个问题思路也很简单,每个循环里面再创造一个闭包,缓存当时i的值。fix后的代码如下:
 
<!DOCTYPE HTML>
<HTML>
 <HEAD>
  <TITLE>fix_循环中使用闭包的陷阱</TITLE>
 </HEAD>

 <BODY>
  <a href="###">1</a>
  <a href="###">2</a>
  <a href="###">3</a>
  <script>
	var doms = document.getElementsByTagName("A");
	for(var i=0;i<doms.length;i++){
		//每个循环产生一个闭包,循环3次,产生3个闭包,每个闭包缓存不同的i值。
		(function(){
			//缓存当前时刻的i值
			var _i = i;
			doms[_i].onclick = function(){
				alert(_i+1);
			}
		})();
	}
  </script>
 </BODY>
</HTML>
 
在循环中使用闭包的时候需要多留个心眼,因为这个实在是太counterintuitive。

通过“先引用,再使用”,弱化模块间的依赖

一个模块的理想状态是既不影响外部,也不会被外部影响的。
但是,实际情况总不可避免地总会依赖一些其他模块的API,修改某个外部API,可以同时对多个模块同时起作用,这是好的,将重复的逻辑放到一个地方。但是缺点是,如果外部API出错了,那么多个模块都受到影响。
 
如果一个模块完全是独立的,不使用任何外部API,那是最完美的状况。但是如果每个模块都是完全独立,那么当模块的数量慢慢多起来的时候,重复代码的问题必然会出现。
 
模块独立和没有重复代码,是一对矛盾。我使用以下策略去弱化模块间的依赖,仅供各位读者参考:
1 对于jquery.js, json2.js这些比较常用的第三方API可以直接在模块中使用
2 对于自己项目编写的xxxComm.js,xxxUtil.js,模块内部并不是直接使用这些外部API,而是先引入,再使用
下面举例子说明“新引入,再使用”。
一个comm组件包含了加减乘除的方法,而一个 MutiFuncitonCalculator组件使用了comm组件中加减乘除来进行多种计算。A同学和B同学用不同的方式实现了MutiFuncitonCalculator组件。
oComm = (function(){
    return {
	    fnAdd : function(a,b){return parseFloat(a)+parseFloat(b)},
	    fnSub : funciton(a,b){...},
	    fnMul : function(a,b){...},
	    fnDiv  : function(a,b){...}
	}
}){};
//B同学写的程序
oMutiFuncitonCalculator2 = (function(){
    var getTax(wage){
         //使用oComm.fnAdd,oComm.fnSub,oComm.fnMul,oComm.fnDiv完成计算
    }
    var getXXX(){
        //使用oComm.fnAdd,oComm.fnSub,oComm.fnMul,oComm.fnDiv完成计算
    }
    //下面好多好多个使用oComm.fnAdd,oComm.fnSub,oComm.fnMul,oComm.fnDiv完成计算的方法
    ....
    return {getTax: getTax,
            getXXX: getXXX,
            ....};
})();
//A同学写的程序
oMutiFuncitonCalculator = (function(){
    var fnAdd = oComm.fnAdd,
        fnSub = oComm.fnSub,
        fnMul = oComm.fnMul,
        fnDiv = oComm.fnDiv
    
    var getTax(wage){
         //使用fnAdd,fnSub,fnMul,fnDiv完成计算
    }
    var getXXX(){
        //使用fnAdd,fnSub,fnMul,fnDiv完成计算
    }
    //下面好多好多个使用 fnAdd,fnSub,fnMul,fnDiv 完成计算的方法
    ....
    return {getTax: getTax,
            getXXX: getXXX,
            ....};
})();
 
后面问题来了,A同学和B同学都发现oComm的算术方法并不准确,导致多功能计算器的结果不准确,第一时间,他们马上google到准确的加减乘除的方法,然后想直接修改oComm的方法,但是,他们不敢确定这么修改是否影响到其他使用comm的模块,甚至没有comm的修改权,怎么办?B同学可着急了,程序要改好多地方!而A同学却可以很淡定,因为A同学只需要修改oMultiFunctionCalculator模块刚开始定义的fnAdd,fnSub,fnMul,fnDiv方法就可以了,如果后面oComm修复了这个问题,再改回去也是少的工作量。
 
而悲催的B同学通常会遭遇以下的挫折:
1. B同学在oMutiFuncitonCalculator2 模块内部,再定义一个oComm对象,把精确的算术方法写到内部的oComm对象,这样修改量就很少了,但oMutiFuncitonCalculator2 中使用oComm对象的其他方法就报错了,需要将模块内所有使用到oComm方法都需要放到内部的oComm中去。
2. B同学使用IDE的replaceAll,把每个oComm.fnAdd都替换成自己实现的add,但是程序还是报错了。因为替换全部不小心替换了一些不该替换的地方。
3. B同学心灰意冷地等待oComm修复,或者只好逐个修改代码中使用fnAdd,fnSub等等使用外部算术方法的地方
 
上面描述的是我虚构的一个简单的例子,大家可能觉得这不太可能发生,实际上,这是我实实在在的教训,由于在这里不好表述当时当前情形,所以使用了类似的例子来说明。不要以为外部API很稳定,很正确,永远不会改变,那样的思想只会让模块变得脆弱。例子中A同学的聪明之处在于,他让模块可以选择外部API,但是不受制于外部API,可以随时轻易地替换,换句话说,模块具有了自主控制权。相对地,B同学的模块,完全受制于外部API,不能简单地作出改变。
 
再举个例子,某某决定在新的项目中重构JS API,尽可能地将现有的全局函数分门别类移动到各个命名空间下。出发点是很好的,但是由于大部分模块都是直接使用全局函数,导致替换API的工作量很大,原以为很简单的任务,结果调试了接近一周时间才能发布出一个版本。然后,参与重构的C同学发现,只需要在原来模块的基础上,先引入外部API再使用,那样替换API的工作就会少很多。例如
原来存在全局API
function fnGlobalFunc(){....}
后来移动到oGlobal命名空间下
oGlobal = {
    fnGlobalFunc : function(){...}
}
那么只需要在原来的模块的基础上加上“引入”的代码,就简单地完成了替换
(function(){
    var fnGlobalFunc =oGlobal. fnGlobalFunc;
    //使用fnGlobalFunc完成功能,原来代码无需改变
    ....
})();
 
到这里,相信大家能体会到先引入再使用的简单做法,对模块的可维护性带来的巨大得益。先引入再使用还有一个好处,就是减少链式作用域查找和对象自身属性的多次查找。例如B同学代码中,使用oComm.fnAdd,首先要链式查找到上一层作用域,然后又在oComm里查找到fnAdd函数。而A同学的代码中的fnAdd方法没有这些效率问题。
 

>>下一篇 使用闭包构造模块(提高篇_实现jQuery)——Object-Oriented Javascript之五

2
0
分享到:
评论
1 楼 jiangwenxian 2013-02-19  
第一次接触,学习了。lz大牛!!!

相关推荐

    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...

    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

    本资料《Object.Oriented.JavaScript.2008》旨在深入探讨JavaScript的面向对象编程概念,帮助开发者更好地理解和利用这一强大的编程模式。 **一、面向对象编程基础** 1. **类与对象**:在JavaScript中,虽然没有像...

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

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

    JavaScript面向对象编程指南

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

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

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

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

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

    javascript 经典面向对象设计

    《Object-Oriented JavaScript》这本书不仅深入介绍了面向对象编程的基础理论,还提供了大量的实际代码示例和最佳实践指导。对于希望提高JavaScript开发技能、编写出高质量、可扩展和可重用代码的开发者来说,这本书...

    JavaScript征途

    JavaScript,作为全球最广泛使用的编程语言之一,是前端开发的核心技术,也是许多后端和全栈框架的基础。在“JavaScript征途”这个另类的教程中,我们将深入探讨JavaScript的两个核心概念:面向对象和闭包,这两个...

    Javascript面向对象编程.

    面向对象编程(Object-Oriented Programming,OOP)是编程的一种重要范式,JavaScript也完全支持这一特性,尽管它并非一种传统的静态类型语言。这篇博客文章可能详细讨论了如何在JavaScript中实现面向对象编程。 在...

    JavaScript面向对象编程案例

    面向对象编程(Object-Oriented Programming,OOP)是一种强大的编程范式,它基于“对象”的概念,允许我们通过封装数据和方法来组织代码。在JavaScript中,面向对象编程并不是原生支持的,但它可以通过模拟类和对象...

    Professional.JavaScript.for.Web.Developers 3rd Ed Nicholas Zakas

    - **类与构造函数**:了解如何使用构造函数创建对象实例。 #### 六、面向对象编程(Chapter 6:Object-Oriented Programming) 介绍了JavaScript中的面向对象编程思想及实现方法。 - **封装**:通过对象来组织...

Global site tag (gtag.js) - Google Analytics