jQuery中的设计模式

目标:用 jQuery 风格封装 DOM

闭包 & 链式操作


链式风格

  • 也叫 jQuery 风格
    • window.jQuery() 是我们提供的全局函数
  • 特殊函数 jQuery
    • jQuery(选择器) 用于获取对应的元素
    • 但是它不返回这些元素
    • 相反,它返回一个对象,称为 jQuery 构造出来的对象
    • 这个对象可以操作对应的元素

代码风格的形成

  1. 我们想获取一个DOM元素,自己封装一个jQuery函数,最开始的做法是直接return获取到的元素
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        return elements
    }
    
  2. 关键:但是 jQuery 的风格就是没有直接 return elements,而是 return 一个对象,并且这个对象具有一些方法,比如添加属性的方法,这个新生成的api可以操作elements,并且这个jQuery函数,return的刚好就是api
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        // api 可以操作这个elements
        const api = {
            // ES6 的简写形式,之前是"addClass": function(className){}
            addClass(className){
                for(let i=0;i
  3. 闭包:上面这段代码使用了闭包(即函数访问了外部的变量)
  4. 上面这段代码返回的是api,而不是返回的是elements
  5. 当我们调用上面的接口的时候,会给元素添加属性
    const api = jQuery('.test')  // 不返回元素们,返回 api 对象
    api.addClass('red')    // 遍历所有刚才获取的元素,添加 .red
    
  6. 关键:我们之前的代码是 api 里面的函数对象,返回的undefined,那么能不能返回api呢?可以
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        const api = {
            addClass(className){
                for(let i=0;i
  7. 链式操作:这意味着我们在使用了api的对象函数后,拿到的还是一个api对象,那么这样就可以进行链式操作
    const api = jQuery('.test')  // 不返回元素们,返回 api 对象
    api.addClass('red').addClass('blue').addClass('green')    // 链式操作
    
  8. 我们之前有一个概念:函数如果使用一个对象来调用,那么这个函数的this就是调用函数的对象,也就是obj.fn(p1)等价于obj.fn.call(obj, p1);我们针对这个概念对代码进行改进
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        const api = {
            addClass(className){
                for(let i=0;i
  9. api是我们定义的,我们能不能直接return?可以的
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        return {
            addClass(className){
                for(let i=0;i
  10. 总结:jQuery的核心思想是
    • 闭包:提供一个函数,这个函数接收一个选择器,jQuery将根据选择器获取这些元素,但是不会去返回元素,而是返回对象,对象中有对应的方法,这些方法来操作元素,jQuery用闭包去维持elements与函数
    • 链式操作:函数在调用的时候充分利用了this形成了链式操作

实现 find 函数


我们通常所说的jQuery对象指的是jQuery构造出来的对象

jQuery是构造函数吗?

    • 因为 jQuery函数确实构造出了一个对象
  • 不是
    • 因为不需要写new jQuery()就能构造出来一个对象
    • 以前见到的构造函数必须结合 new 才行
  • 结论
    • jQuery 是一个不需要加 new 的构造函数
    • jQuery 不是常规意义上的构造函数

链式风格

    • jQuery('#xxx')返回并不是一个元素,而是一个api对象
    • jQuery('#xxx').find('.red')查找#xxx元素中的.red元素
    • jQuery('#xxx').parent()获取爸爸
    • jQuery('#xxx').children()获取儿子
    • jQuery('#xxx').siblings()获取兄弟
    • jQuery('#xxx').index()获取排行老几(从0开始)
    • jQuery('#xxx').next()获取弟弟
    • jQuery('#xxx').prev()获取哥哥
    • jQuery('#xxx').each(fn)遍历并对每一个元素执行fn

实现find

  1. 这段代码添加了find方法,但是这个find方法返回的是数组,无法进行链式操作
  2. 如果我们将array改成this,其实在调用的时候,返回的是elements
    window.jQuery = function(selector){
        const elements = document.querySelectorAll(selector)
        return {
            addClass(className){
                for(let i=0;i
  3. 我们能不能将elements直接改成array?不可以,一旦修改,会影响之前我们对elements的操作
    window.jQuery = function(selector){
        let elements = document.querySelectorAll(selector)
        return {
            addClass(className){
                for(let i=0;i
  4. 我们需要对jQuery重新进行封装
    • 我们对于api要重新构造,这个新的api要靠jQuery重新构造
    • jQuery不能只接收选择器,还需要接收数组。
    • 如果接收的是数组就让elements等于这个数组
      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

实现end函数


  1. 就是回到上一级的elements
  2. 我们将之前的api记录下来
    window.jQuery = function(selectorOrArray){
        let elements
        if(typeof selectorOrArray === 'string'){
            elements = document.querySelectorAll(selectorOrArray)
        }else if(typeof selectorOrArray instanceof Array){
            elements = selectorOrArray
        }
    
        return {
            // 直接将oldApi记录下来
            oldApi: selectorOrArray.oldApi,
            addClass(className){
                for(let i=0;i

实现其他的一些操作



window.jQuery = function(selectorOrArrayOrTemplate){
    let elements
    if(typeof selectorOrArrayOrTemplate === 'string'){
        if(selectorOrArrayOrTemplate[0] === '<'){
            // 创建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 {
        // 标记
        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 this.get(0).appendChildren(node))
            }
        },
        find(selector){
            let array = []
            for(let i=0;i {
                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

命名风格


  • 下面的代码令人费解
    • const div = $('div#test')
    • 我们会误以为div是一个DOM
    • 实际上div是jQuery构造的api对象
      怎么避免这种误解呢?
  • 改成这样
    • const ('div#text')
    • $div.appendChild 不存在,因为它不是DOM对象
    • $div.find存在,因为它是jQuery对象

总结


这是一种什么感觉

  • 就感觉DOM是不可见的
  • 你不需要知道DOM的任何细节
  • 只需要使用简洁的API即可
  • 一个好的封装,能让使用者完全不知道内部的细节
  • 这是通过闭包实现的

你可能感兴趣的:(jQuery中的设计模式)