实现jQuery中的两个API

2018-03-02  本文已影响0人  WWWKY

本文记录手写实现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实际上就是一个构造函数,接受一个参数,参数可以是一个节点也可以是一个选择器,然后返回一个方法对象去操作节点或选择器选中的节点。

上一篇下一篇

猜你喜欢

热点阅读