jQuery初體驗

weixin_34302561發表於2018-05-19

學習了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接受一個函式引數,函式引數是陣列的valuekey,可以看之前總結的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,它返回一個物件,物件裡面有兩個函式,也就是getSiblingsaddClass,並用node2初始化。

//Node2相當於原來的Node
window.Node2 = funcuntion(){    
    return{        //返回2個方法
        getSiblings:function(){},
        addClass:function(){}
    }
}

var node2 = Node2(''item3)    //初始化
node2.getSiblings.call()    //正常呼叫
node2.addClass.call()

getSiblingsaddClass方法還是和前面一樣,之前我們用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其他都不變,是不是也可以,看下面,getSiblingsaddClass裡面的方法就不寫了。

window.jQuery = funcuntion(){    
    return{
        getSiblings:function(){},
        addClass:function(){}
    }
}

var node2 = jQuery(item3)    
node2.getSiblings.call()
node2.addClass.call()

jQuery就是一個升級的DOM,它接受一個引數,然後返回一個新的物件,這個新物件有新的API,就是這邊的getSiblingsaddClass,它們的內部是怎麼實現的呢,還是去呼叫瀏覽器提供的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');