实现jQuery中的两个API

本文记录手写实现jQuery的两个API-getSiblings及addClass的思路及过程

1. 使用原生JS实现getSiblings和addClass的功能

<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>

实现getSiblings功能

//获取item3的兄弟元素
let allChildren = item3.parentNode.children 
let array = {length: 0}
for(let i = 0; i < allChildren.length; i++){
  if(allChildren[i] !== item3){
    array[array.length] = allChildren[i]
    array.length += 1
  }
}
console.log(array)
// {0: li#item1, 1: li#item2, 2: li#item4, 3: li#item5, 4: li#item6, length: 5}
// 0:li#item1
// 1:li#item2
// 2:li#item4
// 3:li#item5
// 4:li#item6
// length:5
// __proto__:Object

实现addClass功能

let classes = {'a': true, 'b': false, 'c': true} // 用一个哈希表示class是否存在
for(let key in classes){
  let value = classes[key]
  // 给item3添加或移除class
  if(value){
    item3.classList.add(key)
  }else{
    item3.classList.remove(key)
  }
}

2. 封装原生代码

封装getSiblings函数

function getSiblings(node){
  let allChildren = node.parentNode.children 
  let 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(item3)

封装addClass函数

function addClass(node, classes){
  for(let key in classes){
    let value = classes[key]
    let methodName = value ? 'add' : 'remove'
      node.classList[methodName](key)
  }
}
addClass(item3, {'a': true, 'b': false, 'c': true})

3. 命名空间


function getSiblings(node){
  let allChildren = node.parentNode.children 
  let 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
}

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

window.newDOM = {}
newDOM.getSiblings = getSiblings
newDOM.addClass = addClass

newDOM.getSiblings(item3)
newDOM.addClass(item3, {'a': true, 'b': false, 'c': true})

4. 改写

方法1:修改Node原型

Node.prototype.getSiblings = function(){
  let allChildren = this.parentNode.children
  let 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
}

item3.getSiblings()

为Node.prototype添加getSiblings方法,在函数内使用this获取指定的item,addClass方法同上

Node.prototype.addClass = function(classes){
  for(let key in classes){
    let value = classes[key]
    let methodName = value ? 'add' : 'remove'
      this.classList[methodName](key)
  }
}
item3.addClass({'a': true, 'b': false})

5. 避免覆盖相同名称的已有方法

如上文所述,我们已经对getSiblings方法和addClass方法进行了封装并且修改了Node的原型,但是为了避免Node原型中已经存在相同名称的方法,我们重写一个Node2原型

window.Node2 = function(node){
  return {
    getSiblings: function(){
      let allChildren = node.parentNode.children 
      let 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){
      for(let key in classes){
        let value = classes[key]
        let methodName = value ? 'add' : 'remove'
        node.classList[methodName](key)
      }
    }
  }
}

var node2 = Node2(item3)
node2.getSiblings()
node2.addClass({'a': true, 'b': false, 'c': true})

需要注意的是,我们在改写的过程中在函数内使用了this来获取item3,但在重写出的Node2中我们声明了一个变量使用Node2构造函数,并把item3作为参数传入,因此函数内只需使用参数node。

6. 添加选择器功能

上文中的Node2在使用的过程中只能接受用户传入一个节点,因此需要对Node2进行改进

window.jQuery = function(nodeOrSelector){
  let node
  if(typeof nodeOrSelector === 'string'){
    node = document.querySelector(nodeOrSelector)
  }else{
    node = nodeOrSelector
  }
  return {
    getSiblings: function(){
      let allChildren = node.parentNode.children 
      let 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){
      for(let key in classes){
        let value = classes[key]
        let methodName = value ? 'add' : 'remove'
        node.classList[methodName](key)
      }
    }
  }
}

var nodeItem = jQuery('#item3')
nodeItem.getSiblings()
nodeItem.addClass({'a': true, 'b': false, 'c': true})

改进过程中首先函数的名称进行了修改,并不影响功能的实现,然后给函数添加判断条件,使得用户传入的节点及选择器都可以使用,在这个过程中node和getSiblings函数、addClass函数构成了闭包。

7. 多个选择器

上文中的选择器只能选择一个节点,如果用户想要一次选择多个节点,则需要继续进行改进

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){
    for(let key in classes){
      let value = classes[key]
      let methodName = value ? 'add' : 'remove'
      for(let i = 0; i < nodes.length; i++){
        nodes[i].classList[methodName](key)
      }
    }
  }
  nodes.text = function(text){
    if(text === undefined){
      let 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 nodeItem = jQuery('ul > li')
nodeItem.addClass({'a': true, 'b': false, 'c': true})
console.log(nodeItem.text())
nodeItem.text('hi')

本次修改允许用户通过 ul > li 选择多个节点,返回的nodes为一个伪数组,同时添加一个新的text()方法,在text()方法的参数为空时,获取节点的文本;在text()方法的参数不为空时,设置所有选中节点的text

8. 总结

本文实现的jQuery实际上就是一个构造函数,接受一个参数,参数可以是一个节点也可以是一个选择器,然后返回一个方法对象去操作节点或选择器选中的节点。

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

推荐阅读更多精彩内容

  • 除了使用new操作符之外,还有更多制造对象的方法。你讲了解到实例化这个活动不应该总是公开的进行,也会认识到初始化经...
    pilipalaKing阅读 367评论 0 0
  • 泛型算法: 当我们希望用户可以有更多有用的操作(针对容器)的时候,标准库并没有给每个容器定义自己的成员函数,而是定...
    罗兆峰阅读 203评论 0 0
  • redis面试总结 http://blog.csdn.net/guchuanyun111/article/cate...
    _郑_阅读 190评论 0 0
  • 《你认为的幼师》 说到『幼师』就会有人说:那很轻松;只要哄骗好小孩就好了;跟保姆一样……这些都是没有接触过幼师这个...
    小丫小琛琛阅读 470评论 0 0
  • 夏末秋初的季节,阳光不再具有很强的攻击性,可我还是拉起了车窗的窗帘。阳光透过窗帘照射在我腿间,随着窗外景物的替换而...
    JetLu阅读 144评论 0 0