美文网首页前端学习笔记
实现一个 jQuery 的 API

实现一个 jQuery 的 API

作者: _ClariS_ | 来源:发表于2019-08-05 08:15 被阅读3次

    HTML 如下:

    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="utf-8">
      <title>JS Bin</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>
      </ul>
    </body>
    </html>
    

    首先封装两个函数

    // 获取除自身以外的所有兄弟节点
    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.length] = allChildren[i] // 这里不用 array[i],因为 i !== 3,伪数组下标会产生间隔
          array.length += 1
        }
      }
      return array
    }
    console.log(getSiblings(item3)) // {0: li#item1, 1: li#item2, 2: li#item4, 3: li#item5, length: 4}
    
    // 给一个节点同时加上多个类名
    function addClass(node, classes) {
      classes.forEach((value) => node.classList.add(value))
    }
    addClass(item3, ['a', 'b', 'c'])
    

    命名空间

    给这两个函数分别命名,避免出现同名函数互相覆盖的情况

    window.zydom = {} // 声明一个全局对象
    zydom.getSiblings = function(node) { // zydom.getSiblings 表示全局对象 zydom 中有一个 getSiblings 属性,然后声明一个函数并赋值给它
      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
    }
    zydom.addClass = function(node, classes) { // zydom.addClass 表示全局对象 zydom 中有一个 addClass 属性,然后声明一个函数并赋值给它
      classes.forEach((value) => node.classList.add(value))
    }
    // 调用函数
    zydom.getSiblings(item3)
    zydom.addClass(item3, ['a', 'b', 'c'])
    

    虽然避免了同名函数互相覆盖的情况,但我们觉得zydom.getSiblings(item3)调用函数的写法不够简洁,能不能写成item.getSiblings()这种形式呢?

    把 node 放在前面

    node.getSiblings()
    node.addClass()

    方法一:扩展 Node 接口
    直接在 Node.prototype 上加函数

    Node.prototype.getSiblings = function(){
      var allChildren = this.parentNode.children 
      var 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
    }
    Node.prototype.addClass = function (classes) {
      classes.forEach( (value) => this.classList.add(value) )
    }
    
    item3.getSiblings() // 相当于 this.getSiblings(),这里的 this 传入的就是 item3
    // 等价于 item3.getSiblings.call(item3)
    item3.addClass(['a','b','c'])
    // 等价于 item3.addClass.call(item3, [abc])
    

    同样地,直接在 Node.prototype 上加函数这种方法也会有同名函数相互覆盖的情况发生,因此采用下面的方法二

    方法二:新的接口 BetterNode「无侵入」
    给 node 一个新的名字 node2

    function Node2(node) {
      return {
        element: node,
        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) {
          classes.forEach((value) => node.classList.add(value))
        }
      }
    }
    let node =document.getElementById('item3')
    let node2 = Node2(node) // 函数 Node2() 接受一个 node ,然后返回一个新的对象给 node2
    node2.getSiblings() // 这里的 this 是 node2,因此 Node2 函数里不能用 this
    node2.addClass(['a','b','c'])
    

    把 Node2 改个名字吧

    function jQuery(node) {
      return {
        element: node,
        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) {
          classes.forEach((value) => node.classList.add(value))
        }
      }
    }
    let node =document.getElementById('item3')
    let node2 = jQuery(node)
    console.log(node2.getSiblings())
    node2.addClass(['a','b','c'])
    

    再给它一个缩写 alias

    改进一:改掉 document.getElementById
    改进二:可以接收多个 node
    改进三:添加 .text()

    window.$ = jQuery

    window.jQuery = function(nodeOrSelector) {
      let nodes = {} // 伪数组
      /*做类型检测*/
      if (typeof nodeOrSelector === 'string') {
        let temp = document.querySelectorAll(nodeOrSelector) // 这里使用临时变量 temp 是为了不让 nodes 返回 NodeList,而是直接返回一个 Object
        for (let i = 0; i < temp.length; i++) {
          nodes[i] = temp[i]
        }
        nodes.length = temp.length
      } else if (nodeOrSelector instanceof Node) {
        nodes = {
          0: nodeOrSelector, // nodes[0] 就表示这个节点
          length: 1
        }
      } // 不管接收的是一个节点还是多个节点都返回一个伪数组
      nodes.addClass = function(classes) {
        classes.forEach((value) => {
          for (let i = 0; i < nodes.length; i++) {
            nodes[i].classList.add(value)
          }
        })
      }
      nodes.text = function(text) {
        if (text === undefined) { // getText
          var texts = [] // 数组
          for (let i = 0; i < nodes.length; i++) {
            texts.push(nodes[i].textContent)
          }
        } else { // setText
          for (let i = 0; i < nodes.length; i++) {
            nodes[i].textContent = text
          }
        }
      }
      return nodes // 返回一个伪数组 {0: li, 1: li, length: 5, addClass: f, text: f}
    }
    window.$ = jQuery
    var $nodes = $('ul > li')
    $nodes.addClass(['red'])
    $nodes.text('hi')
    

    http://js.jirengu.com/qekomedetu/1/edit?html,js,output

    总结

    jQuery 实质上是一个构造函数,它接受一个参数(可能是节点、选择器、字符串),然后返回一个新的对象(API)去操作节点。

    相关文章

      网友评论

        本文标题:实现一个 jQuery 的 API

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