美文网首页
实现jQuery中的两个API

实现jQuery中的两个API

作者: WWWKY | 来源:发表于2018-03-02 09:41 被阅读0次

    本文记录手写实现jQuery的两个API-getSiblings及addClass的思路及过程

    1. 使用原生JS实现getSiblings和addClass的功能

    <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>
    </body>
    

    实现getSiblings功能

    //获取item3的兄弟元素
    let allChildren = item3.parentNode.children 
    let array = {length: 0}
    for(let i = 0; i < allChildren.length; i++){
      if(allChildren[i] !== item3){
        array[array.length] = allChildren[i]
        array.length += 1
      }
    }
    console.log(array)
    // {0: li#item1, 1: li#item2, 2: li#item4, 3: li#item5, 4: li#item6, length: 5}
    // 0:li#item1
    // 1:li#item2
    // 2:li#item4
    // 3:li#item5
    // 4:li#item6
    // length:5
    // __proto__:Object
    

    实现addClass功能

    let classes = {'a': true, 'b': false, 'c': true} // 用一个哈希表示class是否存在
    for(let key in classes){
      let value = classes[key]
      // 给item3添加或移除class
      if(value){
        item3.classList.add(key)
      }else{
        item3.classList.remove(key)
      }
    }
    

    2. 封装原生代码

    封装getSiblings函数

    function getSiblings(node){
      let allChildren = node.parentNode.children 
      let 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
    }
    getSiblings(item3)
    

    封装addClass函数

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

    3. 命名空间

    
    function getSiblings(node){
      let allChildren = node.parentNode.children 
      let 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
    }
    
    function addClass(node, classes){
      for(let key in classes){
        let value = classes[key]
        let methodName = value ? 'add' : 'remove'
          node.classList[methodName](key)
      }
    }
    
    window.newDOM = {}
    newDOM.getSiblings = getSiblings
    newDOM.addClass = addClass
    
    newDOM.getSiblings(item3)
    newDOM.addClass(item3, {'a': true, 'b': false, 'c': true})
    

    4. 改写

    方法1:修改Node原型

    Node.prototype.getSiblings = function(){
      let allChildren = this.parentNode.children
      let array = {length: 0}
      for(let i = 0; i < allChildren.length; i++){
        if(allChildren[i] !== this){
          array[array.length] = allChildren[i]
          array.length += 1
        }
      }
      return array
    }
    
    item3.getSiblings()
    

    为Node.prototype添加getSiblings方法,在函数内使用this获取指定的item,addClass方法同上

    Node.prototype.addClass = function(classes){
      for(let key in classes){
        let value = classes[key]
        let methodName = value ? 'add' : 'remove'
          this.classList[methodName](key)
      }
    }
    item3.addClass({'a': true, 'b': false})
    

    5. 避免覆盖相同名称的已有方法

    如上文所述,我们已经对getSiblings方法和addClass方法进行了封装并且修改了Node的原型,但是为了避免Node原型中已经存在相同名称的方法,我们重写一个Node2原型

    window.Node2 = function(node){
      return {
        getSiblings: function(){
          let allChildren = node.parentNode.children 
          let 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(let key in classes){
            let value = classes[key]
            let methodName = value ? 'add' : 'remove'
            node.classList[methodName](key)
          }
        }
      }
    }
    
    var node2 = Node2(item3)
    node2.getSiblings()
    node2.addClass({'a': true, 'b': false, 'c': true})
    

    需要注意的是,我们在改写的过程中在函数内使用了this来获取item3,但在重写出的Node2中我们声明了一个变量使用Node2构造函数,并把item3作为参数传入,因此函数内只需使用参数node。

    6. 添加选择器功能

    上文中的Node2在使用的过程中只能接受用户传入一个节点,因此需要对Node2进行改进

    window.jQuery = function(nodeOrSelector){
      let node
      if(typeof nodeOrSelector === 'string'){
        node = document.querySelector(nodeOrSelector)
      }else{
        node = nodeOrSelector
      }
      return {
        getSiblings: function(){
          let allChildren = node.parentNode.children 
          let 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(let key in classes){
            let value = classes[key]
            let methodName = value ? 'add' : 'remove'
            node.classList[methodName](key)
          }
        }
      }
    }
    
    var nodeItem = jQuery('#item3')
    nodeItem.getSiblings()
    nodeItem.addClass({'a': true, 'b': false, 'c': true})
    

    改进过程中首先函数的名称进行了修改,并不影响功能的实现,然后给函数添加判断条件,使得用户传入的节点及选择器都可以使用,在这个过程中node和getSiblings函数、addClass函数构成了闭包。

    7. 多个选择器

    上文中的选择器只能选择一个节点,如果用户想要一次选择多个节点,则需要继续进行改进

    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){
        nodes = {0: nodeOrSelector, length: 1}
      }
      nodes.addClass = function(classes){
        for(let key in classes){
          let value = classes[key]
          let methodName = value ? 'add' : 'remove'
          for(let i = 0; i < nodes.length; i++){
            nodes[i].classList[methodName](key)
          }
        }
      }
      nodes.text = function(text){
        if(text === undefined){
          let texts = []
          for(let i = 0; i < nodes.length; i++){
            texts.push(nodes[i].textContent)
          }
          return texts
        }else{
          for(let i = 0; i < nodes.length; i++){
            nodes[i].textContent = text
          }
        }
      }
      return nodes
    }
    
    var nodeItem = jQuery('ul > li')
    nodeItem.addClass({'a': true, 'b': false, 'c': true})
    console.log(nodeItem.text())
    nodeItem.text('hi')
    

    本次修改允许用户通过 ul > li 选择多个节点,返回的nodes为一个伪数组,同时添加一个新的text()方法,在text()方法的参数为空时,获取节点的文本;在text()方法的参数不为空时,设置所有选中节点的text

    8. 总结

    本文实现的jQuery实际上就是一个构造函数,接受一个参数,参数可以是一个节点也可以是一个选择器,然后返回一个方法对象去操作节点或选择器选中的节点。

    相关文章

      网友评论

          本文标题:实现jQuery中的两个API

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