jQuery初探:自制jQuery

前言

为什么要学jQuery呢?因为在学习原生js时候,有许多DOM操作冗长复杂,而jQuery却可以使之简单化,所以jQuery也曾被众多程序员喜爱。那么我们该怎么去学习jQuery呢?所谓知己知彼百战百胜,我们可以尝试自制jQuery式的API来了解其原理,在完成这个API后我们就能非常容易的使用jQuery。


一、封装函数

<!--html代码-->
<body>
  <h1>text</h1>
  <ul>
    <li id="item1">选项1</li>
    <li id="item2">选项2</li>
    <li id="item3">选项3</li>
    </ul>
</body>

让我们来添加以下需求:

  • 获取到一个节点的所有兄弟姐妹

以item2为例,为了获取其所有兄弟姐妹,我们可以使用DOM API 中的ParentNode.children属性。但这有一个问题,我们找到节点父亲后返回的是其所有孩子,为了得到除item2外的所有节点,我们必须创建一个空对象,把节点父亲返回的所有孩子组成的伪数组遍历后通过条件筛选,将不等于自身节点的其他节点放入此空对象里。

//javaScript代码
function getSiblings(node) {
  var allChlidren = node.parentNode.chlidren
  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
}
//调用方法
getSiblings(item2)

以上代码中,我们创建的array是一个伪数组,其并没有数组的push方法,那么怎么才能把value放进数组呢?所以使用array[array.length]=allChildren[i]的方法(不能放i,因为条件不符合时,会有i跳过了)


  • 给节点添加多个类名

我们可以创建一个classes伪数组,通过遍历后,根据判断其输入的属性值所对应的布尔值来决定使用DOM API中的node.classList.add或者remove来一次性增删该节点的多个类名。

//javaScript代码
function addClass(node,classes){
  for(let key in classes){
    var value = classes[key]
    var methodName = value? 'add' : 'remove' 
    node.classList[methodName](key)
  }
}
// 调用addClass()
addClass(item1,{ a:ture , b:false})

二、添加命名空间

在满足以上两点需求后,我们要是想让别人也来使用我们这两个函数方法,该怎么办呢?解决办法就是给它添加命名空间。

//javaScript代码
window.mmmdom = {}
mmmdom.getSiblings = function(node) {
  var allChlidren = node.parentNode.chlidren
  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
}
mmmdom.addClass = function(node,classes){
  for(let key in classes){
    var value = classes[key]
    var methodName = value? 'add' : 'remove' 
    node.classList[methodName](key)
  }
}
// 调用方法
mmmdom.getSiblings(item3)
mmmdom.addClass( item3,{a:false , b:true })

给它命名空间还有一个好处就是可以避免不知不觉的把全局变量给覆盖。


三、扩展Node原型/无侵入设置

以上我们已经满足了需求而且还可以让别人来使用,但是还有一个问题就是上面的函数调用方法都是textdom.addClass()这样的,但是按照我们的习惯,我们更喜欢使用item2.getClass('red')这样的调用,接下来我们就来实现这一点吧。

1.扩展node接口
我们可以直接在 Node.prototype 上添加这个函数,这样我们的节点就可以继承到这个函数方法了。

Node.prototype.getSiblings =function ( ) {
  var allChlidren = this.parentNode.chlidren
  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.call(items) 
//等价于item3.getSiblings()
item3.addClass.call( item1,['text', 'red', 'blue'])
//等价于item3.addClass( ['text', 'red', 'blue'])

特别注意:f.call ( asThis , input1 , input2 )
其中this是call()的第一个参数,即asThis会被当作this,而[ input1 ,input2]会被当作arguments

但是这样的缺点是,当我们调用时,若已声明过这个函数,那该函数就会被覆盖了。

2.构造新的接口,即无侵入

//javaScript
window.jQuery =  function(node){
  return{
    getSiblings : function(){
      var allChlidren = this.parentNode.chlidren
      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
    },
    addClass : function(classes){
      classes.forEach( 
        (value) => this.classList.add(value) 
      )
    } 
  }
}
var item3 = jQuery(item3)
//实现调用
item3.getSiblings()
item3.addClass([' red '])

我们还可以优化window.$ = jQuery
小tips:当一个对象是由 $或jQuery构造出来的,你就在这个对象前也加上$,表示他是jQuery的对象,这样就不会错用成dom api了。即 var $node2 = $(node)。


四、传入非节点

我们以上所有操作都是针对节点,那么当传入参数是非节点时该怎么处理呢?我们应该先对传入参数进行判断:如果传入的是字符串,就用document.querySelectorAll()方法来选择该节点;如果传入的是节点,我们也把它放入到伪数组中,这是因为querySelectorAll()返回的是伪数组,我们要保持一致,后续才能使用同样操作把两个方法添加上去。

window.jQuery =  function( nodeOrSelector ){
    let nodes = {}
    if( typeof nodeOrSelector === 'string' ){
        nodes = document.querySelectorAll(nodeOrSelector)
    }else if (nodeOrSelector instanceof Node){
        nodes = {
        0:nodeSelector , 
        length:1
        }
    }
    nodes.addClass = function (classes) {
        classes.forEach((value) => {
            for( let i=0 ; i< nodes.length ; i++ ){
                nodes[i].classList.add(value)
            }
        })
    } 
    return nodes
}

这样,我们就大致理解了jQuery的基本原理了,当然jQuery的作用不仅仅于此,后续实践会让我们了解更多的。


五、一道面试题

<div id=x></div>
var div = document.getElementById('x');
var $div = $('#x');
请说出 div 和 $div 的联系和区别。

div 和 $div 的联系是:
$(div) 可以将 div 封装成一个 jQuery 对象,就跟 $div 一样
$div[0] === div ,$div 的第一项就是 div
div 和 $div 的区别是:
div 的属性和方法有 childNodes firstChid nodeType 等
$div 的 属性和方法有 addClass removeClass toggleClass 等

相关推荐