案例學習總結:原生JS實現表格排序


最近在學習js的表格排序,沒想到看不起眼的表格排序實際上卻暗含了眾多JS知識點。在這里記錄一下此次學習過程。希望對大家也有所幫助。

完整的表格排序涉及了下列這些知識點:

  • call方法使用
  • sort方法深入
  • 數據綁定
  • DOM映射

下面詳細的總結一下這些知識點,最后結合這些知識點實現下面這樣一個表格排序案例。
表格排序

完整的案例源碼:https://github.com/daweihe/JSProjects

一、知識點總結

1、call方法使用

call方法的作用是改變方法中的this指向。

call這個方法是定義在Function.prototype的方法。我們定義的任何一個函數都可以認為它是Function這個類的一個實例。那么就可以通過實例的__proto__屬性找到所屬類的原型。任何一個函數都可以調用callapply等方法。

先來看一個例子:

var obj = {
    name : 'JS'
}

function testCall () {
    console.log(this);
}

testCall.call( obj );     // {name: "JS"}

首先函數testCall通過原型鏈查找機制找到call方法執行,call方法在執行過程中把調用call方法這個函數實例中的this都改變成call的第一個參數,接下來調用call方法的這個實例函數執行。

看兩個題目:

function fn1() {
    console.log(1);
    console.log(this);
}

function fn2() {
    console.log(2);
    console.log(this);
}

fn1.call(fn2);   //this -> fn2
fn1.call.call(fn2);   //這里的call是改變function.__proto__.call的call方法中的this,相當於執行參數

call方法在執行的時候,call方法的第一個參數是用來改變this的,而從第二個參數開始都是傳給調用call的函數的參數。

在非嚴格模式下,給call方法不傳遞參數、或者傳遞null、undefined后,this都是指向window

sum.call(); //window
sum.call(null); //window
sum.call(undefined); //window

嚴格模式下call執行的時候和非嚴格模式不同:

sum.call(); //undefined
sum.call(null); //null
sum.call(undefined); //undefined

下面使用call方法實現一個類數組轉換為數組的方法:

function listToArray (likeAry) {
    var ary = [];
    try {
        ary = Array.prototype.slice.call(likeAry);
    } catch (e) {
        for (var i = 0; i < likeAry.length; i ++) {
            ary[ary.length] = likeAry[i];
        }
    }
    return ary;
}

和call類似的方法還有apply和bind方法,這里簡單總結一下。

apply方法的作用和call方法一模一樣,只是傳參的形式不太一樣,apply將函數的參數用數組包裹起來:

function sum(num1, num2) {
    console.log(num2 + num1);
    console.log(this);
}

sum.apply(null,[100,200]);

bind方法同樣也是用來改變this關鍵字的,但是它只是僅僅改變this指向,不立即執行調用this的函數。

function sum(num1, num2) {
    console.log(num2 + num1);
    console.log(this);
}

var obj = {name : 'zx'}

var temp = sum.bind(obj);   //temp已經是被改變了this的函數
temp(100,200);              //當我們需要的時候才執行


//或者像這樣處理
var temp = sum.bind(null, 100, 200);
temp();

bind方法體現了js中的預處理思想。

2、 sort排序深入

我們知道數組的sort方法只能排序10以內的數組。如果需要排序的數組中存在大於10的數字,我們就需要向sort方法中傳入回調函數,常見的是這樣:

ary.sort(function (a,b) {
    return a - b;
}); 

這樣就能實現數組的升序排序。那么這樣排序的原理到底是什么呢?

對於傳入的兩個參數:a代表的是找到的數組中的當前項,b代表的是當前項的后一項。

  • return a -b : 如果a大於b,返回結果,a與b交換位置。如果a小於b,那么a和b位置不變。 這是升序排序
  • return b -a : 如果b大於a,返回結果,a與b交換位置。如果a小於b,那么a和b位置不變。 這是降序排序

了解了基本原理后,對於這樣一個二維數組,如何實現按年齡排序?

var persons = [{
    name:'dawei',
    age:55
},{
    name:'ahung',
    age:3
},{
    name:'maomi',
    age:2
},{
    name:'heizi',
    age:78
},{
    name:'afu',
    age:32
}];

其實很簡單:

ary.sort(function(a,b){
    return a.age - b.age;
});

如果按姓名排序,則要涉及字符串的localeCompare()方法:

ary.sort(function(a,b){
    return a.name.localeCompare(b.name);
});

name.localeCompare()這個方法會根據兩個字符串的字母進行比較,如果前一個字符串的第一個字母在24個英文字母中出現的位置比后一個字符串的第一個字符出現的位置靠前,則認定第一個字符串小,返回-1。如果出現的位置靠后,則認定第一個字符串大,返回1。如果所比較的字符相等。則比較下一個字符。

這個方法很實用,常用於按姓氏排序,對於漢字,該方法會自動將漢字轉換為漢語拼音進行比較。

3、數據綁定

在js中一般使用動態綁定或者拼接字符串的方式實現數據綁定。

動態綁定:

//ary為需要添加到頁面中的數據數組
var oDiv = document.getElementById("box");//獲取容器
var myUl = oDiv.getElementsByTagName("ul")[0];//獲取列表

var arrLength = ary.length;
for (var i = 0;i < arrLength ; i ++)
{  //動態創建元素
	var oli = document.createElement("li");
	oli.innerHTML = '<span>' + (i + 5) + '</span>' + ary[i].title;
	myUl.appendChild(oli);//動態添加元素
}

