jQuery中的設(shè)計(jì)模式

目標(biāo):用 jQuery 風(fēng)格封裝 DOM

閉包 & 鏈?zhǔn)讲僮?/h3>

鏈?zhǔn)斤L(fēng)格

  • 也叫 jQuery 風(fēng)格
    • window.jQuery() 是我們提供的全局函數(shù)
  • 特殊函數(shù) jQuery
    • jQuery(選擇器) 用于獲取對(duì)應(yīng)的元素
    • 但是它不返回這些元素
    • 相反,它返回一個(gè)對(duì)象,稱(chēng)為 jQuery 構(gòu)造出來(lái)的對(duì)象
    • 這個(gè)對(duì)象可以操作對(duì)應(yīng)的元素

代碼風(fēng)格的形成

  1. 我們想獲取一個(gè)DOM元素,自己封裝一個(gè)jQuery函數(shù),最開(kāi)始的做法是直接return獲取到的元素
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        return elements
    }
    
  2. 關(guān)鍵:但是 jQuery 的風(fēng)格就是沒(méi)有直接 return elements,而是 return 一個(gè)對(duì)象,并且這個(gè)對(duì)象具有一些方法,比如添加屬性的方法,這個(gè)新生成的api可以操作elements,并且這個(gè)jQuery函數(shù),return的剛好就是api
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        // api 可以操作這個(gè)elements
        const api = {
            // ES6 的簡(jiǎn)寫(xiě)形式,之前是"addClass": function(className){}
            addClass(className){
                for(let i=0;i<elements.length;i++){
                    elements[i].classList.add(className)
                }
                return undefined
            }
        }
        return api
    }
    
  3. 閉包:上面這段代碼使用了閉包(即函數(shù)訪問(wèn)了外部的變量)
  4. 上面這段代碼返回的是api,而不是返回的是elements
  5. 當(dāng)我們調(diào)用上面的接口的時(shí)候,會(huì)給元素添加屬性
    const api = jQuery('.test')  // 不返回元素們,返回 api 對(duì)象
    api.addClass('red')    // 遍歷所有剛才獲取的元素,添加 .red
    
  6. 關(guān)鍵:我們之前的代碼是 api 里面的函數(shù)對(duì)象,返回的undefined,那么能不能返回api呢?可以
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        const api = {
            addClass(className){
                for(let i=0;i<elements.length;i++){
                    elements[i].classList.add(className)
                }
                // 直接返回這個(gè)對(duì)象本身
                return api
            }
        }
        return api
    }
    
  7. 鏈?zhǔn)讲僮?/strong>:這意味著我們?cè)谑褂昧薬pi的對(duì)象函數(shù)后,拿到的還是一個(gè)api對(duì)象,那么這樣就可以進(jìn)行鏈?zhǔn)讲僮?
    const api = jQuery('.test')  // 不返回元素們,返回 api 對(duì)象
    api.addClass('red').addClass('blue').addClass('green')    // 鏈?zhǔn)讲僮?
  8. 我們之前有一個(gè)概念:函數(shù)如果使用一個(gè)對(duì)象來(lái)調(diào)用,那么這個(gè)函數(shù)的this就是調(diào)用函數(shù)的對(duì)象,也就是obj.fn(p1)等價(jià)于obj.fn.call(obj, p1);我們針對(duì)這個(gè)概念對(duì)代碼進(jìn)行改進(jìn)
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        const api = {
            addClass(className){
                for(let i=0;i<elements.length;i++){
                    elements[i].classList.add(className)
                }
                // 改進(jìn)
                return this
            }
        }
        // 這邊不能動(dòng)
        return api
    }
    
  9. api是我們定義的,我們能不能直接return?可以的
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        return {
            addClass(className){
                for(let i=0;i<elements.length;i++){
                    elements[i].classList.add(className)
                }
                return this
            }
        }
    }
    
  10. 總結(jié):jQuery的核心思想是
    • 閉包:提供一個(gè)函數(shù),這個(gè)函數(shù)接收一個(gè)選擇器,jQuery將根據(jù)選擇器獲取這些元素,但是不會(huì)去返回元素,而是返回對(duì)象,對(duì)象中有對(duì)應(yīng)的方法,這些方法來(lái)操作元素,jQuery用閉包去維持elements與函數(shù)
    • 鏈?zhǔn)讲僮鳎汉瘮?shù)在調(diào)用的時(shí)候充分利用了this形成了鏈?zhǔn)讲僮?/li>

