學習了DOM api 以後,作些簡單的小練習node
在HTML寫5個無序列表segmentfault
<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>
獲取item3
的因此的兄弟節點如何作呢?api
DOM 提供了nextSbiling
`previousSbiling,有人說用
parentNode.children獲取,可是這樣作
item3`也在裏面了,因此就本身作一個API,
我把這個名字叫作getSiblings
數組
//node 是參數,傳遞item3進來,傳出item3的全部兄弟節點。 function getSiblings(node){ var allChildren = node.parentNode.children //先獲取item3全部兄弟節點,保存在allChildren中,是一個僞數組 //剔除item3節點 var array = {length:0}; //建立一個僞數組,存儲item3節點的全部兄弟節點,由於是僞數組,因此給它添加length:0 for(var i=0; i<allChildren.length; i++){ //用for循環遍歷allChildren數組 if(allChildren[i] !== node){ //用if循環判斷若是有allChildren[i]是item3剔除出去 array[array.length] = allChildren[i]; //由於array是僞數組,沒有push,因此用array[array.length]添加進去 array.length++; //僞數組,length不會自動加1 } } return array; //返回出去 } getSiblings(item3);
若是要給item3添加或刪除class,用自帶的API 只能一個個添加,看下面瀏覽器
item3.classList.add('a') item3.classList.add('b') item3.classList.add('c') item3.classList.remove('b')
若是本身作一個API,是否是能夠實現批量添加呢函數
//node classes 是參數,要添加或刪除class的節點,這裏傳遞的classes是僞數組 function addOrRemoveClass(node,classes){ for(var aaa in classes){ //用for循環遍歷classes的key var a = classes[aaa]; //用一個變量來存儲classes的value if(a){ //用if循環判斷classes的value是true仍是false node.classList.add(aaa); //若是是true,則添加key }else { node.classList.remove(aaa);//不然移除 } } } addOrRemoveClass(item3,{'a':true,'b':false,'c':true});
上面代碼if...else...
中有重複代碼,可優化成下面這樣。學習
優化代碼的原則就是提出重複的代碼。優化
var methondName = a?'add':'remove'; //add、remove要用字符串 node.classList[methondName](aaa)
下面先看添加class怎麼實現。foreach
接受一個函數參數,函數參數是數組的value
和key
,能夠看以前總結的Array基本概念this
//node classes 是參數,要添加或刪除class的節點,這裏classes是數組 function addClass(node,classes){ classes.forEach(value => node.classList.add(value)) //把數組的`value`添加到class中 } addClass(item3,[a,b,c])
用這樣的方法建立好用的API,但有個不足的地方,這些都是全局變量,若是在大項目中,很容易覆蓋別人的變量。prototype
因此把本身寫的API 添加到Node
的原型上
Node.prototype.getSiblings = function(){ var allChildren = this.parentNode.children; //this指代調用時前面的那個 var array = {length:0}; for(var i=0; i<allChildren.length;i++){ if(allChildren[i] !== this){ //this指代調用時前面的那個 array[array.length] = allChildren[i]; array.length++; } } return array } Node.prototype.addClass = function(classes){ classes.forEach(value => this.classList.add(value)) } item3.getSibllings.call(item3) //用call的話,直接指定this,不用call的話,是隱示指向`.`前面那個 item3.addClass.call(item3,['a','b','c'])
不理解this
的能夠看函數小知識點
在原型上面添加方法也和上面同樣有弊端,第一項目一大,全部人都在原型上面添加,就會形成原型裏面很混亂;第二你也不知作別人有沒寫和你同樣的方法,這又會形成覆蓋的問題。
由於這是在Node
上直接寫的,那我能不能本身寫一個Node
這樣就不會和人家重名了,就算遇到,改一個名字唄。
新的Node
叫它Node2
,它返回一個對象,對象裏面有兩個函數,也就是getSiblings
和addClass
,並用node2
初始化。
//Node2至關於原來的Node window.Node2 = funcuntion(){ return{ //返回2個方法 getSiblings:function(){}, addClass:function(){} } } var node2 = Node2(''item3) //初始化 node2.getSiblings.call() //正常調用 node2.addClass.call()
getSiblings
和addClass
方法仍是和前面同樣,以前咱們用this
來指代node
,可是這裏用this
的話會指向node2
,而咱們要操做的是node
,因此要把this
變成node
才行。
window.Node2 = function(node){ //參數是要操做的節點 return{ getSiblings:function(){ var allChildren = node.parentNode.children; //這裏不能用this 了,由於用this會變成前面的.node2,而這裏咱們要操做的是node var array = {length:0}; for(var i = 0; i < allChildren.length;i++){ if(allChildren[i] !== node){ array[array.length] = allChildren[i]; array.length++; } } return array; }, addClass:function(classes){ classes.forEach(value => node.classList.add(value)) } } } var node2 = Node2(item3); node2.getSiblings(); //這裏爲何不須要傳入節點,由於在初始化的時候,已經把節點傳進去了 node2.addClass(['a','b','c']);
若是把Node2
改爲jQuery
其餘都不變,是否是也能夠,看下面,getSiblings
和addClass
裏面的方法就不寫了。
window.jQuery = funcuntion(){ return{ getSiblings:function(){}, addClass:function(){} } } var node2 = jQuery(item3) node2.getSiblings.call() node2.addClass.call()
jQuery
就是一個升級的DOM,它接受一個參數,而後返回一個新的對象,這個新對象有新的API,就是這邊的getSiblings
和addClass
,它們的內部是怎麼實現的呢,仍是去調用瀏覽器提供的API,區別是你就寫下面調用的API,一句話就能夠了,而jQuery
是在裏面調用老的API,進行復雜的轉換。
簡單的說jQuery
就是接收一個參數,返回給你一個新對象,你調用它提供的API 就能夠了。
固然了這只是jQuery
的基本原理,實際遠比它複雜。
再來看下面的例子,若是我傳遞的是選擇器怎麼操做呢?
window.jQuery = function(nodeOrSelector){ var node; //存儲if判斷後的參數 if(typeof nodeOrSelector === 'string'){ //由於選擇器是以字符串的形式傳遞進來的,因此先要用if進行判斷,typeof用來判斷類型的 node = document.querySelector(nodeOrSelector); //若是是字符串,就是選擇器,根據選擇器找到對應的節點,並保存在聲明的變量中 }else{ node = nodeOrSelector; //若是不是,直接保存在變量中 } return{ getSiblings:function(){ var allChildren = node.parentNode.children; var array={length:0}; for(var i = 0 ;i < allChildren.length;i++){ if(allChildren[i] !== node){ array[array.length] = allChildren[i]; array.length++; } } return array; }, addClass:function(classes){ classes.forEach(value => node.classList.add(value)) } } } var node2 = jQuery('#item3'); //傳遞選擇器,使用字符串的形式表示 node2.getSiblings(); node2.addClass(['red']);
初始化的時候傳遞了一個選擇器,jQuery 內部先進行判斷,傳進來的是否是字符串,若是是,我就找到對應的節點;後面的操做和以前講的同樣。
咱們再來升級下jQuery,若是我想同時操做5個節點呢?看下面:
window.jQuery = function(nodeOrSelector){ var nodes = {}; //querySelectorAll輸出的是僞數組,因此用來存儲的變量也要初始化成僞數組 if(typeof nodeOrSelector === 'string'){ //用typeof判斷傳進來的類型 var temp = document.querySelectorAll(nodeOrSelector);//若是傳進來的是選擇器,就獲取對應全部的節點,querySelectorAll輸出的是僞數組 //由於獲取到的不是純淨的僞數組(原型不是直接指向Object),先用臨時變量存儲,再用for循環遍歷一遍就能夠了 for(var i = 0; i < temp.length; i++){ nodes[i] = temp[i] } nodes.length = temp.length; //把臨時變量的length長度賦值給nodes,下面遍歷時須要用到 }else if(nodeOrSelector instanceof node){ //instanceof用來判斷傳進來的是否是節點,由於節點只能傳進來一個 nodes = { //由於上面用的是僞數組,因此這邊也要一致,雖然節點是一個,但也要作成僞數組的形式 0:nodeOrSelector, length:1 }; } //這時僞數組內部是{0,1,2,3,4}的屬性,沒有addclass的方法,因此這邊能夠直接往裏面添加方法 nodes.addClass = function(classes){ for(var i = 0; i < nodes.length; i++){ classes.forEach(value => nodes[i].classList.add(value)); //nodes不是元素,這邊操做是在元素身上,因此要for循環遍歷,動態添加 } } nodes.text = function(text){ if(text === undefined){ //這裏默認沒傳參數就是獲取節點文本 var texts = []; //文本最終是數組的形式 for(var i = 0; i < nodes.length; i++){ //遍歷nodes,就能夠獲取到了 texts.push(nodes[i].textContent); } return texts; }else{ //有參數就是設置文本 for(var i = 0; i < nodes.length; i++){ nodes[i].textContent = text; } } } return nodes; //由於整個jQuery內部是一個僞數組,因此這邊返回出去了確定也是一個僞數組了 } var node2 = jQuery('ul > li'); node2.addClass(['red']); console.log(node2.text()); node2.text('hi');
最後簡化一下jQuery ,初始化時的變量名最好帶上$
,後面好辨認。
window.$ = jQuery; var $node2 = $('item3');