zoukankan      html  css  js  c++  java
  • 【读书笔记】读《编写可维护的JavaScript》 编程实践(第二部分)

      本书的第二个部分总结了有关编程实践相关的内容,每一个章节都非常不错,捡取了其中5个章节的内容。对大家组织高维护性的代码具有辅导作用。

      5个章节如下——

    一.UI层的松耦合
    二.避免使用全局变量
    三.事件处理
    四.将配置数据从代码中分离出来
    五.抛出自定义错误

      

      一、UI层的松耦合

      如果两个组件耦合太紧,则说明一个组件和另一个组件直接相关,这样的话,如果修改一个组件的逻辑,那么另外一个组件的逻辑也需要修改。比如,有一个名为error的CSS类名,它是贯穿整个站点的,它被嵌入到HTML之中。如果有一天你觉得error的取名并不合适,想将它改为warning,你不仅需要修改CSS还要修改用到这个calssName的HTML。HTML和CSS紧耦合在一起。
      当你能够做到修改一个组件而不需要更改其他的组件的时候,你就做到了松耦合。当一个大系统的每个组件的内容有了限制,就做到了松耦合。本质上讲,每个组件需要保持足够瘦身来确保松耦合。组件知道的越少,就越有利于形成整个系统。有一点要注意:在一起工作的组件无法达到“无耦合”。在所有系统中,组件之间总要共享一些信息来完成各自的工作。这很好理解,我们的目标是确保对一个组件的修改不会经常性地影响其他部分。

      下面我用一个图来来简要解释HTML/CSS/JavaScript三者之间如何做到松耦合——

      

      二、避免使用全局变量

      全局变量所带来的危害在这里就不说了,直接说说如何避免使用全局变量。

      1.单全局变量
      “单全局变量”的意思是所创建的这个唯一全局对象名是独一无二的(不会和内置的API产生冲突),并将你所有的功能代码都挂载到这个全局对象上。因此每个可能的全局变量都成为你唯一全局对象的属性,从而不会创建多个全局变量。
        1> YUI定义了唯一一个YUI全局对象
        2> jQuery定义了两个全局对象,$和jQuery。只有在$被其他的类库使用了的情况下,为了避免冲突,应当使用jQuery
        3> Dojo定义了一个dojo全局对象
        4> Closure类库定义了一个goog全局对象
      2.模块
      如YUI模块及AMD模块(例子略)
      3.零全局变量
      有两种情况会使用这种情形:
        1> 所有所需的脚本都会合并到一个文件
        2> 代码短小且不提供任何接口

    /*
     * 这段立即执行的代码传入了window对象,因此这段代码不需要直接引用任何全局变量。
     * 在这个函数内部,变量doc是指向document对象的引用,只要是函数代码中没有直接修改window或doc且所有变量都是用var关键字定义,
     * 这段脚本则可以注入到页面中而不会产生任何全局变量。
     */ 
    (function(win) {
        
        var doc = win.document;
        
        // 在这里定义其他的变量
        
        // 其他相关代码
        
    })(window);

      三、事件处理

      我们习惯了使用最直接的方式去定义事件,如

    addListener(element, 'click', function() {
        var popup = document.getElementBuId('popup');
        popup.style.left = event.clientX + 'px';
        popup.style.top = event.clientY + 'px';
        popup.className = 'reveal';
    });

      规则一、隔离应用逻辑

      我们将上面的代码进行分解,首先隔离应用逻辑,将应用逻辑从所有事件处理程序中抽离出来的做法,所带来的好处:
        1.提高应用逻辑代码复用性,说不定什么时候其他地方就会触发同一段逻辑。
        2.测试时需要直接触发功能代码,而不必通过模拟对元素的点击来触发。如果将应用逻辑放于事件处理程序中,唯一的测试方法是制造事件的触发。

    // 拆分应用逻辑
    var myApp = {
        
        handleClick: function(event) {
            this.showPopup(event);    
        },
        
        /*
        分离了应用逻辑之后,对这段功能代码的调用可以在多点发生,则不需要一定依赖于某个特定事件的触发。
         */
        showPopup: function(event) {
            var popup = document.getElementBuId('popup');
            popup.style.left = event.clientX + 'px';
            popup.style.top = event.clientY + 'px';
            popup.className = 'reveal';
        }
        
    };
    
    addListener(element, 'click', function(event) {
        myApp.handleClick(event);
    });

      规则二、不要分发事件对象

      上述代码存在一个问题,即event对象被无节制地分发。它从匿名的事件处理函数传入了myApp.handleClick(),然后又传入了myApp.showPopup()。event对象上包含很多和事件相关的额外信息,而这段代码只用到了其中的两个而已。应用逻辑不应当依赖于event对象来正确完成功能,原因如下:
      1.方法接口没有标明那些数据是必要的。好的API一定是对于期望和依赖都是透明的。将event对象作为参数并不能告诉你event的那些属性是有用的,用来干什么。
      2.因此,如果你想测试这个方法,你必须重新创造一个event对象并将它作为参数传入。最佳方法是让事件处理程序使用event对象来处理事件,然后拿到所有需要的数据传给业务逻辑。例如myApp.showPopup()方法只需要两个数据,x坐标和y坐标。

    var myApp = {
        
        handleClick: function(event) {
            this.showPopup(event.clientX, event.clientY);    
        },
        
        /*
        分离了应用逻辑之后,对这段功能代码的调用可以在多点发生,则不需要一定依赖于某个特定事件的触发。
         */
        showPopup: function(x, y) {
            var popup = document.getElementBuId('popup');
            popup.style.left = x + 'px';
            popup.style.top = y + 'px';
            popup.className = 'reveal';
        }
        
    };
    
    addListener(element, 'click', function(event) {
        myApp.handleClick(event);
    });

      这样就很清晰地看到myApp.showPopup()所期望的传入的参数,并且在测试或代码的任意位置都可以很轻易地直接调用这段逻辑。如,myApp.showPopup(10, 10);
      当处理事件时,最好让事件处理程序成为接触到event对象的唯一的函数。事件处理程序应当在进入应用逻辑之前针对event对象执行任何必要的操作,包括阻止默认事件或阻止事件冒泡,都应当直接包含在事件处理程序中。这样很清晰地展现了事件处理程序和应用逻辑之间的分工。

      

    var myApp = {
        
        handleClick: function(event) {
            
            // 假设事件支持DOM Level2
            event.preventDefault();
            event.stopPropagetion();
            
            // 传入应用逻辑
            this.showPopup(event.clientX, event.clientY);    
        },
        
        /*
        分离了应用逻辑之后,对这段功能代码的调用可以在多点发生,则不需要一定依赖于某个特定事件的触发。
         */
        showPopup: function(x, y) {
            var popup = document.getElementBuId('popup');
            popup.style.left = x + 'px';
            popup.style.top = y + 'px';
            popup.className = 'reveal';
        }
        
    };
    
    addListener(element, 'click', function(event) {
        myApp.handleClick(event);
    });

      

      四、将配置数据从代码中分离出来

      配置数据,如
        1.URL
        2.需要展现给用户的字符串
        3.重复的值
        4.设置(比如每页的配置项)
        5.任何可能发生变更的值
      我们时刻要记住,配置数据是可以发生变更的,而且你不希望因为有人突然想修改页面中展示的信息,而导致你去修改JavaScript源码。将配置数据抽离出来意味着任何人都可以修改它们,而不会导致应用逻辑的错误。同样,我们可以将整个config对象放到单独的文件中,这样对配置数据的修改可以完全和使用这些数据的代码隔离开来。

    var config = {
        URL: {
            save: 'module/save',
            remove: 'module/remove',
            update: 'module/update',
            list: 'module/list'
        },
        MESSAGE: {
            empty: '输入内容不可为空',
            success: '编辑成功',
            error: '服务器错误,请稍后再试'
        },
        PAGE: {
            startIndex: 0,        // 开始索引
            limit: 20,            // 每页显示X条数据
            currentPageNum: 1    // 默认当前页
        }
    };

      

      五、抛出自定义错误

      1.抛出错误
      针对所有浏览器,唯一不出差错的显示自定义的错误消息的方式就是用一个Error对象。
      2.抛出错误的好处
      抛出错误陈述发生的问题,能够马上去调试。就像给自己留下告诉自己为什么失败的便签。

    function getDivs(element) {
        if (element && element.getElementByTagName) {
            return element.getElementByTagName('div');
        } else {
            // 推荐总是在错误消息中包含函数名称,以及函数失败的原因
            throw new Error('getDivs(): Argument must be a DOM element');
        }
    }

      3.何时抛出错误
      如果一个函数只被已知的实体调用,错误检查很可能没有必要(这个案例是私有函数);如果不能提前确定函数会被调用的所有地方,你很可能需要一些错误检查。这就更有可能从抛出自己的错误中获益。
      本书中提供了一些抛出错误很好的经验法则:
        1> 一旦修复了一个很难调式的错误,尝试增加一两个自定义错误。当再次发生错误时,这将有助于更容易解决问题。
        2> 如果正在编写代码,思考一下:“我希望[某些事情]不会发生,如果发生,我的代码会一团糟糕”。这时,如果“某些事情”发生,就抛出一个错误。
        3> 如果正在编写的代码别人(不知道是谁)也会使用,思考一下他们使用的方式,在特定的情况下抛出错误。
      4.throw和try-catch的区分

    // 使用throw
    throw new Error('123');
                
    alert(1);    // 不会执行
    // 使用try-catch
    try {
        throw new Error('123');
    } catch(e) {
        alert(e.message);
        // 在这里可以对错误进行处理,目的是从错误中回复
    }
    
    alert(1);    // 会执行
    // 两者结合使用
    function test() {
        throw new Error('123');
        
        alert(1);    // 不会会执行
    }
    
    function test2() {
        try {
            test();
        } catch(e) {
            // 对test调用抛出的错误进行处理
            alert(e.message);    // 123
            // 错误恢复
        }
        alert(2);    // 会执行
    }
    
    test2();

      

      总结:本篇随笔把这本书的第二个部分——编程实践,其中的核心部分抽取出来。这样,结合上一篇随笔(【读书笔记】读《编写可维护的JavaScript》 - 编程风格(第一部分)),完成对本书前两个部分的阅读总结。对第三个部分(自动化),也是最后一个部分,就不做总结了。

      很不错的书,推荐博友们看看。细细品味一下。

      最后,放入这本书的logo——

  • 相关阅读:
    如何通过命令行窗口查看sqlite数据库文件
    eclipse自动补全的设置
    文本装饰
    注释和特殊符号
    文本装饰
    网页背景
    通过ArcGIS Server admin 查看和删除已注册的 Web Adaptor
    通过 ArcGIS Server Manager 查看已安装的 Web Adaptor
    通过 ArcGIS Server Manager 验证 DataStore
    Windows上安装ArcGIS Enterprise——以 Windows Server 2012 R2上安装 ArcGIS 10.8为例
  • 原文地址:https://www.cnblogs.com/jinguangguo/p/3402352.html
Copyright © 2011-2022 走看看