實(shí)現(xiàn) find 函數(shù)


我們通常所說(shuō)的jQuery對(duì)象指的是jQuery構(gòu)造出來(lái)的對(duì)象

jQuery是構(gòu)造函數(shù)嗎?

    • 因?yàn)?jQuery函數(shù)確實(shí)構(gòu)造出了一個(gè)對(duì)象
  • 不是
    • 因?yàn)椴恍枰獙?xiě)new jQuery()就能構(gòu)造出來(lái)一個(gè)對(duì)象
    • 以前見(jiàn)到的構(gòu)造函數(shù)必須結(jié)合 new 才行
  • 結(jié)論
    • jQuery 是一個(gè)不需要加 new 的構(gòu)造函數(shù)
    • jQuery 不是常規(guī)意義上的構(gòu)造函數(shù)

鏈?zhǔn)斤L(fēng)格

    • jQuery('#xxx')返回并不是一個(gè)元素,而是一個(gè)api對(duì)象
    • jQuery('#xxx').find('.red')查找#xxx元素中的.red元素
    • jQuery('#xxx').parent()獲取爸爸
    • jQuery('#xxx').children()獲取兒子
    • jQuery('#xxx').siblings()獲取兄弟
    • jQuery('#xxx').index()獲取排行老幾(從0開(kāi)始)
    • jQuery('#xxx').next()獲取弟弟
    • jQuery('#xxx').prev()獲取哥哥
    • jQuery('#xxx').each(fn)遍歷并對(duì)每一個(gè)元素執(zhí)行fn

實(shí)現(xiàn)find

  1. 這段代碼添加了find方法,但是這個(gè)find方法返回的是數(shù)組,無(wú)法進(jìn)行鏈?zhǔn)讲僮?/li>
  2. 如果我們將array改成this,其實(shí)在調(diào)用的時(shí)候,返回的是elements
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        return {
            addClass(className){
                for(let i=0;i<elements.length;i++){
                    elements[i].classList.add(className)
                }
                return this
            },
            find(selector){
                let array = []
                for(let i=0;i<elements.length;i++){
                    // 偽數(shù)組要用Array.from()
                    const elements2 = Array.from(elements[i].querySelectorAll(selector))
                    array = array.concat(elements2)
                }
                return this
            }
        }
    }
    
  3. 我們能不能將elements直接改成array?不可以,一旦修改,會(huì)影響之前我們對(duì)elements的操作
    window.jQuery = function(selector){
        let elements = document.querySelectorAll(selector)
        return {
            addClass(className){
                for(let i=0;i<elements.length;i++){
                    elements[i].classList.add(className)
                }
                return this
            },
            find(selector){
                let array = []
                for(let i=0;i<elements.length;i++){
                    // 偽數(shù)組要用Array.from()
                    const elements2 = Array.from(elements[i].querySelectorAll(selector))
                    array = array.concat(elements2)
                }
                eleemnts = array
                return this
            }
        }
    }
    
  4. 我們需要對(duì)jQuery重新進(jìn)行封裝
    • 我們對(duì)于api要重新構(gòu)造,這個(gè)新的api要靠jQuery重新構(gòu)造
    • jQuery不能只接收選擇器,還需要接收數(shù)組。
    • 如果接收的是數(shù)組就讓elements等于這個(gè)數(shù)組
      window.jQuery = function(selectorOrArray){
          let elements
          if(typeof selectorOrArray === 'string'){
              elements = document.querySelectorAll(selectorOrArray)
          }else if(typeof selectorOrArray instanceof Array){
              elements = selectorOrArray
          }
      
          return {
              addClass(className){
                  for(let i=0;i<elements.length;i++){
                      elements[i].classList.add(className)
                  }
                  return this
              },
              find(selector){
                  let array = []
                  for(let i=0;i<elements.length;i++){
                      const elements2 = Array.from(elements[i].querySelectorAll(selectorOrArray))
                      array = array.concat(elements2)
                  }
                  // 返回一個(gè)新的api
                  return jQuery(array)
              }
          }
      }
      

