高维护性的JavaScript

1、养成良好的编码习惯,提高代码的可维护性

   1)避免定义全局变量和函数

        定义全局的变量和函数,会影响代码的可维护性。

        有很多手段可以解决因为定义全局变量而导致代码“污染”的问题:

             最简单的方法是把变量和方法封装在一个变量对象上,使其变成对象的属性。弊端:所有变量和函数的访问都需要通过主对象来访问。增加了代码的重复的和代码编写的复杂度。

var myCurrentAction={
    length:0,
    init:function(){...},
    action:function(){...}
}

            另一种改进的方案(最佳方案):把全局变量和函数包含在一个局部作用域中,然后再这个作用域中完成这些变量的定义以及变量使用的逻辑。例如,可以通过定义一个匿名函数实现:

(function(){
    var length=0;
    function init(){...}
    function action(){...}
}) ();

       在实际的业务中,模块之间会有交互,这时则可以使用return语句,返回需要公开的接口。经过如此调整,外部代码访问init()方法时,就可以通过myCurrentAction.init了。次方案既巧妙地做到了代码逻辑的封装,又公开了外部需要访问的接口。

var myCurrentAction=(function () {
    var length=0;
    function init(){...}
    function action(){...}
    return {
          init:init
    }
}) ();

      另一个避免定义全局变量的方式是:确保在定义变量时使用var关键字。如果没有var关键字,浏览器会自动把这一变量解析为全局变量。

  2)使用简化的编码方式

//复杂的编码方式

//对象创建
var person = new Object();
person.age=25;
person.name='dang';

//数组创建
var list=new Array();
list[0]=12;
list[1]=10;
list[3]=45;
//简化的编码方式

//对象创建
var person={age: 25, name: 'dang'};

//数组创建
var list=[12, 10, 45];

   3)使用比较运算符===而不是==

       ===(严格相等)和!==(严格不等)会比较两个基础类型值是否相等,或者两个复杂对象是否指向同一个地址,而==和!=则会先进行比较值的类型转换,再进行比较运算。

      ==和!=在比较时,转换规则和复杂:undefined和null与自己比较时结果为true,它们相互比较时结果也为true,但与其他类型比较时,结果为false。原始类型(数值、布尔和字符类型)进行比较时,会先转换为数值类型再进行比较;对象和原始类型比较时,会先将对象转换为原始类型,然后再比较。

null==undefined;     //true
0==null;       //false

false=='0'   //先转换为数值,true
false=='false'   //false
'\n 123 \t'==123    //true

var p={toString:function() {return '1'}};
p==1;    //选转换为原始类型,ture

    4)避免使用with语句

        在JavaScript中,with语句可用来快捷地访问对象的属性。with语句的格式如下:

with(Object){
    statement
}

       with语句的原理是:JavaScript解析和运行时,会给with语句单独建立一个作用域,而和with语句结合的对象中的属性则成为了此作用域的局部变量,因此可以直接访问。

a=Math.PI*r*r;
x=r*Math.cos(PI);
y=r*Math.sin(PI/2);
//使用with语句
with(Math){
    a=PI*r*r;
    x=r*cos(PI);
    y=r*sin(PI/2);
}

       从代码上来看,使用with语句的确简化了代码,但不幸的是,使用with语句可能也会带来不可思议的Bug以及兼容问题。

       首先,使用with语句,使得代码难以阅读,对于with语句内部的变量引用,只有在运行时才能知道变量属于哪个对象。

//不知道x是参数还是o对象的属性
function(x,o) {
    with(o) {
        print(x);
    }
}

         其次,with语句存在兼容问题。

        此外,with语句的设计方面也存在缺陷,在with语句内部修改和with语句结合的对象后,并不能同步到with内部,即不能保证对象数据的一致性。

var group={
    value:{
        node:1;
    }
}

with(group.value) {
    group.value= {
        node:2;
    }
    //显示错误:1
    console.log(node);
}

