Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Analysis of how to reconstruct JavaScript

2025-03-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

本篇文章为大家展示了如何进行JavaScript重构的分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

通常我们的团队中,开发人员在Java语言层面具备相当的技术素养,经验丰富,而且有许多成熟的、合理的规约,类型繁多的代码隐患检查工具,甚至在团队间还有计划内的评审和飞检。但是前端的代码不似后台,就像一个没人疼的孩子,不仅仅容易被低估、被轻视,导致质量低劣、可维护性差,技能上,更缺少优秀的前端开发人员。

JavaScript是前台代码中重要组成部分,随着版本的延续,产品越做越大,JavaScript层面的重构,需要在整个过程中逐步强化起来。

当代码量达到一定程度,JavaScript最好能够与页面模块组件(例如自定义的FreeMarker标签)一起被模块化。

模块化带来的最大好处就是独立性和可维护性,不用在海量的js中定位问题位置,简单了,也就更容易被理解和接受,更容易被定制。

模块之间的依赖关系最好能够保持简单,例如有一个common.js,成为最通用的函数型代码,不包含或者包含统一管理的全局变量,要求其可以独立发布,其他组件js可以轻松地依赖于它。举个例子,我们经常需要对字符串实现一个trim方法,可是js本身是不具备的,那么就可以在这个common.js中扩展string的prototype来实现,这对外部的使用者是透明的。

模块划分和命名空间

使用命名空间是保持js互不干扰的一个好办法,js讲究起面向对象,就必须遵循封装、继承和多态的原则。

参照Java import的用法,我希望命名空间能带来这样的效果,看一个最简单的实例吧:

我有一个模块play,其中包含了一个方法webOnlinePlay,那么在没有import这个模块的时候,我希望是js的执行是错误的:

webOnlinePlay(); //Error! 无法找到方法

但是如果我引入了这个模块:

import("play"); webOnlinePlay(); //正确,能够找到方法

其实实现这样的效果也很简单,因为默认调用一个方法webOnlinePlay()的实质是:window.webOnlinePlay(),对吗?

所以在import("play")的时候,内部实现机制如下:

var module = new playModule();

对于这个模块中的每一个方法,都导入到window对象上面,以直接使用:

window[methodName] = module[methodName];

其实这里并没有什么玄机,但是这种即需即取的思想却给前端重构带来了一个思路,一个封装带来的可维护性增强的思路,不是吗?

聪明的你也许还会提到一个问题:

如果我没有import这个play模块,这个页面都不需要,那我能否连这个play.js都不加载呢?

当然可以,请关注下一页---关于js的动态加载的部分。

JavaScript的动态加载

前一节留下了一个问题,如果JS分门别类也清晰了,那我现在需要在必要的情况下才加载某一模块的JS,这个怎么实现呢?

方法一,最简单也是最容易被接受的方法,通过后台代码来控制,还是少些复杂的JS吧,通过一个标签、一个分支判断,就可以做到,何乐而不为呢?

方法二,如果要使用纯JS来控制,那么看看这样如何:

$.ajax(){ url:"xxx/play.js"; …… success:function(res){ eval(res.responseText); } }

原理是很简单,不过有一个藏匿着的魔鬼:eval,js加载的生效就靠它了,那么执行的上下文就在它的里面,这就会带来一些潜在的问题,而且,调试也变得困难。

方法三,通过添加标签的方式来动态引入脚本:

原理相信大家也马上能领悟个大概了,需要的时候动态地往页面的里面写一对标签,让浏览器自己去取需要的js,这样的就解决了方法二里面的魔鬼eval的问题,是一个比较好的方法:

这里啰嗦一句,标签中的src--本质上不就是对src所表示的地址发送一个get请求吗?这虽然看起来有点歪门邪道,却恰恰是一个跨域问题的解决办法!

JavaScript的测试

进行JavaScript重构时,我希望引入易于使用的测试框架来保证重构的顺利进行,未来能持续通过测试代码对JavaScript逻辑的正确性做保障。