實(shí)現(xiàn)end函數(shù)


  1. 就是回到上一級(jí)的elements
  2. 我們將之前的api記錄下來(lái)
    window.jQuery = function(selectorOrArray){
        let elements
        if(typeof selectorOrArray === 'string'){
            elements = document.querySelectorAll(selectorOrArray)
        }else if(typeof selectorOrArray instanceof Array){
            elements = selectorOrArray
        }
    
        return {
            // 直接將oldApi記錄下來(lái)
            oldApi: selectorOrArray.oldApi,
            addClass(className){
                for(let i=0;i<elements.length;i++){
                    elements[i].classList.add(className)
                }
                return this
            },
            find(selector){
                let array = []
                for(let i=0;i<elements.length;i++){
                    const elements2 = Array.from(elements[i].querySelectorAll(selectorOrArray))
                    array = array.concat(elements2)
                }
                // 將當(dāng)前操作的api放入數(shù)組中,作為記錄保存下來(lái)
                array.oldApi = this
                return jQuery(array)
            },
            end(){
                // 直接return就好
                return this.oldApi
            }
        }
    }
    ```
    

實(shí)現(xiàn)其他的一些操作



window.jQuery = function(selectorOrArrayOrTemplate){
    let elements
    if(typeof selectorOrArrayOrTemplate === 'string'){
        if(selectorOrArrayOrTemplate[0] === '<'){
            // 創(chuàng)建div
            elements = [createElement(selectorOrArrayOrTemplate)]
        }else{
            // 查找div
            elements = document.querySelectorAll(selectorOrArrayOrTemplate)
        }
    }else if(typeof selectorOrArrayOrTemplate instanceof Array){
        elements = selectorOrArrayOrTemplate
    }
    function createElement(string){
        const container = document.createElement("template");
        container.innerHTML = string.trim();
        return container.content.firstChild;
    }
    
    return {
        // 標(biāo)記
        jquery: true,
        elements: elements,
        get(index){
            return elements[index]
        },
        appendTo(node){
            if(node instanceof Element){
                this.each(el => node.appendChild(el))
            }else if(node.jquery === true){
                this.each(el => node.get(0).appendChild(el))
            }
        },
        append(children){
            if(node instanceof Element){
                this.get(0).appendChild(children)
            }else if(children instanceof HTMLCollection){
                for(let i=0;i<children.length;i++){
                    this.get(0).appendChild(children[i])
                }
            }else if(children.jquery === true){
                children.each(node => this.get(0).appendChildren(node))
            }
        },
        find(selector){
            let array = []
            for(let i=0;i<elements.length;i++){
                const elements2 = Array.from(elements[i].querySelectorAll(selectorOrArrayOrTemplate))
                array = array.concat(elements2)
            }
            array.oldApi = this
            return jQuery(array)
        },
        each(fn){
            for(let i=0;i<elements.length;i++){
                fn.call(null, elements[i], i)
            }
            return this
        },
        parent(){
            const array = []
            this.each((node) => {
                if(array.indexOf(node.parentNode) === -1){
                    array.push(parentNode)
                }
            })
            return jQuery(array)
        },
        children(){
            const array = []
            this.each((node) => {
                if(array.indexOf(node.parentNode) === -1){
                    array.push(...node.children)
                }
            })
        },
        print(){
            console.log(elements)
        },
        addClass(className){
            for(let i=0;i<elements.length;i++){
                elements[i].classList.add(className)
            }
            return this
        },
        oldApi: selectorOrArrayOrTemplate.oldApi,
        end(){
            return this.oldApi
        } 
    }
}

// 簡(jiǎn)寫(xiě)
window.$ = window.jQuery

命名風(fēng)格


  • 下面的代碼令人費(fèi)解
    • const div = $('div#test')
    • 我們會(huì)誤以為div是一個(gè)DOM
    • 實(shí)際上div是jQuery構(gòu)造的api對(duì)象
      怎么避免這種誤解呢?
  • 改成這樣
    • const div =('div#text')
    • $div.appendChild 不存在,因?yàn)樗皇荄OM對(duì)象
    • $div.find存在,因?yàn)樗莏Query對(duì)象

總結(jié)


這是一種什么感覺(jué)

  • 就感覺(jué)DOM是不可見(jiàn)的
  • 你不需要知道DOM的任何細(xì)節(jié)
  • 只需要使用簡(jiǎn)潔的API即可
  • 一個(gè)好的封裝,能讓使用者完全不知道內(nèi)部的細(xì)節(jié)
  • 這是通過(guò)閉包實(shí)現(xiàn)的
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容