//显示正确:2
console.log(group.value.node);

    5)避免使用eval

        eval函数的用法很简单,它会接受一个字符串参数,把字符串内容作为代码执行,并返回执行的结果。

eval("x=1;y=2;x*y");

         从eval的功能上看,使用eval函数会让代码难以阅读,影响代码的可维护性。除此之外,eval的使用也存在安全问题,因为它会执行任意传入的代码,而传入的代码有可能是未知的或者来自不受控制的源,所以尽量避免使用eval。

         和eval函数类似的还有setTimeout和setInterval函数,这两个函数也可以接受字符串参数,当传入的参数为字符串时,它们会做类似eval函数的处理。此外,Function构造器也和eval函数的功能类似,也应该避免使用。

    6)不要编写检测浏览器的代码

       现在的浏览器更新的速度越来越快,并且浏览器之间的差异也越来越小。所以最佳的做法是不要编写检测浏览器的代码,取而代之的是检测浏览器是否支持某个特定功能。开发者可以借助目前流行的Modernizr框架来检测浏览器的特性支持。

       当然,也存在某些特定情况需要判断浏览器的版本,尤其是IE浏览器。这个时候,最好是把针对特定浏览器的代码逻辑放置在单独的文件中,方便后期的维护和移除。

<!--[if lt IE 9]>
    <script src="html5.js"></script>
<![endif]-->

2、使用更严格的编码格式

       严格模式主要是针对如下不合理的地方做了改进,包括:禁用with关键字、防止意外的全局变量、函数中this不再默认指向全局、防止函数参数重名、防止对象属性重名、更安全地使用eval等。具体可以参考Mozilia网站上的详细介绍:https://developer.mozilia.org/en-US/docs/Web/JavaScript/Reference/Function_and_function_scope/Strict_mode。

       启用严格模式很简单,只要在代码中添加如下代码即可:

"use strict"

        使用严格模式时需要遵循的几条最佳实践:

            1)不要在全局中启用严格模式:尽量把严格模式限定在函数作用域范围内。

function () {
    "use strict";
    //这个函数中的代码将会运行严格模式
}

function () {
    //这个函数中的代码不会运行于严格模式
}

                  如果想给大量的代码设置严格模式,则可以把代码包含在一个立即执行的函数内,并在函数开头启用严格模式。

(function () {
    "use strict";
    //其他代码
}) ();

        2)在已有代码中谨慎启用严格模式

            如果代码中还包含不符合严格模式的代码,则启用严格模式会产生风险。

           如果要更严格地编写JavaScript代码,可以使用JavaScript代码检查工具。目前流行的检查工具主要是JSLint和JSHint。JSHint是从JSLint派生出来的,比JSLint更轻量级,并且它提供了一系列的可配置项,让开发者可以关掉某些没有必要的错误提示。JSHint使用起来更灵活,并不带有强制性,推荐开发者使用。

           JSHint的检查项则涵盖了大部分的基本编码规范,比如:缺少分号、空格和tab混合使用、错误的转义等。JSHint官方网站上列出了详细的检查项:http://www.jshint.com/docs/options。

3、事件处理和业务逻辑分离

var move_while_dnd = function (e) {
    var lb = scheduler._get_lightbox();    //取得元素
    //设置元素的位置
    lb.style.left = e.clientX+"px";
    lb.style.top = e.clientY+"px";
};

      这个函数是直接绑定到事件上的事件处理函数。这段代码中的业务逻辑和事件处理耦合紧密,不利于代码的复用。好的做法是把事件处理和业务逻辑处理分离开来,业务逻辑处理代码中不需要调用任何event对象中的属性,而在事件处理的代码中则专门处理和事件相关的逻辑,比如取得event对象上的信息、阻止事件冒泡,或者默认行为等。

var setLightBoxPosition = function(top,left) {
    var lb = scheduler._get_lightbox();    //取得元素
    //设置元素的位置
    lb.style.left = left+"px";
    lb.style.top = top+"px";
};

