jQuery

216 阅读7分钟

本节要点:

千万不要学jQuery Mobile,因为它跟jQuery没啥关系,已经过时了。

  1. 本节首先实现一个类似jQuery的API,来帮助理解什么是jQuery。
  2. 看jQuery的API文档。
  3. 建议先阅读阮一峰的jQuery设计思想
    此处引用部分内容:
  • jQuery的基本设计思想和主要用法,就是"选择某个网页元素,然后对其进行某种操作"。这是它区别于其他Javascript库的根本特点。
  • jQuery设计思想之二,就是提供各种强大的过滤器,对结果集进行筛选,缩小选择结果。
  • jQuery设计思想之三,就是最终选中网页元素以后,可以对它进行一系列操作,并且所有操作可以连接在一起,以链条的形式写出来。
  • jQuery设计思想之四,就是使用同一个函数,来完成取值(getter)和赋值(setter),即"取值器"与"赋值器"合一。到底是取值还是赋值,由函数的参数决定。
  • jQuery设计思想之五,就是提供两组方法,来操作元素在网页中的位置移动。一组方法是直接移动该元素,另一组方法是移动其他元素,使得目标元素达到我们想要的位置。
  • jQuery设计思想之六:除了对选中的元素进行操作以外,还提供一些与元素无关的工具方法(utility)。不必选中元素,就可以直接使用这些方法。
  • jQuery设计思想之七,就是把事件直接绑定在网页元素之上。

一、写一个简化版的jQuery


封装两个函数

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>
JS:
/* getSiblings: 获得node 的 兄弟姐妹们 */
function getSiblings(node){ /* API */
  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: 给node添加class */
function addClass(node, classes){
  for(let key in classes){
    var value = classes[key]
    if(value){
      node.classList.add(key)
    }else{
      node.classList.remove(key)
    }
  }
}

/* 获得item3的兄弟姐妹们 */
console.log(getSiblings(item3))
/* 给item2添加class="a c" /
addClass(item2, {'a':true,'b':false,'c':true})
效果如下

控制台:
{0: li#item1, 1: li#item2, 2: li#item4, 3: li#item5, length: 4}
0: li#item1
1: li#item2
2: li#item4
3: li#item5
length: 4
__proto__: Object

html:

<li id="item2" class="a c">选项2</li>

代码优化准则一:如果出现类似的代码,就有优化的可能

JS:
/* addClass: 给node添加class */
function addClass(node, classes){
  for(let key in classes){
    var value = classes[key]
    /* 优化: obj.fn === obj[fn]*/
    var methodName = value ? 'add' : 'remove'
    node.classList[methodName]
  }
}

命名空间

曾经这种方法很流行,现在已经过时了。

命名空间就是把一些函数放在同一个空间里,好处是可以区分不同命名空间的同名API,使它们不会冲突。
比如document就是一个命名空间,document里有很多API,这些API统一用document.xxx形式调用,如果是yyy.xxx则调用的是别的命名空间的API。
更多信息看命名空间

JS:
window.omdom = {} /* omdom只是一个名字,可以随意起 */

ondom.getSiblings = function (node){ /* API */
  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,只保留了添加class功能*/
omdom.addClass = function (node, classes){
  classes.forEach((value) => node.classList.add(value) )
}


omdom.getSiblings(item1)
omdom.addClass(item2, ['a','b','c'])

改进一:node.xxx()

上面命名空间的调用方法仍然有些麻烦,我们希望能够是node.getSiblings() , node.addClass()这种形式。有两种方法:

  1. 扩展Node接口
    我们可以直接在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) )
}

/* 以下两种方法表示,帮助理解this */


/* 隐式this */
item2.getSiblings()
item3.addClass(['a','b','c'])

/* 显式this */
item2.getSiblings.call(item2)
item3.addClass.call(item3, ['a','b','c'])

但是使用这种方法,如果多人一起给Node.prototype添加接口呢?互相又不知道对方加了什么,很可能就互相覆盖了,所以我们要用另一种方法!

  1. 自己写一个新的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) {
    classes.forEach((value) => node.classList.add(value) )
    }
  }
}


var node2 = Node2(item3)
node2.getSiblings()
node2.addClass(['a', 'b', 'c'])

第二种方法就是 无侵入


那上面的这些操作跟jQuery有什么关系呢?

现在把上述代码的Node2改成jQuery:只是个函数名字的变化