每添加一次就會引起一次DOM回流,如果數據量過大,這樣則會嚴重影響性能。

關於DOM的回流與重繪,推薦大家看一下這篇文章:http://www.css88.com/archives/4996

拼接字符串:

var str = "";
for(var i=0; i<ary.length; i++){
    str += '<li>';
    str += '<span>';
    str += (i+5);
    str += '</span>';
    str += ary[i].title;
    str += '</li>';
}

myUl.innerHTML += str;

這種方式雖然只引起一次回流,但是它會去除原來存在的元素中所有的事件和屬性。如果我們為列表中的li標簽添加鼠標移入,背景變色的事件,那么這種方法會使這個事件失效。

為了解決上面的兩種數據綁定方法帶來的問題,我們使用文檔碎片來添加數據。

var frg = document.createDocumentFragment();//創建文檔碎片
for (var i =0; i <ary.length ;i ++ ){
	var li = document.createElement("li");
	li.innerHTML = '<span>' + ( i + 5 ) + '</span>' + ary[i].title;
	frg.appendChild(li);//將數據動態添加至文檔碎片中
}
myUl.appendChild(frg); //將數據一次性添加到頁面中
frg = null;  //釋放內存

這樣即只引起一次DOM回流,又會保留原來存在的事件。

4、DOM映射

DOM映射機制:所謂映射,就是指兩個元素集之間元素相互“對應”的關系。頁面中的標簽集合和在JS中獲取到的元素對象(元素集合)就是這樣的關系。如果頁面中的HTML標簽結構發送變化,那么集合中對應的內容也會跟着自動改變。

<ul id="myul">
	<li>1</li>
	<li>2</li>
	<li>3</li>
	<li>4</li>
	<li>5</li>
</ul>

對於這樣一個列表使用下列腳本:

var myul = document.getElementById("myul");
var mylis = myul.getElementsByTagName('li');
	for (var i = mylis.length - 1 ; i >= 0; i --) {
	    myul.appendChild(mylis[i]);
	}
console.log(mylis.length);   // 5

將獲取到的列表元素反序重新插入ul中,那么ul列表會變成下面這樣:

<ul id="myul">
    <li>5</li>
    <li>4</li>
    <li>3</li>
    <li>2</li>
    <li>1</li>
</ul>

我們看到列表的長度依然是5,只是位置顛倒了。這是因為每個li標簽和JS中獲取的標簽對象存在一個對應關系,當某個標簽被重新插入到頁面中時,頁面中對應的標簽會移動到插入的位置。這就是DOM映射。

二、實現表格排序

1、使用ajax獲取數據

之所以使用動態獲取數據,是為了使用文檔碎片綁定數據。

var res = ''; //聲明一個全局變量,接收數據
var xhr = new XMLHttpRequest();
xhr.open('get', 'date.txt', false);
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        res = JSON.parse(xhr.responseText);
    }
}
xhr.send(null);

此時數據就保存在了res這個全局變量之中。

2、使用文檔碎片綁定數據

var frg = document.createDocumentFragment();
for (let i = 0; i < res.length; i++) {
    var tr = document.createElement("tr");
    for (key in res[i]) {
        var td = document.createElement("td");
        td.innerHTML = res[i][key];
        tr.appendChild(td);
    }
    frg.appendChild(tr);
}
tbody.appendChild(frg);

3、對表格進行排序

這里涉及的點較多

//為兩列添加點擊事件
for (let i = 0; i < ths.length; i++) {
    let curTh = ths[i];
    curTh.sortFlag = -1; //用於對列進行升降序排列
    curTh.index = i; //記錄當前點擊列的索引,便於排序操作
    if (curTh.className == 'sort') {
        curTh.onclick = function() {
            sort.call(this); //改變排序函數內this的指向,讓其指向當前點擊列
        }
    }
}


//排序方法
function sort() {
    //對數組元素進行排序
    let target = this; //這里將this取出,因為在sort方法里需要使用該this,但是sort方法里的this是調用方法的數組
    this.sortFlag *= -1; //1 代表升序   -1代表降序
    let ary = listToArray(bodyTrs); //獲取body數據
    ary = ary.sort(function(a, b) {
        let one = a.cells[target.index].innerHTML;
        let two = b.cells[target.index].innerHTML;
        let oneNum = parseFloat(one);
        let twoNum = parseFloat(two);

        if (isNaN(oneNum) || isNaN(two)) {
            return one.localeCompare(two) * target.sortFlag;
        } else {
            return (oneNum - twoNum) * target.sortFlag;
        }
    });
    //把排好序的數組重新寫入頁面
    let frg = document.createDocumentFragment();
    for (let i = 0; i < ary.length; i++) {
        rg.appendChild(ary[i]);
    }
    tbody.appendChild(frg);
    frg = null;

    //點擊某列時,要將其他列的排序標志恢復為-1,讓下次再點擊任意一個標簽時都是默認是升序排列
    for (let i = 0; i < ths.length; i++) {
        if (ths[i] != this) {
            ths[i].sortFlag = -1;
        }
    }
}

表格排序應用很常見,在面試中也會有這樣的題目。這個小案例做下來,受益匪淺。這是我在學習的某峰學院的JS課程中的一個案例,如果對JS掌握不扎實的同學,歡迎保存:鏈接: https://pan.baidu.com/s/1jHVy8Uq 密碼: v4jk。如果鏈接失效,加Q群領取:154658901


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM