nfssuzukaze / Blog

0 stars 0 forks source link

jQuery 神奇操作 #6

Open nfssuzukaze opened 3 years ago

nfssuzukaze commented 3 years ago

jQuery 神奇操作

  1. 简化操作

    • 曾经写过 DOM 库,具体是将包括操作的函数的对象放在了 window 对象的 dom 属性中,调用 dom 库获取元素时可以直接使用 dom.xxx(parameters) 就可以,而调用 jQuery 库获取元素竟然只需要使用 $(parameters) 即可,相比之下更加简洁

      // 这是自己写的 dom 库
      window.dom = { xxx() {}, ...}
      // 这是 jQuery 的 dom 库
      window.$ = window.jQuery = function(parameters) { ... return {xxx() {}, ...} }
      1. jQuery 通过函数对传入的 parameters 进行一定的处理得到 elements
      2. 返回一个具有各种操作的 API 对象
      3. API 对象中有对 elements 的引用,导致 elements 并不会被 js 的垃圾回收机制回收掉,从而在保留获取指定元素的前提下获得各种操作 dom 的方法
  2. 链式操作

    • jQuery 可以做到 $('div').find('h3').parent() ,找到 div 元素中的 h3 元素的父元素。在前一个 .xxx() 后可以继续使用 .xxx() 对对象进行操作

      window.$ = window.jQuery = function(parameters) { ... return {xxx() {... return this}}}

      jQuery 通过 return this 的操作,将调用 xxx() 函数的对象再次返回,使得下一次能够继续调用

      但是这样有一个问题,比如 $('div').find('h3').parent() 操作,其中的 parent() 作用的并不是 h3 而是 div ,原因在于 find('h3') 返回了 this ,即与 div 有关联的对象,而不是与 h3 关联的对象。

      本来想是以修改 elements 来使得链式操作能够正常进行,但是这样同样也会出现问题,比如

      let temp1 = $('div')
      let temp2 = temp1.find('h3')
      temp1.find('h4')

      这时候再对 temp1 进行操作,那么操作的就不是 div ,而是 h3 ,其原因就在于所有返回的对象关联的都是同一个 elements ,一旦通过一个对象修改了 elements ,那么其他对象访问的 elements 也会是被修改过后的 elements 。为对应更新元素,jQuery 有了如下解决方法

      window.$ = window.jQuery = function(parameters) {
       ...
       return {
           find(selector) {
               let array = []
               for (let i = 0; i < elements.length; i ++) {
                   array.push(...Array.from(elements[i].querySelectorAll(selector)))
               }
               return $(array)
           }
       }
      }
    • 这里可以发现传入 $(parameters) 的参数已经不是一个普通的 String 了,而是一个 Array ,故需要对 parameters 进一步处理

      letlet elements
      if (typeof parameters === 'string') {
       elements = document.querySelectorAll(parameters)
      } else if (parameters instanceof Array) {
       elements = parameters
      }
  3. 回退一步结果集

    jQuery 中有一个 end() 函数,可以使得结果集回退一步,例如:

    $('div').find('.hello').addClass('world').end().addClass('helloworld')
    // 为 div 中的类名为 hello 的元素添加 world 类名,然后为 div 添加 helloworld 类名

    end() 的实现需要其他函数的配合,例如,需要更换结果集的时候,需要将原结果集添加到新结果集中

    find(selector) {
       let array = []
       for (let i = 0; i < elements.length; i ++) {
           array.push(...Array.from(elements[i].querySelectorAll(selector)))
       }
       array.oldApi = this
       return $(array)
    }

    如此的话,end() 函数就更好实现了

    end() {
       return elements.oldApi
    }
  4. 原型链

    上述的实现其实有一个很大的问题,每调用一次函数都会返回一个全新而内容又完全相同的对象,极大程度的浪费了内存,但是利用 js 的原型链可以极好的解决这个问题

    window.$ = window.jQuery = function(parameters) {
       ...
       const api = Object.create(jQuery.prototype)
       api.elements = elements
       return api
    }
    jQuery.prototype = {
       constructor: jQuery,
       ...
    }

模仿 jQuery 实现一个小 dom 库