這次我們自己實現(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)生覆蓋么,所以叙淌,
如果沒有命名空間:
- 別人不知道你的庫叫什么
- 你會不知不覺把所有全局變量都覆蓋了
將 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