var move_while_dnd = function (e){
    setLightBoxPosition(e.clientY,e.clientX);
};

4、配置数据和代码逻辑分离

    很多数据在代码中是写死了的,比如一些URL、显示在界面上的提示信息、页面元素相关的样式值及其他使用到的固定值,这部分无逻辑数据可统称为配置数据。

    经过业务分析,把可变的配置数据可分离了出来,并使用命名有意义的属性保存这些数据。

this.config = {
    first_hour: 0,
    last_hour: 24,
    hour_size_px: 42,
    min_event_height: 40;
}

     这种将配置数据分离的做法极大地提高了代码的可维护性和可扩展性,开发者可以很方便地修改配置数据而不会导致代码逻辑的错误。分离的配置数据一般以JSON格式保存。

5、逻辑和结构样式分离

    在编写JavaScript代码时,让JavaScript代码只关注逻辑行为,尽量不要越权做HTML代码和CSS代码完成的工作,即要让逻辑和结构样式分离。

    1)从JavaScript代码逻辑中分离CSS样式

        在JavaScript中可以通过设置元素的style对象,来修改元素的样式:

var el=document.getElementById('main_container');
        var list=el.getElementsByTagName('li');
        for(var i=0,j=list.length; i<j; i++){
            list[i].style.borderColor = '#f00';
            list[i].style.borderStyle = 'solid';
            list[i].style.borderWidth = '1px';
        }

        也可以通过设置style上的cssText属性,来设置元素的样式:

var el=document.getElementById('main_container');
        var list=el.getElementsByTagName('li');
        for(var i=0,j=list.length; i<j; i++){
            if(list[i].innerHTML === '') {
                 list[i].style.cssText += 'border: 1px solid #f00;';
            } 
        }

       但这两种方式都降低了代码的可维护性。最佳的做法是,在JavaScript代码中,仅仅是设置元素的Class,然后在页面引用的css文件中添加一条css规则。

var el=document.getElementById('main_container');
        var list=el.getElementsByTagName('li');
        for(var i=0,j=list.length; i<j; i++){
            if(list[i].innerHTML === '') {
                 list[i].className += 'empty';
                 //HTML5:list[i].classList.add('empty');
            } 
        }
.empty {
    border: 1px solid #f00;
}

        如果遇到元素样式的修改,则只修改元素上的Class对应的css代码。

    2)从JavaScript代码逻辑中分离HTML结构

       在JavaScript中,可以通过多种方式生成元素,最常见的是通过元素的innerHTML属性设置内部子元素。

var d2= document.createElement('DIV');
d2.innerHTML='<textarea class="editor">location</textarea>';
d.appendChild(d2);

       这种在JavaScript中内嵌HTML代码的方式,有很大的缺点。首先,开发者很容易写出结构不完整的HTML代码;其次,会带来维护的困难;此外,这种方式也不利于排错。

      让HTML代码和JavaScript代码分离的解决方案:

          (1) 从服务端动态获取HTML代码:将页面加载初始不需要加载的HTML代码从页面中分离,放置在单独的文件中。当需要显示这些内容时,再通过AJAX动态从服务器端获取,然后显示在页面上。或者是把JavaScript模板放置于单独的文件中,需要时才从服务器端加载。

 

        var xhr = new new XMLHttpRequest();
        xhr.open('GET',"store.html",true);
        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4 && xhr.status === 200) {
                document.getElementById('store_container').innerHTML = xhr.responseText;
            }
        };
        xhr.send(null);

 

                     如果页面中引用了jQuer框架,则使用jQuery中提供的方式:

        $('#store_container').load("store.html");

                   使用AJAX技术动态获取HTML代码的另一个优点是减少了页面初始的HTML代码量,加快了页面的传输时间,减少了页面的解析时间。

       (2)通过客户端动态生成页面结构

              在客户端,为了不把动态加载的HTML代码或模板内嵌在JavaScript代码中,可以把这部分代码或模板放置在页面的HTML结构中。如果纯粹是HTML代码,则隐藏在页面中即可,通过JavaScript代码直接设置display样式就可以显示此区域了。模板文件内嵌在页面的HTML代码中的潍坊市有多种,比如放在注释、<textarea>标签中,但最佳的方式是包含在<script>标签中(是符合标准的做法)。

    <script id="main_info" type="text/x-tmpl">
        <li><b>${name}</b> (${class}) </li>
    </script>

                  当需要取得模板代码时,通过innerHTML属性即可得到:

    var infoTemplate = document.getElementById('main_info').innerHTML;

                 HTML5中新加入了一个<template>标签,用来内嵌JavaScript模板代码。

 

