美文网首页
模拟 jQuery API的实现

模拟 jQuery API的实现

作者: 夜未央_M | 来源:发表于2019-01-23 18:05 被阅读12次

    jQuery 是什么?

    jQuery实质上是一个构造函数,接受一个参数,这个参数可能是节点,然后返回一个方法对象去操作节点。官方文档是这样说明的:
    jQuery是一个快速,小巧,功能丰富的JavaScript库。它通过易于使用的API在大量浏览器中运行,使得HTML文档遍历和操作,事件处理,动画和Ajax变得更加简单。

    那么今天我们就来演示一下jQuery API的工作原理

    用原生DOM写一个类似jQuery的API

    1.写一个带有id的 ul 列表

    <!DOCTYPE html>
    <html lang="zh">
    
        <head>
            <meta charset="UTF-8" />
            <meta name="viewport"
              content="width=device-width, 
              initial-scale=1.0" />
            <meta http-equiv="X-UA-Compatible" content="ie=edge" />
            <title>模拟 jQuery API的实现</title>
        </head>
    
        <body>
            <ul>
                <li id="item1">选项1</li>
                <li id="item2">选项2</li>
                <li id="item3">选项3</li>
                <li id="item4">选项4</li>
                <li id="item5">选项5</li>
                <li id="item6">选项6</li>
            </ul>
            <script>
            </script>
        </body>
    
    </html>
    


    2. 以item3为节点,找到其兄弟节点

    通过 var allChildren = item3.parentNode.children 获取 item3 父节点的所以子节点,然后遍历所有子节点,得到 item3以外 的所有节点,这样就找到选项3的所以兄弟节点啦。可以 console.log一下。
    (由于array是伪数组,不能用push的方法,所以我们用到 array[array.length] = allChildren[i] 的方法)

    <script>
          var allChildren = item3.parentNode.children
          var array = {length:0}
          for(let i = 0; i < allChildren.length; i++){
            if(allChildren[i] !== item3){
              array[array.lenth]=allChildren[i]
              array.length += 1
            }
          }
          console.log(array)
    </script>
    


    3. 代码封装

    封装的好处有很多:给代码一个名字方便调用;形成局部变量可以避免覆盖JS原始变量(立即调用函数)等
    给这个函数取个名字,如 getSiblings;把 item3 换成 node,这样输入任意节点都可以使用这个函数了;注意不要忘记 return,这样我们就得到了一个函数 function getSiblings(node){}

    <script>
          function getSiblings(node){
            var allChildren = node.parentNode.children
            var array = {length:0}
            for(let i = 0; i < allChildren.length; i++){
              if(allChildren[i] !== node){
                array[array.lenth]=allChildren[i]
                array.length += 1
              }
            }
            return array
          }
    </script>
    


    4. 封装函数:function addClass(node, classes){}

    现在我们要给 item3 加 class属性
    首先我们声明一个 classes 对象,里面有 a、b、c 三个属性;同时分别给它们一个布尔值,方便 add 和 remove;遍历各个属性。

          var classes = {'a':false, 'b':true, 'c':true}
          for(let key in classes){
            var value = classes[key]
            var methodName = value?'add':'remove';
            item3.classList[methodName](key)
          }
    

    可以看到,class b、c已经被添加到 item3 中了
    同样我们来封装一下这些代码,如下所示:

    function addClass(node, classes){
      for (var key in classes){
        var value = classes[key]
        var methodName = value ? 'add' : 'remove'
        //console.log (methodName )
        //console.log (node.classList)
        //console.log (node.classList.add)
        //console.log (node.classList[methodName])
        node.classList[methodName](key)
      }
    }
    /*
    obj.x()  等同于  obj['x']()
    注意一点上述代码不能用点运算符,要用[]运算符,classList['add'] === classList.add
    */
    addClass(item3, {a:true, b:false, c:true})
    

    现在,只要你给一个 node 和 classes 于此函数,就可以给 该节点添加 classes所包含的正确属性


    5.命名空间:

    给封装的函数一个名字,方便其他人使用,同时防止与前人命名的冲突。
    假如我们想把这两个封装好好的函数联系到一起,以便以后调用的话,我们可以这样去写。

    window.xxdom = {} 
    xxdom.getSiblings = function (node) { 
      var allChildren = node.parentNode.children
    
      var array = {
        length: 0
      }
      for (let i = 0; i < allChildren.length; i++) {
        if (allChildren[i] !== node) {
          array[array.length] = allChildren[i]
          array.length += 1
        }
      }
      return array
    }
    xxdom.addClass = function (node, classes) {
      classes.forEach( (value) => node.classList.add(value) )
    }
    
    xxdom.getSiblings(item3)
    xxdom.addClass(item3, ['a','b','c'])
    


    6.能不能把node 放在前面

    node.getSiblings()
    node.addClass()

    我们发现当我们要用到上面的命名空间的时候会非常麻烦,我们总是要用 dom.getSiblings()、xxdom.addClass()、总是要带有别人的一个小尾巴。 item3.getSiblings()、item3.addClass() 好像看起来更清爽一些,有没有办法来实现呢?
    其实是有的。为了验证,我们给 Node 的共有属性添加 getSiblings属性,然后我们测试下能否访问到:

          Node.prototype.getSiblings = function(){
            return 1
          }
    

    现在有个问题,我们这里的getSiblings()怎么获取到item3 ?
    方法一:扩展 Node 接口
    直接在 Node.prototype 上加函数
    Node 如何取到 item3?
    this
    why?把上面写成 .call 的形式,因为this 是call 的第一个参数。


    那么 item3 为什么会有 getSiblings属性呢?
    因为我们篡改了其 proto 最终指向的 node.prototype 的共有属性,然后添加了一个 getSiblings的方法,然后它里面呢先去获取this 的父节点的所有儿子,那么 this 是谁呢?this 就是你在调用的时候就会帮你把 item3给传递进来,item3会自动的传给 getSiblings() 的 this,所以 this 就是.前面的东西,不管你是什么。然后声明一个伪数组,遍历这个伪数组,如果伪数组里面的第[i]项全不等于 this (这时候的 this 就是 item3),那么不等于 item3的 item放到伪数组里面,伪数组的 length +1,循环后返回这个伪数组,于是我们得到了 item1、item2、item4、item5。

    ** 自己命名新的接口 Node2**
    前面写的内容其实不是很好,为什么呢?
    因为我们在改 node 的共用属性。比如 A 同学在上面添加了2个函数,B 同学也在上面添加了2个函数,然后我们也添加了2个函数,所以就存在被覆盖的可能性。
    那, 如果不改原型,我们怎么能实现 item3.getSiblings呢?方法也是有的!就是命名新的接口,示例如下:

     function Node2(node){
         return {
             element: node,
             getSiblings: function(){
    
             },
             addClass: function(){
    
             }
         }
     }
     let node =document.getElementById('x')
     let node2 = Node2(node)
     node2.getSiblings()
     node2.addClass()
    



    真实代码(这种对 Node 没有产生破坏的方法叫做「无侵入」)如下:

    window.Node2 = function (node) {
      return {
        getSiblings: function () {
          var allChildren = node.parentNode.children
          var array = {
            length: 0
          }
          for (let i = 0; i < allChildren.length; i++) {
            if (allChildren[i] !== node) {
              array[array.length] = allChildren[i]
              array.length += 1
            }
          }
          return array
        },
        addClass: function (classes) {
          for (var key in classes) {
            var value = classes[key]
            var methodName = value ? 'add' : 'remove'
            node.classList[methodName](key)
          }
        }
      }
    }
    


    7. 把 Node2 改成jQuery

    通过上面的操作,我们就在原有 Node 的基础上新扩展了接口,我们这时候就可以给新扩展的起个自己喜欢的名字,就叫jQuery吧

    function jQuery(node) {
      return {
        element: node,
        getSiblings: function () {
        },
        addClass: function () {
        }
      }
    }
    let node = document.getElementById('x')
    let node2 = jQuery(node)
    node2.getSiblings()
    node2.addClass()
    

    实际操作如下:

    window.jQuery = function (nodeOrselector) {
      let nodes = {}
      if (typeof nodeOrselector === 'string') {
        let temp = document.querySelectorAll(nodeOrselector) //伪数组
        for (let i = 0; i < temp.length; i++) {
          nodes[i] = temp[i]
        }
        nodes.length = temp.length
      } else if (nodeOrSelector instanceof node) {
        node = {
          0: nodeOrSelector,
          length: 1
        }
      }
    
      nodes.getSiblings = function () {
        var allChildren = node.parentNode.children
        var array = {
          length: 0
        }
        for (let i = 0; i < allChildren.length; i++) {
          if (allChildren[i] !== node) {
            array[array.length] = allChildren[i]
            array.length += 1
          }
        }
        return array
      }
    
    
      nodes.addClass = function (classes) {
        classes.forEach((value) => {
          for (let i = 0; i < nodes.length; i++) {
            node[i].calssList.add(value)
          }
        })
      }
    
      return nodes
    
    }
    
    window.$ = jQuery   //缩写吧:alias
    var $div = $('div')
    $div.addClass('red') // 可将所有 div 的 class 添加一个 red
    $div.setText('hi') // 可将所有 div 的 textContent 变为 hi
    


    8. $缩写与alias

    window.$ = jQuery
    即var node2 = $(node)
    

    但是为了防止记混 node2 到底有没有引入 jQuery
    大家通常这样写:

    var $node2 = $(node)
    

    我们会在引用的 jQuery标签前面加上$符号来区分原生的标签或者接口,看到$我们立马就明白了这是由 jQuery 引用染生出来的。

    到这里我们已经知道 jQuery 是个什么了:它就是一个函数,是 JS 原始 DOM 的扩展,便于我们更好得使用JS写代码的加强版 DOM API。

    相关文章

      网友评论

          本文标题:模拟 jQuery API的实现

          本文链接:https://www.haomeiwen.com/subject/lvdsjqtx.html