目录

一个优秀的项目经理与JQuery的故事

本文是对jQuery的起源的初步探索。先通过两个函数来扩展原生DOM的操作,然后引入命名空间以及对其重构,接着将该命名空间扩大到Node上,改造一个自己的Node2,引出jQuery。

引子

<!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>
    <li id="item6">选项6</li>
  </ul>
</body>
</html>

此时你刚学完原生DOM操作,知道有nextSibling previousSibling parentNode。你发现貌似没有直接一下子获得全部兄弟元素的API啊,身为一个优秀的90后,你果断手写一个函数实现这个需求啊。

function getSiblings(node){
  var allChild = item2.parentNode.children 
  var childObj = {length: 0}
  for (let i = 0; i < allChild.length; i++){
    if (allChild[i] !== node){
       childObj[childObj.length] = allChild[i]
       childObj.length += 1
    }
  }
  return childObj
}

好了,以上的函数就能满足需求了,它接受你传入的某个元素,返回包含这个元素所有兄弟元素的伪数组。

注意: 要用item2.parentNode.children 这样子才不会获得文本节点。所以你想获得item2的所有兄弟,只需要getSiblings(item2)

获得所有兄弟的演示地址============>demo

领导还没说完,你立马想到了,直接item2.classList.add('类名')啊,哈哈,我好聪明啊,不愧是优秀的90后。
给你任意一个元素要直接加上这个类名,别给我的一个一个的加,太二了,如果元素原来有一个不应该存在的类名,给我删了,领导接着说完全部的需求。
这...看来不能item1.classList.add('类名') item2.classList.add('类名') item3.classList.add('类名')这么弱智的干了啊,那我还用函数嘛,你灵机一动。
嗯,不愧是善于思考的90后

function addClass(node, classes){
  for (var key in classes){
    var value = classes[key]
    if (value){
      node.classList.add(key)
    } else{
      node.classList.remove(key)
    }
  }
}

上图是为添加元素的时候的item2的模样,记住它,待会和下图对比。

可以看到,执行方法后,item2的类名变为b、c,这是因为你是addClass(item2, {a: 0, b: 1, c: true})这么调用的,意思是类名不应该有a,删除a,并加上b c


以上对象的遍历并取值用到了falsey

复习一下,js的6个falsey

除此之外,其他的全是true。


不过你想的太美了,领导看到你的代码中的这个片段,直接抓狂了……

if (value){
      node.classList.add(key)
    } else{
      node.classList.remove(key)
    }
 }

这段代码给我优化了,明明就是一句话的事。

你回去想了一会,可以这么优化

var methodName = value ? 'add''remove'
node.classList[methodName](key)

最后你把如下代码提交。

function addClass(node, classes){
  for (var key in classes){
    var value = classes[key]
    var methodName = value ? 'add' : 'remove'
    node.classList[methodName](key)
  }
}

给任一元素添加类名==========================>demo

命名空间

你完成了上面的两个需求后,领导本着锻炼你的原则,又给你提了新的需求。

var shaolinDom = {} //少林开的超市
shaolinDom.addClass = addClass //把addClass这个商品收进来
shaolin.getSibling = getSiblings //把getSiblings这个商品收进来

那我咋用呢,该咋用就咋用呗。

shaolinDom.addClass(item5, {a: true, b: false, c: 0}) //把item5上原本的b c类名删掉,加上 a类名
shaolinDom.getSiblings(item6) //获得item6的所有兄弟元素

var shaolinDom = {}
shaolinDom.addClass = function(node, classes){
  for (var key in classes){
    var value = classes[key]
    var methodName = value ? 'add':'remove'
    node.classList[methodName](key)
  }
 
}
 
shaolinDom.getSiblings = function (node){
  var allChild = node.parentNode.children
  var childObj = {length: 0}
  for (let i = 0; i< allChild.length; i++){
    if (allChild[i] !== node){
      childObj[childObj.length] = allChild[i]
      childObj.length += 1
    }
  }
  return childObj
}
 
shaolinDom.addClass(item5, {a: true, b: false, c: 0})
var allSiblings = shaolinDom.getSiblings(item6)
console.log(allSiblings)

引入命名空间=======================>demo

命名空间的优化=====================>demo

