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

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

引子

  • 首先,我有一个需求===========> 要获得一个<li>标签的所有兄弟元素。
<!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

  • 你刚解决了一个问题,领导又给你提了一个需求,让你给<li>添加一个类

领导还没说完,你立马想到了,直接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

  • 0
  • NaN
  • ''
  • null
  • undefined
  • false

除此之外,其他的全是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)
  }
}
  • 注意一点上述代码不能用点运算符,要用[]运算符,classList['add'] === classList.add

给任一元素添加类名==========================>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

  • 你提交了新的需求后,领导对你刮目相看啊,今年的新员工还是不错的
  • 你刚想松口气,领导接着说,少林那,你看你这两函数啊只能在shaolinDom用啊,而且我每次要把item5传到函数里面,每次好麻烦的啦,你改进一下,让我的元素可以直接调用方法呗,比如item5.getSiblings()这样多好。这样子操作的话,item5拥有自主权,就像你买东西,你想去买那个东西你就去买那个东西嘛,而不是东西去选择你啊。
  • 我擦擦嘞,想想经理分析还是很有道理的,果然还是要继续优化啊……
  • 回来会,我在工位上想,你不是要操作DOM吗,还想这么item5.getSiblings()操作,那我这次直接给你干到Node的原型上
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
}
  • 丫的,我写出这些代码之后,我瞬间感觉自己两米了呢,赶紧去找领导
  • 领导一看,吸了一口冷气,心想,挺牛 啊,不过我要问问我这小子 this的知识
  • 少林那,你这函数里面this是啥,再给我讲讲咋用呗
  • 我屮艸芔茻,轮到我牛了吧,我把袖子已撸,是这么回事,巴拉巴拉
  • 当然,我要先给经理讲一下怎么用
item5.addClass({a: true, b: false, c: 0}) //既然Node原型都有了这两函数,item5是node类型,直接用呗
console.log(item6.getSiblings())

  • 我刚写完,领导就叫,这addClass函数里面这就一个参数啊,getSiblings函数怎么没参数啊
  • 哦,这个啊,我讲了this后就明白了。不过讲这个this之前先要讲一讲这个call(),方便理解
//上面的代码等同于以下代码
item5.addClass.call(item5, {a: true, b: false, c: 0}) //call()方法的第一个参数就是this
console.log(item6.getSiblings.call(item6))
  • 如果你把call()省了,直接用()去调用函数,自己脑补call()就好啦,自然也就知道this是谁啦。
  • 这小子还可以啊,不错。不过要继续引导一下啊

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

自己写一个构造函数

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

  • 少林那,你看你上次吧你自己写的函数绑到Node上了,看似挺好,但是其他人不一定用你的这两函数啊,你绑到Node上,多占地啊。你自己写一个全局函数实现一下相同的需求吧。
  • 呦,这次经理说的很对啊,我的错,我改进一下
  • 突然联想到以前的各种构造函数,String() Number() Array()可以直接返回一个对象,我也这么干吧
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) //闭包的使用
      }
    }
  }
}
  • 实现了一个全局构造函数,返回一个对象,该对象里面有两个key,value分别又是两个函数(又体现了函数是第一公墓的地位),而且还用到了闭包。
var node2 = Node2(item3) //node2就是用Node2()构造函数构造的返回的对象
node2.getSiblings() //对象的点运算符去去操作属性啊
node2.addClass({'a': 0, 'b': true, 'c': true})
  • 领导一看,嗯,是时候让他见识真正的jQuery

自己实现一个构造函数去理解=======================>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) //闭包的使用
      }
    }
  }
}
  • 你看看我改了一个位置,你看着这像啥
  • 我看了一会这难道是传说中的jQuery
  • 这是它的雏形,大概意思你已经一步一步写出来了,jQuey就是一个构造函数,它返回一个对象,这个对象有很多key,对应的value又是一些函数。
  • 那怎么还用$这个操作呢
  • 哈哈,一个语法糖吗,你看
window.$ = jQuery
  • 给你出个题吧,你用现在的知识用jQuery实现把某个元素变红,最好验证一下,你的参数是node还是一个选择器,提示一下,可以用querySelector()querySelector会返回文档中匹配指定的选择器组的第一个元素
  • 我想了一会,写出如下代码
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

使用一下自己的jQuery

  • 少林那,你也会用初级的jQuery了,咱们练习一下,你把修改一下所有的<li>的内容吧,可以使用querySelectorAll() 返回一个NodeList的伪数组
  • 我听完之后,思考了一会,写下了如下代码
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的道路。虽然使用量在下降,但是依然有60%的web开发人员在用。

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

jQuery我来啦~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • jQuery是一套跨浏览器的JavaScript库,简化HTML与JavaScript之间的操作。由约翰·雷西格(...
    静候那一米阳光阅读 781评论 0 18
  • 1.JQuery 基础 改变web开发人员创造搞交互性界面的方式。设计者无需花费时间纠缠JS复杂的高级特性。 1....
    LaBaby_阅读 1,167评论 0 1
  • 1.JQuery 基础 改变web开发人员创造搞交互性界面的方式。设计者无需花费时间纠缠JS复杂的高级特性。 1....
    LaBaby_阅读 1,330评论 0 2
  • 警告请使用 document.write() 仅仅向文档输出写内容。如果在文档已完成加载后执行 document....
    hx永恒之恋阅读 2,844评论 3 104
  • Canvas的drawText绘制文本自动换行(支持设置显示最大行数) 使用Canvas的drawText绘制文本...
    三也视界阅读 9,405评论 0 12