JsUnit (http://sourceforge.net/projects/jsunit/,http://www.jsunit.net/)

JsUnit是一个独立的JavaScript单元测试框架,和JUnit差不多,没有上手难度,包括传统的setUp和tearDown,提供的assert方法也和JUnit类似,多了assertNaN和assertUndefined等等JavaScript特有的方法。测试页面必须在里面引入jsUnitCore.js这个js文件。

测试套件的支持:提供了addTestPage和addTestSuite;

测试日志的支持:包括warn、info和debug三种日志级别,前端编码不似后台代码,正式代码中不宜使用过多log,再说log也只有FF下才支持,现在好了,在测试代码里尽情打吧。

千言万语不及一个例子:

//模块JS function testWithMainProcess() { assertEquals("Web play url", "##http://...##", webOnlinePlay()); }

项目的代码里到处是Ajax调用,要做单元测试,看来打桩是不可避免了。Mock类的工具有许多,比如适合JQuery的QMock:

var mockJquery = new Mock(); mockJquery .expects(1) .method('ajax') .withArguments({ url: 'http://xxx, success: Function, dataType: "jsonp" }) .callFunctionWith({ feed : { entry : "data response" }});

这个桩正是mock了一个假的ajax jason返回:[feed:[entry:"data response"]],看看,使用就和以前接触过的EasyMock差不多嘛。

对于JavaScript测试框架感兴趣的同学还可以了解一些其他的测试框架,例如JSpec。

单元测试代码建议就放在模块的包内:test.html,即便理想状况下,模块单独发布时,也是伴随着测试用例的可靠的前端代码。

从哪些JavaScript代码开始做?

1、函数式的代码。这样的代码保证独立性好,也不需要打什么桩,测试成本低,如果不明白函数式的代码的含义,请参见"函数式编程"。

2、复杂的逻辑。

是否尝试TDD?不建议在我们团队内部使用,前端TDD需要更高的技巧,对人的因素要求更高。如果有一天,后台Java代码的TDD做好了,那么换成JavaScript的代码,没有本质区别。

如果效果得当,为什么不能把JavaScript的UT集成到ICP-CI上作为持续集成的一部分呢?

JavaScript编码规则

没有规矩,不成方圆,JavaScript带来了灵活性,也带来了不受控的变量和访问,所以要用规则限制它。一支成熟的团队,还是一支新鲜的团队,规则应当是不一样的,我只是列出一些常见的或者有效的办法,来约束跳跃的开发人员,思维可以任意飞跃,代码却要持续受控。当然,任何规则都是建立在一定的认知基础之上的,面向对象JavaScript的基础是必备的,否则一切无从谈起。

变量和方法控制:

模块开发不允许存放独立的全局变量、全局方法,只允许把变量和方法放置到相应模块的"命名空间"中,对此的解释请参见此文。实在心痒了,那么使用匿名函数如何?

(function() { var value = 'xxx'; var func = function() {...}; })();

模块化需要严格控制住代码的区域性,这不仅仅是代码可维护性、可定制性的一方面,同时也让JavaScript引擎在属性和方法使用完毕后及时地回收掉。

不允许在模块代码中污染原生对象,例如

String.prototype.func = new function(){...};

如此的代码必须集中控制,例如统一放置在common.js中,严格保护起来。

数据存放约束:

普通变量、prototype变量和function变量分而治之,方法名一律大写开头,变量名还是遵从骆驼命名法如何:

function T(name){ T.prototype._instance_number++; this.name = name; this.showName=function(){ alert(this.name); } }; T.prototype = { _instance_number:0, getInstanceNum: function(){ return T.prototype._instance_number; } }; var t = new T("PortalONE"); t.showName(); new T("Again"); alert(t.getInstanceNum()); //打印:2

这里有意做了一件事情,T内部的属性和私有方法使用下划线开头,这样很好地实现了封装(上述代码中如果使用t.instanceNum,是无法访问到这个值的),如果这段代码都看不懂的话,赶紧温习一下JavaScript的面向对象吧 :)。JavaScript中提供了闭包和原型两种办法来实现继承和多态,关于重构中应用这一点,后续的章节我再啰嗦吧。

另外,优先使用JavaScript的原生对象和容器,比如Array,Ajax的数据类型统一切到JSON上来,尽量不要使用隐藏域;另外,通常是不允许随意扩展DOM对象的。

至于模块间的通信:模块间的通信意味着模块间的耦合性,是需要严格避免的;通信的途径通常使用方法级属性或者模块级的prototype变量。

DOM操纵规则:

在模块代码中,通常要求把对DOM的操纵独立到模块js中,应当避免在DOM模型上显示地写时间触发函数,例如:

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report