资讯专栏INFORMATION COLUMN

聊聊jQuery的反模式

CoderStudy / 2474人阅读

摘要:如果我们认为模式代表一个最佳的实践,那么反模式将代表我们已经学到一个教训。受启发于的设计模式,在年的月的报告大会上首次提出反模式。参考链接反模式学用设计模式极客学院

如果我们认为模式代表一个最佳的实践,那么反模式将代表我们已经学到一个教训。受启发于Gof的《设计模式》,Andrew Koeing在1995年的11月的C++报告大会上首次提出反模式。在Koeing的报告中,反模式有着两种观念:

描述对于一个特殊的问题,提出了一个糟糕的解决方案,最终导致一个坏结果发生

描述如何摆脱上述解决方案并能提出一个好的解决方案

在如今这个前端发展如火如荼的时代,谈及jq总是显得非常的low,但实际上,在学校,在很多前端新人以及所谓“页面仔 || 切图工”之类的同行之间,jq的活力还是远超各种框架的时候,之所以想写这样一篇文章,一是因为见到了身边的jq烂代码,二是因为我在百度jQuery反模式的时候居然什么有价值的相关结果都没有,所以觉得还是有必要聊聊的。

先从些简单的开始: 插入DOM节点:
// 反模式
$.each(reallyLongArray, function(count, item) {
    var newLi = "
  • " + item + "
  • "; $("#ballers").append(newLi) }) // 更好的实践 var frag = document.createDocumentFragment() $.each(reallyLongArray, function(count, item) { var newLi = "
  • " + item + "
  • "; frag.appendChild(newLi[0]) }) $("#ballers")[0].appendChild(frag) // 你也可以用字符串 var myHTML = "" $.each(reallyLongArray, function(count, item) { myHTML += "
  • " + item + "
  • "; }) $("#ballers").html(myHTML)

    DocumentFragment是浏览器为了减少DOM操作中的更新所使用的API,详情请查阅MDN相关文档。

    遵循DRY原则:
    if ($a.data("current") != "showing") {
        $a.stop()
    }
    if ($b.data("current") != "showing") {
        $b.stop()
    }
    if ($c.data("current") != "showing") {
        $c.stop()
    }
    
    // 用数组来保存不同的主体
    var elems = [$a, $b, $c]
    $.each(elems, function(k, v) {
        if (v.data("current") != "showing") {
            v.stop()
        }
    })

    用数组或对象来保存重复片段的差异参数是一种很常见的方法。更多内容可以参考常见的JavaScript设计模式中的“九、策略模式”

    地狱式回调(callback hell):
    $(document).ready(function() {
      $("#button").click(function() {
        $.get("http://api.github.com/repos/facebook/react/forks", function(data) {
          alert(data[0].owner.login)
        })
      })
    })
    
    // 以前有这么一种优化的方法,使用对象字面量保存回调使其扁平化
    var cbContainer = {
      initApp: function() {
        $(document).ready(cbContainer.readCb)
      },
      readyCb: function() {
        $("#button").click(cbContainer.clickCb)
      },
      clickCb: function() {
        $.get("http://api.github.com/repos/facebook/react/forks", function(data) {
          cbContainer.getCb(data)
        })
      },
      getCb: function(data) {
        alert(data[0].owner.login)
      }
    }
    
    cbContainer.initApp()
    
    // 不过现在流行Promise
    
    var initApp = function() {
      return new Promise(function(resolve, reject) {
          $(document).ready(resolve)
      })
    }
    
    var readyCb = function() {
        return new Promise(function(resolve, reject) {
            $("#button").click(resolve)
        })
    }
    
    var clickCb = function() {
        return new Promise(function(resolve, reject) {
            $.get("http://api.github.com/repos/facebook/react/forks", function(data) {
          resolve(data)
        })
        })
    }
    
    var getCb = function(data) {
        alert(data[0].owner.login)
    }
    
    initApp()
      .then(readyCb)
      .then(clickCb)
      .then(getCb)
    

    用对象将回调扁平还好,Promise是什么鬼。不是比回调还恶心,好吧,示例确实是这样。其实之所以用Promise除了将回调转成链式调用以外,主要还是为了用它的reject函数获取回调中的错误。像示例这种一路resolve的,没必要这么用。这里只是提一句。

    如果希望了解更多关于回调相关的知识,可以看看Promise, generator, async與ES6这篇文章。

    重复查询:
    $(document.body).append("
    ") $(".baaron").click(function() {}) // 更好的方式 $("
    ") .appendTo(document.body) .click(function() {})
    选择器:

    对于jq的选择器还有许多要注意的问题,因为jq的选择器是从右向左查询,所以请记住一个“左轻右重”的原则:

    // 请看下面两个选择器
    $("div.foo .bar")
    $(".foo span.bar")        //右边更明确一点,会好不少
    
    // 当左边确实要比右边明确的时候这么干
    $("#foo .bar")
    $("#foo").find(".bar")
    
    // 尤其避免使用通配符
    $("#foo > *")
    $("#foo").children()
    
    // 有些通配符是隐式的
    $(".foo :radio")
    $(".foo *:radio")        //和上边一样的
    $(".foo input:radio")    //改成这样
    聊聊依赖:

    接下来,让我们从优化一段jq代码开始,聊聊js中的依赖

    $("#button").click(function() {
        $.get("http://xxxx", function(data) {
            $("#page").html(data.abc)
        })
    })

    这段代码有以下问题:

    click事件绑定的匿名函数难以重复利用,也很难测试

    click回调的匿名函数中的$是全局变量,ajax请求回调的匿名函数中的$("#page")也是用到了$这一全局变量,全局变量应该是要避免的

    回调的问题前面也说过了,这里的回调还很清楚不至于说到地狱的程度

    现在我们把代码这样改写:

    var downJSON = function() {
        $.get("http://xxxx", function(data) {
            $("#page").html(data.abc)
        })
    }
    
    $("#button").click(downJSON)

    现在匿名函数被我们拿出来了,可以重用了,但还是难以测试,且涵盖全局变量。

    继续:

    var downJSON = function($, $el) {
        $.get("http://xxxx", function(data) {
            $el.html(data.abc)
        })
    }
    
    $("#button").click(function() {
        downJSON($, $("#page"))
    })

    这样改写以后,没有了全局变量,函数已经独立出去。换一种说法就是,我们去除了函数中的隐式依赖(前面例子中的函数要运行需要全局变量$,但没有从函数声明中表现出来,我们称其为隐式依赖),现在,函数执行所需要的依赖被显示声明,使其具有更好的可控性。前端的依赖管理如今是一个很流行的话题,不过在这里就不废话了。

    奇技淫巧:

    最后,对于几种比较常见的写法,我们也可以使用一些奇技淫巧,或能使代码更短,或能使代码更为易读:

    简化条件语句:
    // 常见的写法
    if(!data) {
        data = {}
    }
    
    // 简化
    data = data || {}

    你可能觉得这不值一提,但可能有些时候你写着写着就忽视了。比如,js数组去重的4个方法中的第二个方法,就可以应用这个技巧:

    // 原来的代码
    Array.prototype.unique2 = function()
    {
        var hashTable = {},res=[];                //n为hash表,r为临时数组
        for(var i = 0; i < this.length; i++) {    //遍历当前数组
            if (!hashTable[this[i]]) {            //如果hash表中没有当前项
                res.push(this[i]);                //把当前数组的当前项push到临时数组里面
                hashTable[this[i]] = true;        //存入hash表
            }
        }
        return res;
    }
    
    // 应用此技巧
    Array.prototype.unique2 = function()
    {
        var hashTable = {}, res = []
        for(var i = 0; i < this.length; i++) {
            !hashTable[this[i]] ? res.push(this[i]) : null
            hashTable[this[i]] = hashTable[this[i]] || true
        }
        return res
    }

    写成这样也未必说是优化,目测判断逻辑还多了一个哈哈,但是嵌套少了一层,怎么说呢,自行决定吧。

    下面展示的一个技巧和上面这个也差不多:

    // 正常写法
    if(type === "foo" || type === "bar") {}
    
    // 用对象有三种方法
    // 方法1
    if(({foo: 1, bar: 1})[type]) {}                    // type === "toString"可以绕过验证
    
    // 方法2
    if(type in ({foo: 1, bar: 1})) {}                // 和上一种等价但是慢点
    
    // 方法3
    if(({foo: 1, bar: 1}).hasOwnProperty(type)) {}    // 最严密,也最慢
    
    // 用正则
    if(/^(foo|bar)$/.test(type)) {}

    这种技巧的话,使用与否同样是自己决定。很多有意思的东西,都是自己想着玩的,有些情况,我们倒不如好好写成switch - case,都看的懂,也挺清晰。

    总结两句,虽说jQuery库和新式的框架相比老了,但我觉得它在DOM操作上真的做到了一个极致。我相信很长一段时间,前端开发人员入门,还是要从它开始的。个人认为jq不适应时代的原因,是因为它本身也仅仅限于DOM操作,没有其他限制,以至于当应用复杂时,你完全控制不住你的页面。当你用上流行的框架,按照他们的Best Practice去组织代码,我想你刚刚开始的时候,一定会怀念jQuery这个溺爱你的老朋友的。

    参考链接:

    反模式 - 学用 JavaScript 设计模式 - 极客学院
    jQuery Anti-Patterns for Performance & Compression
    Patterns of Large-Scale JavaScript Applications

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/78818.html

    相关文章

    • php资料集

      摘要:简单字符串缓存实战完整实战种设计模式设计模式是面向对象的最佳实践成为专业程序员路上用到的各种优秀资料神器及框架成为一名专业程序员的道路上,需要坚持练习学习与积累,技术方面既要有一定的广度,更要有自己的深度。 微型新闻系统的开发(PHP 5.4 + MySQL 5.5) 微型新闻系统的开发(PHP 5.4 + MySQL 5.5) 九个很有用的 PHP 代码 php 代码 国内值得关注的...

      RobinQu 评论0 收藏0
    • 聊聊技术写作的个人体会

      摘要:由此看来,的官方文档就把当成内置函数,这个认识错误是有根源的等到的时候,官方把错误改正过来了,然而改得并不彻底。使用进行判断,结果为的才是内置函数。 showImg(https://segmentfault.com/img/bVbm3Bu?w=5184&h=3456);有群友问过,是什么原因使我开始写技术公众号,又是什么动力让我坚持写的。 在我看来,写作是一件不能敷衍的事,通过写作来学...

      madthumb 评论0 收藏0
    • 从命令式到响应式 (二)

      摘要:知识点回顾,上次主要说了函数式和面向对象,命令式和响应式,系统和系统的差别。以内联的形式使用。响应式中的设计模式观察者模式在这种模式中,一个对象维持一系列依赖于它的对象,将有关的状态变更自动的通知给它们。 知识点回顾,上次主要说了函数式和面向对象,命令式和响应式,push 系统和 pull 系统的差别。在编程范式,风格之外,设计模式也是在程序设计中时时刻刻都在使用的东西,今天主要就讨论...

      DataPipeline 评论0 收藏0

    发表评论

    0条评论

    最新活动
    阅读需要支付1元查看
    <