6、JavaScript模板的使用

    目前一种流行的网站设计方式是,页面中动态的HTML结构不再由后端生成,后端仅仅提供一套REST API,返回JSON数据。所有HTML都是在前端根据这些数据生成的。模板引擎的使用让这个过程变得非常简单。模板引擎使用原理很简单,每个模板引擎都对应着一套JavaScript模板格式,模板引擎作为模板和数据的桥梁,它通过编译把两者组织在一起,生成 最终的HTML代码。

    大致上,模板引擎可以分为两个大类:一类是把JavaScript逻辑写到了模板中,方便控制复杂的逻辑;另外一类无逻辑(Logic_less)的,模板代码与逻辑分离,符合web结构与逻辑分离这一设计原则。

    目前,从使用率来看,流行的JavaScript模板引擎主要有:

        1)Mustache:http://github.com/janl/mustache.js。被认为是模板引擎的基础,是一套与语言无关的模板引擎系统规范,很多其他的模板引擎都是基于此模板引擎构建的。一个很大的特点是:此模板是无逻辑(Logic_less)的,模板中没有if或者for等结构,而是通过数据值实现这些逻辑。Mustache使用“{”符号包含数据占位。

        2)Underscore中的模板引擎:http://underscorejs.com。是一个实用的JavaScript库,提供了许多JavaScript原生没有的功能,而模板功能是它所提供的一个功能。Underscore的模板功能还支持诸如循环或条件判断等逻辑。

        3)Handlebars:http://handlebarsjs.com。是基于Mustache构建的一个模板引擎。其模板和Mustache的模板是兼容的。Handlebars支持循环和条件判断逻辑块,并且开发者可以实现用自己的辅助函数构建复杂的模板这一功能。Handlebars支持预编译,提高了性能。

        还有Jade:http://jade-lang.com、EJS:http://embeddedjs.com,以及由jQuery作者John Resig创建的Micro-Templating:http://ejohn.org/blog/javascript-micro-templating等流行的模板引擎。

        一个工具类网站Template-engine-chooser:http://garann.github.io/template-chooser。它会辅助开发者根据实际的需要选择合适的模板引擎。

    使用模板引擎时的一些好的实践方法:

       1)尽量不要在模板中滥用逻辑块:循环或条件判断等逻辑块可以使用子模板的形式代替。

     <h1>Comments</h1>
     <div id="comments">
         {{@each comments}}
         <h2><a href="/post#{{id}}">{{title}}</a></h2>
         <div>{{body}}</div>
         {{/each}}
     </div>
     <!--静态部分HTML代码-->
     <h1>Comments</h1>
     <div id="comments">
         
     </div>

     <!--可变的部分作为子模板分离出来-->
     <h2><a href="/post#{{id}}">{{title}}</a></h2>
     <div>{{body}}</div>

           把循环逻辑放置在模板的外部,即JavaScript代码中。降低了后期代码维护的成本。

      2)不要构建太复杂的模板

      3)使用预编译模板

         模板引擎预编译的原理是把模板字符串编译为一个函数,然后把编译好的函数缓存起来。

