A28-JQuery

这次我们自己实现一个类似Query的API(简化版)

封装函数

获取一个元素所有兄弟元素

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

// js, 假设我们获取 item3 的所有兄弟元素
var allChildren = item3.parentNode.children
var 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)  // 测试
// 这样就获得 item3的所有兄弟元素了
// 接下来我们将这段代码封装起来
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  // 返回一个伪数组
}
console.log(getSiblings(item2)) // test
// 这样,一个api就做好了,它可以获得一个元素的所有兄弟元素

接下来我们开始给一个元素添加 class

仍然是上面那段 html

// 为元素添加多个 class
item3.classList.add('a')
item3.classList.add('b')
item3.classList.add('c')

// 简洁一点的写法
var classes = ['a','b','c']
classes.forEach( value => item3.classList.add(value) )

// 这次我们既可以 add 也可以 remove
var classes = {'a':true, 'b':false, 'c':true}
for(let key in classes){
  if(classes[key]){
    item3.classList.add(key)
  }
  else{
    item3.classList.remove(key)
  }
}

// 封装成函数
function addClass(node, classes){
  for(let key in classes){
    /*
    if(classes[key]){
      node.classList.add(key)
    }
    else{
      node.classList.remove(key)
    }
    */
    // 代码优化守则1:如果出现类似的代码,就存在优化的可能
    // 优化这段代码
    let methodName = class[key] ? 'add' : 'remove'
    node.classList[methodName](key)
  }
}
addClass(item3, {a:true,b:false,c:true}) // test

命名空间

上面两个函数是有关联性的,他们都是对node进行操作
现在我们来声明一个变量,将它们放进去

window.zdom = {}

zdom.getSiblings = function(node){
  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  // 返回一个伪数组
}

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

zdom.getSiblings(item3)
zdom.addClass(item3,{a: true, b: true, c: flase})

有一个库就是这样的方式:yui
这就叫做命名空间,也是一种设计模式。所有的套路都是设计模式,就比如哈希,就比如数组。
这种常用的模式或者组合就叫做设计模式。
为什么要命名空间:想想JQuery,那么多api,难道对别人说的时候要一个一个报出来么,所以一个统称的名字就很必要了,同时,你怎么知道别人没写addClass呢?全部放到window里不会产生覆盖么,所以,
如果没有命名空间:

  1. 别人不知道你的库叫什么
  2. 你会不知不觉把所有全局变量都覆盖了

将 node 放在前面

相较于命名空间的调用方式(这种方法已经过时了)
zdom.getSiblings(item3)
zdom.addClass(item3,{a: true, b: true, c: flase})
我们认为这样的方式更好:
item3.getSiblings()
item3.addClass({a: true, b: true, c: flase})

这时候,就需要用到原型,来扩展 Node 接口,
这是第一种方法

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){
  for(let key in classes){
    let methodName = class[key] ? 'add' : 'remove'
    this.classList[methodName](key)
  }
}
// 隐式指定 this
item3.getSiblings()
item3.addClass({a: true, b: true, c: flase})
// 显式指定 this
item3.getSiblings.call(item3)
item3.addClass.call(item3, {a: true, b: true, c: flase})

但是改写Node.prototype可能会出现覆盖情况,所以又有了第二种方法
这种叫做「无侵入」

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){
      for(let key in classes){
        let methodName = class[key] ? 'add' : 'remove'
        node.classList[methodName](key)
      }
    }
  }
}
var node2 = Node2(item3)
node2.getSiblings()
node2.addClass({a: true, b: true, c: flase})

给 Node2 换个名字 jQuery

window.jQuery = function(node){
  return {
    getSiblings: function(){ ... },
    addClass: function(classes){ ... }
  }
}
var node2 = jQuery(item3)
node2.getSiblings()
node2.addClass({a: true, b: true, c: flase})

