聊聊jQuery的反模式

若是咱們認爲模式表明一個最佳的實踐,那麼反模式將表明咱們已經學到一個教訓。受啓發於Gof的《設計模式》,Andrew Koeing在1995年的11月的C++報告大會上首次提出反模式。在Koeing的報告中,反模式有着兩種觀念:javascript

  • 描述對於一個特殊的問題,提出了一個糟糕的解決方案,最終致使一個壞結果發生html

  • 描述如何擺脫上述解決方案並能提出一個好的解決方案前端

在現在這個前端發展如火如荼的時代,談及jq老是顯得很是的low,但實際上,在學校,在不少前端新人以及所謂「頁面仔 || 切圖工」之類的同行之間,jq的活力仍是遠超各類框架的時候,之因此想寫這樣一篇文章,一是由於見到了身邊的jq爛代碼,二是由於我在百度jQuery反模式的時候竟然什麼有價值的相關結果都沒有,因此以爲仍是有必要聊聊的。java

先從些簡單的開始:

插入DOM節點:

// 反模式
$.each(reallyLongArray, function(count, item) {
    var newLi = '<li>' + item + '</li>';
    $('#ballers').append(newLi)
})

// 更好的實踐
var frag = document.createDocumentFragment()
$.each(reallyLongArray, function(count, item) {
    var newLi = '<li>' + item + '</li>';
    frag.appendChild(newLi[0])
})
$('#ballers')[0].appendChild(frag)

// 你也能夠用字符串
var myHTML = ''
$.each(reallyLongArray, function(count, item) {
    myHTML += '<li>' + item + '</li>';
})
$('#ballers').html(myHTML)

DocumentFragment是瀏覽器爲了減小DOM操做中的更新所使用的API,詳情請查閱MDN相關文檔。react

遵循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設計模式中的「9、策略模式」git

地獄式回調(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的,不必這麼用。這裏只是提一句。es6

若是但願瞭解更多關於回調相關的知識,能夠看看Promise, generator, async與ES6這篇文章。github

重複查詢:

$(document.body).append('<div class="baaron"></div>')
$('.baaron').click(function() {})

// 更好的方式
$('<div class="baaron"></div>')
    .appendTo(document.body)
    .click(function() {})

選擇器:

對於jq的選擇器還有許多要注意的問題,由於jq的選擇器是從右向左查詢,因此請記住一個「左輕右重」的原則:web

// 請看下面兩個選擇器
$('div.foo .bar')
$('.foo span.bar')        //右邊更明確一點,會好很多

// 當左邊確實要比右邊明確的時候這麼幹
$('#foo .bar')
$('#foo').find('.bar')

// 尤爲避免使用通配符
$('#foo > *')
$('#foo').children()

// 有些通配符是隱式的
$('.foo :radio')
$('.foo *:radio')        //和上邊同樣的
$('.foo input:radio')    //改爲這樣

聊聊依賴:

接下來,讓咱們從優化一段jq代碼開始,聊聊js中的依賴ajax

$('#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

相關文章
相關標籤/搜索