7、在JavaScript开发中应用MVC模式

       MVC是Model-View-Controlle:http://zh.wikipedia.org/wiki/MVC的缩写,它将程序设计分为三个部分:model层是数据层,view层是用户表现层,而controller是用户交互控制层。一般来说,controller会从model层请求数据,在将数据经过处理后就会放到view层显示给最终用户。

       在前端页面中,通过AJAX或者其他手段将从后端得到的数据进行一些处理并构成model层,而最终的CSS和生成的HTML以及一些细节的DOM操作构成了view层;controller层主要关注具体业务。一篇介绍JavaScript MVC模式的构建方式的文章:http://alistapart.com/article/javascript-mvc。

      在前端构建MVC模式时,推荐使用成熟的JavaScript MVC框架。一般具有如下功能:双向绑定、模板、路由及可观察对象等。

      TodoMVC项目网站:http://todomvc.com/。不仅可用于辅助选择前端MVC框架,同时也是一个很好的学习前端MVC框架的网站。

8、JavaScript模板化开发

      JavaScript模块化开发:是指根据业务或功能的不同对前端代码进行划分。

      模块就是指包含业务相关方法和数据的组合,且模块之间实现松耦合,每个模块都有对外公开的接口,供模块之间互相调用。JavaScript模块化开发的优点是它可按照模块划分前端结构,使得代码的结构更加清晰、团队协作变得更加融洽、单元测试更容易实施。

      为了最大量保持模块的独立性,模块与模块最好通过各自的公开接口来通信。如果模块之间存在很紧的依赖关系,则模块内部最好不要直接访问所依赖的外部模块,而是通过参数的方式传入模块:

var module1 = (function ($, module2) {
    //...
    }) (jQuery, module2);

       目前,通用的JavaScript模块规范主要有两种:CommonJS:http://wiki.commonjs.org/wiki/Modules/1.1  和AMD:http://github.com/amdjs/amdjs-api/wiki/AMD。两者的主要差别是在加载模块的方式上,在CommonJS规范中以同步的方式加载模块,在AMD规范中则是以异步的方式加载模块。

      目前实现了AMD规范的JavaScript库主要有两个:requireJS:http://requirejs.org  和curl:http://github.com/cujojs/curl。其中requireJS使用较为广泛。

9、合理使用AJAX技术

      1)明确AJAX技术的使用场景

         适合使用AJAX技术的场景有:前端会根据用户需求动态取得后端数据,然后更新网页界面;期望通过不刷新页面而取得任何资源或页面,从而给用户提供一种类似桌面应用程序的体验;动态进行用户输入的验证;其他任何期望通过异步方式取得资源的情况。

         使用AJAX的缺陷:因为使用AJAX技术取得的数据都是动态的,所以如果没有添加额外的处理,AJAX的应用就会破坏浏览器上的后退按钮,破话搜索引擎的索引,并且会在任何禁用JavaScript的场合失去作用。

      2)借助成熟的AJAX框架,但不要忘记原生的AJAX使用方法

         一篇介绍如何完整正确地使用原生的AJAX的文章:http://api.jquery.com/category/ajax。

      3)在AJAX操作过程中,做好和用户的交互

         为了提高用户的体验,有必要在进行AJAX的操作的过程中给用户适当的反馈。具体的方式有:在AJAX操作过程中禁用触发此操作的按钮、添加蒙版,或者在代码中添加表明正在操作中的标志位等,可防止用户重复操作,并且在操作过程中要有合适的页面提示,如添加一个加载动画等,告知用户操作进行中。

      4)使用JSON格式作为AJAX传输的数据格式

      5)使用合适的方案,弥补AJAX技术带来的缺陷

          浏览器不会记录AJAX请求的历史,也就是说,不能通过单击后退按钮返回到AJAX请求之前的状态。可以使用jQuery插件jquery-hashchange:https://github.com/cowboy/jquery-hashchange。此插件基本的设计思路是通过设置URL上的hash值来实现浏览器历史的保留,用户可以使用浏览器工具栏上的前进后退按钮来切换页面上的无刷新操作。

 

posted @ 2017-10-20 10:43  新功夫涂鸦  阅读(263)  评论(0编辑  收藏  举报