A28-JQuery

這次我們自己實現(xiàn)一個類似Query的API(簡化版)

封裝函數(shù)

獲取一個元素所有兄弟元素

// 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, 假設(shè)我們獲取 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  // 返回一個偽數(shù)組
}
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)
  }
}

// 封裝成函數(shù)
function addClass(node, classes){
  for(let key in classes){
    /*
    if(classes[key]){
      node.classList.add(key)
    }
    else{
      node.classList.remove(key)
    }
    */
    // 代碼優(yōu)化守則1:如果出現(xiàn)類似的代碼,就存在優(yōu)化的可能
    // 優(yōu)化這段代碼
    let methodName = class[key] ? 'add' : 'remove'
    node.classList[methodName](key)
  }
}
addClass(item3, {a:true,b:false,c:true}) // test

命名空間

上面兩個函數(shù)是有關(guān)聯(lián)性的萝究,他們都是對node進(jìn)行操作
現(xiàn)在我們來聲明一個變量哩都,將它們放進(jìn)去

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  // 返回一個偽數(shù)組
}

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
這就叫做命名空間,也是一種設(shè)計模式号涯。所有的套路都是設(shè)計模式亡驰,就比如哈希,就比如數(shù)組栽连。
這種常用的模式或者組合就叫做設(shè)計模式险领。
為什么要命名空間:想想JQuery,那么多api秒紧,難道對別人說的時候要一個一個報出來么绢陌,所以一個統(tǒng)稱的名字就很必要了,同時熔恢,你怎么知道別人沒寫addClass呢脐湾?全部放到window里不會產(chǎn)生覆蓋么,所以叙淌,
如果沒有命名空間:

  1. 別人不知道你的庫叫什么
  2. 你會不知不覺把所有全局變量都覆蓋了

將 node 放在前面

相較于命名空間的調(diào)用方式(這種方法已經(jīng)過時了)
zdom.getSiblings(item3)
zdom.addClass(item3,{a: true, b: true, c: flase})
我們認(rèn)為這樣的方式更好:
item3.getSiblings()
item3.addClass({a: true, b: true, c: flase})

這時候秤掌,就需要用到原型,來擴(kuò)展 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  // 返回一個偽數(shù)組
}
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可能會出現(xiàn)覆蓋情況闻鉴,所以又有了第二種方法
這種叫做「無侵入」

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  // 返回一個偽數(shù)組
    },
    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})

除了名字換了一下,和剛才的代碼并沒有區(qū)別
只是Node2變成了jQuery
jQuery就是一個這么升級了的DOM
它接受一個舊的節(jié)點茂洒,然后返回你一個新的對象
這個新的對象有新的API孟岛,它的內(nèi)部實現(xiàn)依然是去調(diào)用舊的API,只是變得更好用获黔、更方便(一句話相當(dāng)于以前十句話)
然而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){ // 注意這里用到了閉包,匿名函數(shù)訪問了外面的 node
          array[array.length] = allChildren[i]  // node 和 匿名函數(shù)組成了閉包(下面的函數(shù)同理)
          array.length += 1
        }
      }
      return array  // 返回一個偽數(shù)組
    },
    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})

那么如果要操作多個節(jié)點呢玷氏?

window.jQuery = function(nodeOrSelector){
  let nodes = {}
  if(typeof nodeOrSelector === 'string'){
    let temp = document.querySelectorAll(nodeOrSelector) // 偽數(shù)組
    for(let i=0; i < temp.length; i++){ // 如果你想要一個純凈的偽數(shù)組不要那些其它屬性方法
      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 認(rèn)為堵未,你沒有設(shè)置參數(shù),那就是要獲取 text盏触,
    // 如果有參數(shù)渗蟹,那就是要設(shè)置 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 構(gòu)造出來的 對象都在前面加一個 $ ,以表示它是由$構(gòu)造的块饺,避免和正常的弄混
var $node2 = $('ul>li')

知道了 jQuery 是怎么實現(xiàn)的,現(xiàn)在去看看真正的 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(){
  /* 常規(guī)寫法
  nodes.removeClass('red')
  nodes.addClass('green')
  */
  nodes.removeClass('red').addClass('green') // 鏈?zhǔn)讲僮?}

總結(jié)

現(xiàn)在大概已經(jīng)知道 jQuery 是怎么寫的了雌芽,明白了它的原理授艰,接下來只需要多了解它的api,
但實際上 jQuery 并沒有這么簡單世落,作為曾經(jīng)風(fēng)靡前端的庫淮腾,它的強(qiáng)大超乎我們的想象。

  • jQuery 在兼容性方面做得很好屉佳,1.7 版本兼容到 IE 6
  • jQuery 還有動畫谷朝、AJAX 等模塊,不止 DOM 操作
  • jQuery 的功能更豐富
  • jQuery 使用了 prototype武花,我們沒有使用圆凰,等學(xué)了 new 之后再用

庫是特定種類的API

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市体箕,隨后出現(xiàn)的幾起案子专钉,更是在濱河造成了極大的恐慌,老刑警劉巖累铅,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件跃须,死亡現(xiàn)場離奇詭異,居然都是意外死亡争群,警方通過查閱死者的電腦和手機(jī)回怜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門大年,熙熙樓的掌柜王于貴愁眉苦臉地迎上來换薄,“玉大人,你說我怎么就攤上這事翔试∏嵋” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵垦缅,是天一觀的道長冲泥。 經(jīng)常有香客問我,道長壁涎,這世上最難降的妖魔是什么凡恍? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮怔球,結(jié)果婚禮上嚼酝,老公的妹妹穿的比我還像新娘。我一直安慰自己竟坛,他們只是感情好闽巩,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布钧舌。 她就那樣靜靜地躺著,像睡著了一般涎跨。 火紅的嫁衣襯著肌膚如雪洼冻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天隅很,我揣著相機(jī)與錄音撞牢,去河邊找鬼。 笑死叔营,一個胖子當(dāng)著我的面吹牛普泡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播审编,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼撼班,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了垒酬?” 一聲冷哼從身側(cè)響起砰嘁,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎勘究,沒想到半個月后矮湘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡口糕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年缅阳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片景描。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡十办,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出超棺,到底是詐尸還是另有隱情向族,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布棠绘,位于F島的核電站件相,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏氧苍。R本人自食惡果不足惜夜矗,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望让虐。 院中可真熱鬧紊撕,春花似錦、人聲如沸澄干。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辩稽,卻和暖如春惧笛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背逞泄。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工患整, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喷众。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓各谚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親到千。 傳聞我的和親對象是個殘疾皇子昌渤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353