除了名字换了一下,和刚才的代码并没有区别
只是Node2变成了jQuery
jQuery就是一个这么升级了的DOM
它接受一个旧的节点,然后返回你一个新的对象
这个新的对象有新的API,它的内部实现依然是去调用旧的API,只是变得更好用、更方便(一句话相当于以前十句话)
然而jQuery的厉害不止于此,它还可以接受选择器

window.jQuery = function(nodeOrSelector){
  let node
  // 类型检测
  if(typeof nodeOrSelector === 'string'){
    node = document.querySelector(nodeOrSelector)
  } else{
    node = nodeOrSelector
  }
  return {
    getSiblings: function(){
      var allChildren = node.parentNode.children
      var array = {  length: 0; }
      for(let i=0; i < allChildren.length; i++){
        if(allChildren[i] !== node){ // 注意这里用到了闭包,匿名函数访问了外面的 node
          array[array.length] = allChildren[i]  // node 和 匿名函数组成了闭包(下面的函数同理)
          array.length += 1
        }
      }
      return array  // 返回一个伪数组
    },
    addClass: function(classes){
      for(let key in classes){
        let methodName = class[key] ? 'add' : 'remove'
        node.classList[methodName](key)
      }
    }
  }
}
var node2 = jQuery('#item3')
// var node2 = jQuery('ul > li:nth-child(3)')
node2.addClass({a: true, b: true, c: flase})

那么如果要操作多个节点呢?

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]
    }
    node.length = temp.length
  } else if(nodeOrSelector instanceof Node) {
    nodes = {0: nodeOrSelector, length: 1}
  }
  nodes.getSiblings = function(){/*太麻烦不做了*/}
  nodes.addClass = function(classes){
    classes.forEach((value) => {
      for(let i=0; i < nodes.length; i++){
        nodes[i].classList.add(value)
      }
    })
  }
  // 添加几个有用的 jQuery Api
  nodes.getText = function(){
    let texts = []
    for(let i=0; i < nodes.length; i++){
      texts.push(nodes[i].textContent)
    }
    return texts
  }
  nodes.setText = function(text){
    for(let i=0; i < nodes.length; i++){
      nodes[i].textContent = text
    }
  }
  // 然而实际上 jQuery 合并了 get和set
  // 类似的 api jquery 有很多
  nodes.text = function(text){
    // jQuery 认为,你没有设置参数,那就是要获取 text,
    // 如果有参数,那就是要设置 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 node2 = jQuery('ul > li')
node2.addClass(['red'])
var text = node2.text()
node2.text('hi')

缩写 alias

window.$ = jQuery
// 建议使用 jQuery 构造出来的 对象都在前面加一个 $ ,以表示它是由$构造的,避免和正常的弄混
var $node2 = $('ul>li')

知道了 jQuery 是怎么实现的,现在去看看真正的 jQuery吧 -MDN

// html
<ul>
  <li class='red'>选项1</li>
  <li class='red'>选项2</li>
  <li class='red'>选项3</li>
  <li class='red'>选项4</li>
  <li class='red'>选项5</li>
</ul>
<button id=x></button>

// css
.red{color:red;}
.green{color:green;}

// js,使用 jQuery
var nodes = jQuery('ul>li')
var classes = ['red','green','blue','yellow','black']
x.onclick = function(){
  /* 常规写法
  nodes.removeClass('red')
  nodes.addClass('green')
  */
  nodes.removeClass('red').addClass('green') // 链式操作
}

总结

现在大概已经知道 jQuery 是怎么写的了,明白了它的原理,接下来只需要多了解它的api,
但实际上 jQuery 并没有这么简单,作为曾经风靡前端的库,它的强大超乎我们的想象。

  • jQuery 在兼容性方面做得很好,1.7 版本兼容到 IE 6
  • jQuery 还有动画、AJAX 等模块,不止 DOM 操作
  • jQuery 的功能更丰富
  • jQuery 使用了 prototype,我们没有使用,等学了 new 之后再用

库是特定种类的API

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