學習了DOM api 之後,做些簡單的小練習
在HTML寫5個無序列表
<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
的所以的兄弟節點如何做呢?
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基本概念
//node classes 是引數,要新增或刪除class的節點,這裡classes是陣列
function addClass(node,classes){
classes.forEach(value => node.classList.add(value)) //把陣列的`value`新增到class中
}
addClass(item3,[a,b,c])
用這樣的方法建立好用的API,但有個不足的地方,這些都是全域性變數,如果在大專案中,很容易覆蓋別人的變數。
所以把自己寫的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');