window.jQuery = function(node){
    ...
}

var node2 = jQuery(item3)
node2.getSiblings()
node2.addClass(['a', 'b', 'c'])

由此,我们就可以理解jQuery到底在干什么:

jQuery就是一个升级的DOM,它接受一个旧的节点,返回一个新的对象,这个对象有新的API如getSiblings、addClass。这些新的API的实现是在jQuery内部,我们写代码只需要去调用即可。

更简单的说,jQuery就是一个构造函数,接受一个参数,这个参数可能是节点,然后返回一个方法对象去操作这个节点。

实际上的jQuery比上述的更加强大,比如我们不只是接受一个节点,还可以接受一个字符串:

CSS:
.red{
    color: red;
}
JS:
window.jQuery = function(nodeOrSelector){
 let node
 if(typeof nodeOrSelector === 'string'){
     node = document.querySelctor(nodeOrSelector)
 }lese{
     node = nodeOrSelector
 }
    ...
}

var node2 = jQuery('#item3')
// var node2 = jQuery('ul > li:nth-child(3)')
node2.getSiblings()
node2.addClass(['red'])
效果:选项3 变为 红色 。

改进二: 接受多个node

JS:
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 = { // nodes可能是多个,也可能是一个,为了统一,都定义成伪数组
        0: nodeOrSelector,
        length: 1
      }
    }

  nodes.addClass = function(classes){
    classes.forEach((value) => {
      for(let i=0; i<nodes.length; i++){
        nodes[i].classList.add(value)
      }
    } )
  }  
  return nodes
}

var node2 = jQuery('ul > li')
node2.addClass(['red'])

接受的nodeOrSelector经过if - else if 判断,获得一个伪数组,然后对这个伪数组进行操作。


改进三:添加几个jQuery的API

比如获取节点的文本内容或者设置节点的文本内容。

这里先插一句。jQuery不喜欢写get、set,所以jQuery把以上两个功能合并在一起,如果没有传递参数就是获得节点的文本内容,如果传递参数就是设置节点的文本内容。代码如下:

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){
    classes.forEach((value) => {
      for(let i=0; i<nodes.length; i++){
        nodes[i].classList.add(value)
      }
    } )
  }
  
  // 以下为新添加内容 以下为新添加内容 以下为新添加内容  以下为新添加内容  以下为新添加内容
  
  nodes.text = function(text){
      if(text ===  undefined){
        var 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 node2 = jQuery('ul > li')
node2.addClass(['red'])
console.log(node2.text())
node2.text('hi')

解释说明:

  1. 你可以给我一个选择器,也可以是一个节点——nodeOrSelector,然后我判断nodeOrSelector是一个节点还是多个节点并将结果以伪数组的形式放在nodes里。然后你可以调用我写的API。
    这里的“我”,就是本文写的简化版jQuery。
  2. 调用API有两种方法,文中一直用的是闭包的方法(闭包:函数+函数外定义的变量就叫闭包)。实际上,node2就是nodes,如果你能理解这一点,就可以直接使用node[0].classList.add('red')来直接给第一个li添加red类。node[0]就是nodes的第一项。

jQuery API

这里给大家一个网站——www.jquery123.com/,里面有jQuery的所有API。

主要还是看得懂我给的网站内容就行,那上面有例子。

总结

  1. jQuery 在兼容性方面做得很好,1.7 版本兼容到 IE 6
  2. jQuery 还有动画、AJAX 等模块,不止 DOM 操作
  3. jQuery 的功能更丰富
  4. jQuery 使用了 prototype,因为还涉及到了 new ,我目前还不会,以后再补充吧。

补充:

  1. window.$ = jQuery,一般用$符号代替jQuery,不过也可能存在$被占用的情况,这种情况下我们只能老老实实的使用jQuery了。
  2. 建议一个小习惯:如果是jQuery构造出来的对象,就在对象名前加上一个$,来标记这个对象是jQuery构造的,这样在使用的过程中不会造成混淆。例如:var $nodes = $('ul > li')
  3. 再补一遍 什么是jQuery?
    jQuery就是一个升级的DOM,它接受一个旧的节点,返回一个新的对象,这个对象有新的API如getSiblings、addClass。这些新的API的实现是在jQuery内部,我们写代码只需要去调用即可。
    更简单的说,jQuery就是一个构造函数,接受一个参数,这个参数可能是节点,然后返回一个方法对象去操作这个节点。