Node.prototype.addClass = function(classes){
  for (var key in classes){
    var value = classes[key]
    var methodName = value ? 'add':'remove'
    this.classList[methodName](key)
  }
 
}
 
Node.prototype.getSiblings = function (){
  var allChild = this.parentNode.children
  var childObj = {length: 0}
  for (let i = 0; i< allChild.length; i++){
    if (allChild[i] !== this){
      childObj[childObj.length] = allChild[i]
      childObj.length += 1
    }
  }
  return childObj
}
item5.addClass({a: true, b: false, c: 0}) //既然Node原型都有了这两函数,item5是node类型,直接用呗
console.log(item6.getSiblings())
//上面的代码等同于以下代码
item5.addClass.call(item5, {a: true, b: false, c: 0}) //call()方法的第一个参数就是this
console.log(item6.getSiblings.call(item6))

进一步升级,绑定Node的原型链上==================>democall()方便理解this\================================>demo

没多久,领导的考验又来了

window.Node2 = function(node){
  return {
    getSiblings: function(){
      var allChild = node.parentNode.children
      var childObj = {length: 0}
      for (let i = 0; i< allChild.length; i++){
        if (allChild[i] !== node){
          childObj[childObj.length] = allChild[i]
          childObj.length += 1
        }
      }
      return childObj
    },
 
    addClass: function(classes){
      for (var key in classes){
        var value = classes[key]
        var methodName = value ? 'add':'remove'
        node.classList[methodName](key) //闭包的使用
      }
    }
  }
}
var node2 = Node2(item3) //node2就是用Node2()构造函数构造的返回的对象
node2.getSiblings() //对象的点运算符去去操作属性啊
node2.addClass({'a': 0, 'b': true, 'c': true})

自己实现一个构造函数去理解=======================>demo

jQuery的雏形

window.jQuery = function(node){
  return {
    getSiblings: function(){
      var allChild = node.parentNode.children
      var childObj = {length: 0}
      for (let i = 0; i< allChild.length; i++){
        if (allChild[i] !== node){
          childObj[childObj.length] = allChild[i]
          childObj.length += 1
        }
      }
      return childObj
    },
 
    addClass: function(classes){
      for (var key in classes){
        var value = classes[key]
        var methodName = value ? 'add':'remove'
        node.classList[methodName](key) //闭包的使用
      }
    }
  }
}
window.$ = jQuery
window.JQuery = function(nodeOrSelector){
  let node
  //判断一下nodeOrSelector是node还是一个选择器
  if(typeof nodeOrSelector === 'string'){
    node = document.querySelector(nodeOrSelector)
  } else{
    node = nodeOrSelector
  }
 
  return {  
    getSiblings: function(){
      var allChild = node.parentNode.children
      var childObj = {length: 0}
      for (let i = 0; i< allChild.length; i++){
        if (allChild[i] !== node){
          childObj[childObj.length] = allChild[i]
          childObj.length += 1
        }
      }
      return childObj
    },
 
    addClass: function(classes){
      for (var key in classes){
        var value = classes[key]
        var methodName = value ? 'add':'remove'
        node.classList[methodName](key)
      }
    }
  }
}

所以

//var node2 = JQuery('#item3')与下列代码作用相同,把item3变红
var node2 = JQuery('ul > li:nth-child(3)')

jQuery的雏形======================>demo

window.JQuery = function(nodeOrSelector){
  let nodes = {}
  if(typeof nodeOrSelector === 'string'){
    let temp = document.querySelectorAll(nodeOrSelector) //NodeList
    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)
      }
    })
 
  }
 
  //等同于get、set方法
  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
}

控制多个<li>的内容================================>demo

最终,少林在经理的循循善诱下,开始探索jQuery的道路。虽然jQuery使用量在下降,但是依然有60\%的web开发人员在用。

以上并不是完全真实的jQuery的推导,只是大约是那个意思,可以帮助我更好的理解而已。真正的JQuery必须去看文档,英文文档中文文档

总之,jQuery我来啦\~去探索真正强大的jQuery吧,去理解write less, do more的含义吧,去体会一句顶一万句的力量吧。

最后安利一波刘震云的小说一句顶一万句

饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com

本文作者:饥人谷方应杭老师