標籤: 台北網頁設計公司

  • 工信部擬規範新能源汽車廢舊動力電池綜合利用

    工業和資訊化部近日就《新能源汽車廢舊動力蓄電池綜合利用行業規範條件》向社會公開徵求意見,擬要求已在禁止建設區域投產運營的廢舊動力蓄電池綜合利用企業,要在一定期限內通過“依法搬遷、轉產”等方式逐步退出。

    禁止建設區域包括:國家法律、法規、規章及規劃確定或縣級以上人民政府批准的自然保護區、生態功能保護區、風景名勝區、飲用水水源保護區、基本農田保護區和其他需要特別保護的區域等。

    意見稿還對廢舊動力電池綜合利用作出規範。廢舊動力蓄電池綜合利用企業應依據相關國家、行業標準,參考新能源汽車和動力蓄電池生產企業提供的拆卸、拆解技術資訊,嚴格遵循先梯級利用後再生利用的原則,提高綜合利用水準。

    根據意見稿,濕法冶煉條件下,鎳、鈷、錳的綜合回收率應不低於98%;火法冶煉條件下,鎳、稀土的綜合回收率應不低於97%;不得擅自丟棄、傾倒、焚燒與填埋廢舊動力電池中的有色金屬、石墨、塑膠、橡膠、隔膜、電解液等零部件和材料。

    在能源消耗方面,意見稿規定,企業應加強對拆卸、儲存、拆解、檢測和再生利用等環節的能耗管控,努力降低綜合能耗,提高能源利用效率,鼓勵企業採用先進適用的節能技術工藝及裝備。

    此外,工信部同日發佈《新能源汽車廢舊動力蓄電池綜合利用行業規範公告管理暫行辦法(徵求意見稿)》,擬對新能源汽車廢舊動力蓄電池綜合利用企業實行動態管理,委託相關專業機構負責協助做好公告管理相關工作。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • 2016上海國際汽車新能源及智慧技術展覽會

    創新技術 智能演繹 展會日期:2016年6月28日-30日 展會地點:上海新國際博覽中心   觀眾數量:30,000人次(預計) 展覽週期:二年一屆,2016年首屆 同期展會:上海國際汽車零部件及相關服務展覽會   主辦單位: 中國國際貿易促進委員會上海市分會 中國國際貿易促進委員會汽車行業分會 中國汽車工程學會 上海市國際展覽有限公司   展位價格: 室內光地:650元/平方米(36平米起租) 標準展位:900元/平方米(9平米起租)   參展範圍: -節能汽車、純電動車,混合動力車,燃料電池車,輕型電動車,天然氣(液化氣)車,醇類及其他代用燃料車和節能汽車。 -電池、電機、電控等核心零部件和先進技術應用;輕量化材料、整車優化設計及混合動力等節能技術產品; 自動駕駛,汽車共用,模組化(集成化)構造,汽車車載資訊系統及智慧化設備;汽車相關後市場服務及產品。 -充電樁、充電機、配電櫃、充換電池及電池管理系統、停車場充電設施、智慧監控、充電站供電解決方案、充電站-智慧電網解決方案等。 -檢測,維修,監控,實驗,安全防護裝備及媒體等.   展會諮詢: 北京盛大超越國際展覽有限公司 連絡人:岳巍 先生 電話(微信):135 5286 5285 郵箱:     附:其他推薦展會   2016中國國際汽車新能源及技術應用展覽會 節能與新能源汽車產業發展規劃成果展覽會   展會時間:2016年10月13-16日 展會地點:北京•國家會議中心 展覽規模:30,000平方米(2015年) 觀眾數量:60,000人次(2015年) 展覽週期:每年一屆,2013年首屆 詳情請點擊  

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 【algo&ds】2.線性表

    【algo&ds】2.線性表

    1.線性表

    線性表(英語:Linear List)是由n(n≥0)個元素()a[0],a[1],a[2]…,a[n-1]組成的。

    其中:

    • 數據元素的個數n定義為表的長度 = “list”.length() (”list”.length() = 0(表裡沒有一個元素)時稱為空表)
    • 將非空的線性表(n>=1)記作:(a[0],a[1],a[2],…,a[n-1])
    • 數據元素a[i](0≤i≤n-1)只是個抽象符號,其具體含義在不同情況下可以不同

    一個數據元素可以由若干個數據項組成。數據元素稱為記錄,含有大量記錄的線性表又稱為文件。這種結構具有下列特點:存在一個唯一的沒有前驅的(頭)數據元素;存在一個唯一的沒有後繼的(尾)數據元素;此外,每一個數據元素均有一個直接前驅和一個直接後繼數據元素。

    2.線性表的存儲結構

    • 鏈表
      • 單鏈表
        • 動態單鏈表
        • 靜態單鏈表
      • 循環鏈表
        • 單循環鏈表
        • 雙循環鏈表
      • 靜態鏈表

    3.順序表

    利用數組的連續存儲空間順序存放線性表的各元素

    3.1結構體定義

    如果需要使用自定義的結構體來維護一個順序表,通常來講結構體的元素一般是一個固定大小的數組(可用長度足夠大),以及當前數組存放的元素個數,也即數組的長度

    typedef struct LNode *List;
    struct LNode {
        ElementType Data[MAXSIZE];
        int Last;//記錄順序表的最後一個元素的下標
    } ;
    struct LNode L;
    List PtrL;

    訪問結構體的成員

    • 訪問下標為 i 的元素:L.Data[i] 或 PtrL->Data[i]
    • 線性表的長度:L.Last+1 或 PtrL->Last+1
    • 指針變量PtrL還可以這樣訪問兩個屬性(*PtrL).Data[i](*PtrL).Last,不過這種訪問方式並不常用

    而一般來講,為了簡單,不會去維護這樣一個結構體,(因為一旦維護了這個結構體,就需要去封裝相應的函數,比如說常見的插入、刪除、查找等操作),而是直接類似下面這樣

    ElementType data[MaxSize];
    int length;

    定義一個足夠大的數組,然後定義一個對應關聯的變量來時刻維護數組的長度。

    這兩種定義方式沒有什麼區別,一種是把常用操作封裝好,方便調用,另一種則是需要時刻自己維護對應的屬性。因為順序表的結構足夠簡單,所以不定義結構體也是可以的。

    3.2順序表的常見操作

    為了方便,這一節內容記錄的都是在定義的結構體基礎上,封裝的常見操作。

    1.初始化

    List MakeEmpty( ) {
        List PtrL;
        PtrL = (List )malloc( sizeof(struct LNode) );
        PtrL->Last = -1;
        return PtrL;
    }

    初始化的順序表,長度為0,所以Last為-1

    2.查找

    int Find( ElementType X, List PtrL ) {
        int i = 0;
        while( i <= PtrL->Last && PtrL->Data[i]!= X )
            i++;
        if (i > PtrL->Last) return -1; /* 如果沒找到,返回-1 */
        else return i; /* 找到后返回的是存儲位置 */
    }

    查找操作比較簡單,從順序表的第一個元素(下標為0開始)開始遍歷。

    還有一種更加巧妙一點的實現方式,就是引入哨兵思想。

    int Find( ElementType X, List PtrL ) {
        PtrL->Data[0] = x;//順序表第一個元素就是哨兵,賦值為x
        int i = PtrL->Last;//從最後一個元素開始遍歷
        while( PtrL->Data[i]!= X )
            i--;
        return i;
    }

    這樣做的好處很明顯,少了邊界的判斷,可以優化時間複雜度,編碼也更加簡單。

    注意:這裏把下標為0的元素設置為哨兵,則要求順序表從下標為1開始存儲。而且,函數如果沒有找到,則一定返回i=0

    3.插入

    看圖示應該要注意,移動的方向是從后往前移,如果從前往後移,則Data[i]=Data[i+1]=…=Data[n],因為後面的元素都被前面移過來的元素給覆蓋了。

    void Insert( ElementType X, int i, List PtrL ) {
        int j;
        if ( PtrL->Last == MAXSIZE-1 ) { /* 表空間已滿,不能插入*/
            printf("表滿");
            return;
        }
        if ( i < 1 || i > PtrL->Last+2) { /*檢查插入位置的合法性*/
            printf("位置不合法");
            return;
        }
        for ( j = PtrL->Last; j >= i-1; j-- )
            PtrL->Data[j+1] = PtrL->Data[j]; /*將 ai~ an倒序向後移動*/
        PtrL->Data[i-1] = X; /*新元素插入*/
        PtrL->Last++; /*Last仍指向最後元素*/
        return;
    }

    為什麼這裏需要判斷順序表的空間是否已滿?

    因為這個數組,是在初始化之後就固定了數組可容納的元素個數MaxSize,一旦超出,則程序下標就會越界。C++提供了動態數組vector,可以很方便的支持動態擴展數組長度,而且基本的插入刪除等操作都封裝好了,可以很方便的使用。

    4.刪除

    同樣的,需要注意元素移動的方向,如果從后往前移,則最後面的元素會一直覆蓋到Data[i]。

    void Delete( int i, List PtrL ) {
        int j;
        if( i < 1 || i > PtrL->Last+1 ) { /*檢查空表及刪除位置的合法性*/
            printf (“不存在第%d個元素”, i );
            return ;
        }
        for ( j = i; j <= PtrL->Last; j++ )
            PtrL->Data[j-1] = PtrL->Data[j]; /*將 ai+1~ an順序向前移動*/
        PtrL->Last--; /*Last仍指向最後元素*/
        return;
    }

    5.排序

    因為排序算法比較多,本文不展開講解,可以參考以下博文,內容包括了常見的十大排序算法的算法分析,時間複雜度和空間複雜度分析以及c實現和動圖圖解。

    4.鏈表

    不要求邏輯上相鄰的兩個元素物理上也相鄰;通過“鏈”建立起數據元素之間的邏輯關係。插入、刪除不需要移動數據元素,只需要修改“鏈”。

    4.1單鏈表

    typedef struct LNode *List;
    struct LNode {
        ElementType Data;
        List Next;
    };
    struct Lnode L;
    List PtrL;
    1.求表長
    int Length ( List PtrL ) {
        List p = PtrL; /* p指向表的第一個結點*/
        int j = 0;
        while ( p ) {
            p = p->Next;
            j++; /* 當前p指向的是第 j 個結點*/
        }
        return j;
    }

    時間複雜度O(n)

    2.查找

    按序查找

    List FindKth( int K, List PtrL ) {
        List p = PtrL;
        int i = 1;
        while (p !=NULL && i < K ) {
            p = p->Next;
            i++;
        }
        if ( i == K ) return p;
        /* 找到第K個,返回指針 */
        else return NULL;
        /* 否則返回空 */
    }

    時間複雜度O(n)

    按值查找

    List Find( ElementType X, List PtrL ) {
        List p = PtrL;
        while ( p!=NULL && p->Data != X )
            p = p->Next;
        return p;
    }

    時間複雜度O(n)

    3.插入

    (1)先構造一個新結點,用s指向;
    (2)再找到鏈表的第 i-1個結點,用p指向;
    (3)然後修改指針,插入結點 ( p之後插入新結點是 s)

    List Insert( ElementType X, int i, List PtrL ) {
        List p, s;
        if ( i == 1 ) { /* 新結點插入在表頭 */
            s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
            s->Data = X;
            s->Next = PtrL;
            return s; /*返回新表頭指針*/
        }
        p = FindKth( i-1, PtrL ); /* 查找第i-1個結點 */
        if ( p == NULL ) { /* 第i-1個不存在,不能插入 */
            printf("參數i錯");
            return NULL;
        } else {
            s = (List)malloc(sizeof(struct LNode)); /*申請、填裝結點*/
            s->Data = X;
            s->Next = p->Next; /*新結點插入在第i-1個結點的後面*/
            p->Next = s;
            return PtrL;
        }
    }
    4.刪除

    (1)先找到鏈表的第 i-1個結點,用p指向;
    (2)再用指針s指向要被刪除的結點(p的下一個結點);
    (3)然後修改指針,刪除s所指結點;
    (4)最後釋放s所指結點的空間。

    List Delete( int i, List PtrL ) {
        List p, s;
        if ( i == 1 ) { /* 若要刪除的是表的第一個結點 */
            s = PtrL; /*s指向第1個結點*/
            if (PtrL!=NULL) PtrL = PtrL->Next; /*從鏈表中刪除*/
            else return NULL;
            free(s); /*釋放被刪除結點 */
            return PtrL;
        }
        p = FindKth( i-1, PtrL ); /*查找第i-1個結點*/
        if ( p == NULL ) {
            printf("第%d個結點不存在", i-1);
            return NULL;
        } else if ( p->Next == NULL ) {
            printf("第%d個結點不存在", i);
            return NULL;
        } else {
            s = p->Next; /*s指向第i個結點*/
            p->Next = s->Next; /*從鏈表中刪除*/
            free(s); /*釋放被刪除結點 */
            return PtrL;
        }
    }

    4.2雙鏈表

    雙向鏈表,又稱為雙鏈表,是的一種,它的每個數據結點中都有兩個,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。

    typedef struct DuLNode {
        ElemType data;
        struct DuLNode *prior, *next;
    } DuLNode, *DuLinkList;

    4.3循環鏈表

    4.3.1單循環鏈表

    存儲結構和單鏈表相同。

    typedef struct LNode {
        ElemType data;
        struct LNode *next;
    } LNode, *LinkList;
    
    // 設立尾指針的單循環鏈表的12個基本操作
    void InitList(LinkList *L) { // 操作結果:構造一個空的線性表L
        *L = (LinkList)malloc(sizeof(struct LNode)); // 產生頭結點,並使L指向此頭結點
        if (!*L) // 存儲分配失敗
            exit(OVERFLOW);
        (*L)->next = *L; // 指針域指向頭結點
    }
    
    void DestroyList(LinkList *L) { // 操作結果:銷毀線性表L
        LinkList q, p = (*L)->next; // p指向頭結點
        while (p != *L) { // 沒到表尾
            q = p->next;
            free(p);
            p = q;
        }
        free(*L);
        *L = NULL;
    }
    
    void ClearList(LinkList *L) /* 改變L */ { // 初始條件:線性表L已存在。操作結果:將L重置為空表
        LinkList p, q;
        *L = (*L)->next; // L指向頭結點
        p = (*L)->next; // p指向第一個結點
        while (p != *L) { // 沒到表尾
            q = p->next;
            free(p);
            p = q;
        }
        (*L)->next = *L; // 頭結點指針域指向自身
    }
    
    Status ListEmpty(LinkList L) { // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
        if (L->next == L) // 空
            return TRUE;
        else
            return FALSE;
    }
    
    int ListLength(LinkList L) { // 初始條件:L已存在。操作結果:返回L中數據元素個數
        int i = 0;
        LinkList p = L->next; // p指向頭結點
        while (p != L) { // 沒到表尾
            i++;
            p = p->next;
        }
        return i;
    }
    
    Status GetElem(LinkList L, int i, ElemType *e) { // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
        int j = 1; // 初始化,j為計數器
        LinkList p = L->next->next; // p指向第一個結點
        if (i <= 0 || i > ListLength(L)) // 第i個元素不存在
            return ERROR;
        while (j < i) { // 順指針向後查找,直到p指向第i個元素
            p = p->next;
            j++;
        }
        *e = p->data; // 取第i個元素
        return OK;
    }
    
    int LocateElem(LinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) { // 初始條件:線性表L已存在,compare()是數據元素判定函數
        // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
        //           若這樣的數據元素不存在,則返回值為0
        int i = 0;
        LinkList p = L->next->next; // p指向第一個結點
        while (p != L->next) {
            i++;
            if (compare(p->data, e)) // 滿足關係
                return i;
            p = p->next;
        }
        return 0;
    }
    
    Status PriorElem(LinkList L, ElemType cur_e, ElemType *pre_e) { // 初始條件:線性表L已存在
        // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
        //           否則操作失敗,pre_e無定義
        LinkList q, p = L->next->next; // p指向第一個結點
        q = p->next;
        while (q != L->next) { // p沒到表尾
            if (q->data == cur_e) {
                *pre_e = p->data;
                return TRUE;
            }
            p = q;
            q = q->next;
        }
        return FALSE; // 操作失敗
    }
    
    Status NextElem(LinkList L, ElemType cur_e, ElemType *next_e) { // 初始條件:線性表L已存在
        // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
        //           否則操作失敗,next_e無定義
        LinkList p = L->next->next; // p指向第一個結點
        while (p != L) { // p沒到表尾
            if (p->data == cur_e) {
                *next_e = p->next->data;
                return TRUE;
            }
            p = p->next;
        }
        return FALSE; // 操作失敗
    }
    
    Status ListInsert(LinkList *L, int i, ElemType e) /* 改變L */ { // 在L的第i個位置之前插入元素e
        LinkList p = (*L)->next, s; // p指向頭結點
        int j = 0;
        if (i <= 0 || i > ListLength(*L) + 1) // 無法在第i個元素之前插入
            return ERROR;
        while (j < i - 1) { // 尋找第i-1個結點
            p = p->next;
            j++;
        }
        s = (LinkList)malloc(sizeof(struct LNode)); // 生成新結點
        s->data = e; // 插入L中
        s->next = p->next;
        p->next = s;
        if (p == *L) // 改變尾結點
            *L = s;
        return OK;
    }
    
    Status ListDelete(LinkList *L, int i, ElemType *e) /* 改變L */ { // 刪除L的第i個元素,並由e返回其值
        LinkList p = (*L)->next, q; // p指向頭結點
        int j = 0;
        if (i <= 0 || i > ListLength(*L)) // 第i個元素不存在
            return ERROR;
        while (j < i - 1) { // 尋找第i-1個結點
            p = p->next;
            j++;
        }
        q = p->next; // q指向待刪除結點
        p->next = q->next;
        *e = q->data;
        if (*L == q) // 刪除的是表尾元素
            *L = p;
        free(q); // 釋放待刪除結點
        return OK;
    }
    
    void ListTraverse(LinkList L, void(*vi)(ElemType)) { // 初始條件:L已存在。操作結果:依次對L的每個數據元素調用函數vi()
        LinkList p = L->next->next; // p指向首元結點
        while (p != L->next) { // p不指向頭結點
            vi(p->data);
            p = p->next;
        }
        printf("\n");
    }

    4.3.2雙循環鏈表

    // 線性表的雙向鏈表存儲結構
    typedef struct DuLNode {
        ElemType data;
        struct DuLNode *prior, *next;
    } DuLNode, *DuLinkList;
    
    // 帶頭結點的雙向循環鏈表的基本操作(14個)
    void InitList(DuLinkList *L) {
        // 產生空的雙向循環鏈表L
        *L = (DuLinkList)malloc(sizeof(DuLNode));
        if (*L)
            (*L)->next = (*L)->prior = *L;
        else
            exit(OVERFLOW);
    }
    
    void DestroyList(DuLinkList *L) {
        // 操作結果:銷毀雙向循環鏈表L
        DuLinkList q, p = (*L)->next; // p指向第一個結點
        while (p != *L) { // p沒到表頭
            q = p->next;
            free(p);
            p = q;
        }
        free(*L);
        *L = NULL;
    }
    
    void ClearList(DuLinkList L) { // 不改變L
        // 初始條件:L已存在。操作結果:將L重置為空表
        DuLinkList q, p = L->next; // p指向第一個結點
        while (p != L) { // p沒到表頭
            q = p->next;
            free(p);
            p = q;
        }
        L->next = L->prior = L; // 頭結點的兩個指針域均指向自身
    }
    
    Status ListEmpty(DuLinkList L) {
        // 初始條件:線性表L已存在。操作結果:若L為空表,則返回TRUE,否則返回FALSE
        if (L->next == L && L->prior == L)
            return TRUE;
        else
            return FALSE;
    }
    
    int ListLength(DuLinkList L) {
        // 初始條件:L已存在。操作結果:返回L中數據元素個數
        int i = 0;
        DuLinkList p = L->next; // p指向第一個結點
        while (p != L) { // p沒到表頭
            i++;
            p = p->next;
        }
        return i;
    }
    
    Status GetElem(DuLinkList L, int i, ElemType *e) {
        // 當第i個元素存在時,其值賦給e並返回OK,否則返回ERROR
        int j = 1; // j為計數器
        DuLinkList p = L->next; // p指向第一個結點
        while (p != L && j < i) { // 順指針向後查找,直到p指向第i個元素或p指向頭結點
            p = p->next;
            j++;
        }
        if (p == L || j > i) // 第i個元素不存在
            return ERROR;
        *e = p->data; // 取第i個元素
        return OK;
    }
    
    int LocateElem(DuLinkList L, ElemType e, Status(*compare)(ElemType, ElemType)) {
        // 初始條件:L已存在,compare()是數據元素判定函數
        // 操作結果:返回L中第1個與e滿足關係compare()的數據元素的位序。
        // 若這樣的數據元素不存在,則返回值為0
        int i = 0;
        DuLinkList p = L->next; // p指向第1個元素
        while (p != L) {
            i++;
            if (compare(p->data, e)) // 找到這樣的數據元素
                return i;
            p = p->next;
        }
        return 0;
    }
    
    Status PriorElem(DuLinkList L, ElemType cur_e, ElemType *pre_e) {
        // 操作結果:若cur_e是L的數據元素,且不是第一個,則用pre_e返回它的前驅,
        // 否則操作失敗,pre_e無定義
        DuLinkList p = L->next->next; // p指向第2個元素
        while (p != L) { // p沒到表頭
            if (p->data == cur_e) {
                *pre_e = p->prior->data;
                return TRUE;
            }
            p = p->next;
        }
        return FALSE;
    }
    
    Status NextElem(DuLinkList L, ElemType cur_e, ElemType *next_e) {
        // 操作結果:若cur_e是L的數據元素,且不是最後一個,則用next_e返回它的後繼,
        // 否則操作失敗,next_e無定義
        DuLinkList p = L->next->next; // p指向第2個元素
        while (p != L) { // p沒到表頭
            if (p->prior->data == cur_e) {
                *next_e = p->data;
                return TRUE;
            }
            p = p->next;
        }
        return FALSE;
    }
    
    DuLinkList GetElemP(DuLinkList L, int i) { // 另加
        // 在雙向鏈表L中返回第i個元素的地址。i為0,返回頭結點的地址。若第i個元素不存在,
        // 返回NULL
        int j;
        DuLinkList p = L; // p指向頭結點
        if (i < 0 || i > ListLength(L)) // i值不合法
            return NULL;
        for (j = 1; j <= i; j++)
            p = p->next;
        return p;
    }
    
    Status ListInsert(DuLinkList L, int i, ElemType e) {
        // 在帶頭結點的雙鏈循環線性表L中第i個位置之前插入元素e,i的合法值為1≤i≤表長+1
        // 改進算法2.18,否則無法在第表長+1個結點之前插入元素
        DuLinkList p, s;
        if (i < 1 || i > ListLength(L) + 1) // i值不合法
            return ERROR;
        p = GetElemP(L, i - 1); // 在L中確定第i個元素前驅的位置指針p
        if (!p) // p=NULL,即第i個元素的前驅不存在(設頭結點為第1個元素的前驅)
            return ERROR;
        s = (DuLinkList)malloc(sizeof(DuLNode));
        if (!s)
            return OVERFLOW;
        s->data = e;
        s->prior = p; // 在第i-1個元素之後插入
        s->next = p->next;
        p->next->prior = s;
        p->next = s;
        return OK;
    }
    
    Status ListDelete(DuLinkList L, int i, ElemType *e) {
        // 刪除帶頭結點的雙鏈循環線性表L的第i個元素,i的合法值為1≤i≤表長
        DuLinkList p;
        if (i < 1) // i值不合法
            return ERROR;
        p = GetElemP(L, i); // 在L中確定第i個元素的位置指針p
        if (!p) // p = NULL,即第i個元素不存在
            return ERROR;
        *e = p->data;
        p->prior->next = p->next; // 此處並沒有考慮鏈表頭,鏈表尾
        p->next->prior = p->prior;
        free(p);
        return OK;
    }
    
    void ListTraverse(DuLinkList L, void(*visit)(ElemType)) {
        // 由雙鏈循環線性表L的頭結點出發,正序對每個數據元素調用函數visit()
        DuLinkList p = L->next; // p指向頭結點
        while (p != L) {
            visit(p->data);
            p = p->next;
        }
        printf("\n");
    }
    
    void ListTraverseBack(DuLinkList L, void(*visit)(ElemType)) {
        // 由雙鏈循環線性表L的頭結點出發,逆序對每個數據元素調用函數visit()
        DuLinkList p = L->prior; // p指向尾結點
        while (p != L) {
            visit(p->data);
            p = p->prior;
        }
        printf("\n");
    }

    4.4靜態鏈表

    前面講解的都是動態鏈表,即需要指針來建立結點之間的連接關係。而對有些問題來說結點的地址是比較小的整數(例如5位數的地址),這樣就沒有必要去建立動態鏈表,而應使用方便得多的靜態鏈表。
    靜態鏈表的實現原理是hash,即通過建立一個結構體數組,並令數組的下標直接表示結點的地址,來達到直接訪問數組中的元素就能訪問結點的效果。另外,由於結點的訪問非常方便,因此靜態鏈表是不需要頭結點的。靜態鏈表結點定義的方法如下:

    struct Node{
        typename data;//數據域
        int next;//指針域
    }node[size];

    參考資料:

    • 《算法筆記》
    • 《數據結構和算法》-極客時間專欄

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • X-Admin&ABP框架開發-RBAC

    X-Admin&ABP框架開發-RBAC

      在業務系統需求規劃過程中,通常對於諸如組織機構、用戶和角色等這種基礎功能,通常是將這部分功能規劃到通用子域中,這也說明了,對於這部分功能來講,是系統的基石,整個業務體系是建立於這部分基石之上的,當然,還有諸如多語言、設置管理、認證和授權等。對於這部分功能,ABP中存在這些概念,並且通過Module Zero模塊完成了這些概念。

     

    一、角色訪問控制之RBAC

      RBAC:Role Based Access Control,基於角色的訪問控制,這在目前大多數軟件中來講已經算得上是普遍應用了,最常見的結構如下,結構簡單,設計思路清晰。

      

      但是也存在其它升級版的設計,諸如用戶權限表、角色組、用戶組的概念等,具體分類有RBAC0、RBAC1、RBAC2等,後者功能越來越強大,也越來越複雜。

    • RBAC0:是RBAC的核心思想。
    • RBAC1:是把RBAC的角色分層模型。
    • RBAC2:增加了RBAC的約束模型。
    • RBAC3:整合RBAC2 + RBAC1。

     

    二、ABP中的RBAC

      在Abp中,已經集成了這些概念,並在ModuleZero模塊中實現了這些概念,基於IdentityServer4的ModuleZero模塊完成了封裝。對於我們大多數以業務為中心的開發人員來講,不應該又去造一個輪子,而是應該開好這輛車。首先看下Abp中的RBAC模型

      

      在這其中權限表中記錄了用戶與權限,角色與權限兩部分。對於權限通常指的是功能權限和數據權限兩部分,一般來講,大多指的是功能權限,這種通過角色與權限進行管理即可,如還有用戶部分的功能區分,則可以再使用上用戶與權限,而對於數據權限,可以利用用戶與權限部分,個人用的比較少,但是,可以想象到這麼一個場景,針對於一家門店內的多個店長,角色相同即相應的權限相同,但各自關心的數據來源不同,關心東部、南部等數據,而不關心西部、北部數據,因此可以在數據層面進行劃分,比如設置數據來源,東南西北,對於數據來源進行權限關聯,這樣一來用戶本身如果擁有東部數據權限,則只能看到東部數據。

     

    1、權限聲明及應用

      在Abp中,需要首先在Core層/Authorization/PermissionNames.cs中聲明權限,Abp權限部分設計原則是:先聲明再使用

    /// <summary>
    /// 權限命名
    /// </summary>
    public static class PermissionNames
    {
        #region 頂級權限
        public const string Pages = "Pages";
        #endregion
    
        #region 基礎支撐平台
        public const string Pages_Frame = "Pages.Frame";
    
        #region 租戶管理
        public const string Pages_Frame_Tenants = "Pages.Frame.Tenants";
        #endregion
    
        #region 組織機構
        public const string Pages_Frame_OrganizationUnits = "Pages.Frame.OrganizationUnits";
        public const string Pages_Frame_OrganizationUnits_Create = "Pages.Frame.OrganizationUnits.Create";
        public const string Pages_Frame_OrganizationUnits_Update = "Pages.Frame.OrganizationUnits.Update";
        public const string Pages_Frame_OrganizationUnits_Delete = "Pages.Frame.OrganizationUnits.Delete";
        #endregion
    
        #region 用戶管理
        public const string Pages_Frame_Users = "Pages.Frame.Users";
        public const string Pages_Frame_Users_Create = "Pages.Frame.Users.Create";
        public const string Pages_Frame_Users_Update = "Pages.Frame.Users.Update";
        public const string Pages_Frame_Users_Delete = "Pages.Frame.Users.Delete";
        public const string Pages_Frame_Users_ResetPassword = "Pages.Frame.Users.ResetPassword";
        #endregion
    
        #region 角色管理
        public const string Pages_Frame_Roles = "Pages.Roles";
        public const string Pages_Frame_Roles_Create = "Pages.Frame.Roles.Create";
        public const string Pages_Frame_Roles_Update = "Pages.Frame.Roles.Update";
        public const string Pages_Frame_Roles_Delete = "Pages.Frame.Roles.Delete";
        #endregion
    
    }

      然後在Core層/Authorization/XXXAuthorizationProvider.cs中設置具體權限,在此處設置權限時,可以根據權限設計時候的職責劃分,比如如果僅僅是多租戶需要這部分,那便設置權限範圍為多租戶即可。

    public class SurroundAuthorizationProvider : AuthorizationProvider
    {
        public override void SetPermissions(IPermissionDefinitionContext context)
        {
            #region 頂級權限
            var pages = context.CreatePermission(PermissionNames.Pages, L("Pages"));
            #endregion
    
            #region 基礎支撐平台
            var frame = pages.CreateChildPermission(PermissionNames.Pages_Frame, L("Frame"));
    
            #region 租戶管理
            frame.CreateChildPermission(PermissionNames.Pages_Frame_Tenants, L("Tenants"), multiTenancySides: MultiTenancySides.Host);
            #endregion
    
            #region 組織機構
            var organizationUnits = frame.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits, L("OrganizationUnits"));
            organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Create, L("CreateOrganizationUnit"));
            organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Update, L("EditOrganizationUnit"));
            organizationUnits.CreateChildPermission(PermissionNames.Pages_Frame_OrganizationUnits_Delete, L("DeleteOrganizationUnit"));
            #endregion
    
            #region 用戶管理
            var users = frame.CreateChildPermission(PermissionNames.Pages_Frame_Users, L("Users"));
            users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Create, L("CreateUser"));
            users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Update, L("UpdateUser"));
            users.CreateChildPermission(PermissionNames.Pages_Frame_Users_Delete, L("DeleteUser"));
            users.CreateChildPermission(PermissionNames.Pages_Frame_Users_ResetPassword, L("ResetPassword"));
            #endregion
    
            #region 角色管理
            var roles = frame.CreateChildPermission(PermissionNames.Pages_Frame_Roles, L("Roles"));
            roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Create, L("CreateRole"));
            roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Update, L("UpdateRole"));
            roles.CreateChildPermission(PermissionNames.Pages_Frame_Roles_Delete, L("DeleteRole"));
            #endregion
        }
    }

      在設置完畢后,需要將該類集成到Core層/XXXCoreModule當前模塊中,才能使得該部分權限設置生效。

    //配置權限管理
    Configuration.Authorization.Providers.Add<SurroundAuthorizationProvider>();

       作為業務的入口,菜單是較為直觀的體現方式,現在可以,為菜單分配權限了,擁有權限的人才能看的到菜單,同時後台方法中也要有權限判定,菜單僅作為前端入口上的控制,權限判定作為後端的控制。在MVC層的Startup/XXXNavigationProvider.cs中完成菜單的配置工作,可以配置多級菜單,每個菜單可以配置相應的權限,在生成菜單判定時,如果父級菜單權限不足,則直接會跳過子級菜單的判定。

    new MenuItemDefinition(//基礎支撐
        PageNames.FrameManage,
        L(PageNames.FrameManage),
        icon: "&#xe828;",
        requiredPermissionName: PermissionNames.Pages_Frame
    ).AddItem(
        new MenuItemDefinition(//組織機構
            PageNames.OrganizationUnits,
            L(PageNames.OrganizationUnits),
            url: "/OrganizationUnits",
            icon: "&#xe6cb;",
            requiredPermissionName: PermissionNames.Pages_Frame_OrganizationUnits
        )
    ).AddItem(
        new MenuItemDefinition(//用戶管理
            PageNames.Users,
            L(PageNames.Users),
            url: "/Users",
            icon: "&#xe6cb;",
            requiredPermissionName: PermissionNames.Pages_Frame_Users
        )
    ).AddItem(
        new MenuItemDefinition(//角色管理
            PageNames.Roles,
            L(PageNames.Roles),
            url: "/Roles",
            icon: "&#xe6cb;",
            requiredPermissionName: PermissionNames.Pages_Frame_Roles
        )
    ).AddItem(
        new MenuItemDefinition(//系統設置
            PageNames.HostSettings,
            L(PageNames.HostSettings),
            url: "/HostSettings",
            icon: "&#xe6cb;",
            requiredPermissionName: PermissionNames.Pages_Frame_HostSettings
        )
    )

      在前端頁面上,對於按鈕級別的控制也通過權限判定,Abp提供了判定方法,利用Razor語法進行按鈕控制

    @if (await PermissionChecker.IsGrantedAsync(PermissionNames.Pages_Core_DataDictionary_Create))
    {
        <button class="layui-btn layuiadmin-btn-dataDictionary" data-type="addDataDictionary">添加類型</button>
    }

      在後端方法上,通常我喜歡直接在應用服務中的方法上做權限判定(當然也可以前移到MVC層,但是這樣一來,針對於WebApi形式的Host層,又得多加一次判定了),利用AbpAuthorize特性,判定該方法需要哪幾個權限才能訪問,而在mvc的控制器上做訪問認證。

    [AbpAuthorize(PermissionNames.Pages_Core_DataDictionary_Create)]
    private async Task CreateDataDictionaryAsync(CreateOrUpdateDataDictionaryInput input)
    {
    
    }

     

    2、角色與權限

       在Abp中,角色信息存儲在abprole表中,角色與權限間的關聯存儲在abppermission這張表中,一個角色有多個權限,如果某個角色的權限被去掉了,這張表中的相關記錄將由abp負責刪除,我們只需要完成掌控哪些權限是這個角色有的就行。Abp中已經完成了角色的所有操作,但是前端部分採用的是bootstrap弄的,將其改造一波,成為layui風格。

      

      在創建角色中,主要是將選中的權限掛鈎到具體的某個角色上,該部分代碼沿用abp中自帶的角色權限處理方法。

    private async Task CreateRole(CreateOrUpdateRoleInput input)
    {
        var role = ObjectMapper.Map<Role>(input.Role);
        role.SetNormalizedName();
    
        CheckErrors(await _roleManager.CreateAsync(role));
    
        var grantedPermissions = PermissionManager
            .GetAllPermissions()
            .Where(p => input.PermissionNames.Contains(p.Name))
            .ToList();
    
        await _roleManager.SetGrantedPermissionsAsync(role, grantedPermissions);
    }

      指定角色Id,租戶Id及之前聲明的權限名稱,在abppermission中可查看到具體角色權限。

      

     

    3、用戶與角色

       一個用戶可以承擔多個角色,履行不同角色的義務,作為一個業務系統最基本的單元,abp中提供了這些概念並在Module Zero模塊中已經完成了對用戶的一系列操作,用戶信息存儲在AbpUsers表中,用戶直接關聯的角色保存在AbpUserRoles表中,abp中MVC版本採用的是bootstrap風格,因此,用layui風格完成一次替換,並且,改動一些頁面布局。

      

      Abp版本中,由於是土耳其大佬所開發的習慣,針對於姓和名做了拆分,因此對於我們的使用要做一次處理,我這先簡單處理了一下,並且在業務系統中,郵箱時有時無,因此也需要進行考慮。

    [AbpAuthorize(PermissionNames.Pages_Frame_Users_Create)]
    private async Task CreateUser(CreateOrUpdateUserInput input)
    {
        var user = ObjectMapper.Map<User>(input.User);
        user.TenantId = AbpSession.TenantId;
        user.IsEmailConfirmed = true;
        user.Name = "Name";
        user.Surname = "Surname";
        //user.EmailAddress = string.Empty;
    
        await UserManager.InitializeOptionsAsync(AbpSession.TenantId);
        foreach (var validator in _passwordValidators)
        {
            CheckErrors(await validator.ValidateAsync(UserManager, user, AppConsts.DefaultPassword));
        }
    
        user.Password = _passwordHasher.HashPassword(user, AppConsts.DefaultPassword);
    
        await _userManager.InitializeOptionsAsync(AbpSession.TenantId);
    
        CheckErrors(await _userManager.CreateAsync(user, AppConsts.DefaultPassword));
    
        if (input.AssignedRoleNames != null)
        {
            CheckErrors(await _userManager.SetRoles(user, input.AssignedRoleNames));
        }
    
        if (input.OrganizationUnitIds != null)
        {
            await _userManager.SetOrganizationUnitsAsync(user, input.OrganizationUnitIds);
        }
    
        CurrentUnitOfWork.SaveChanges();
    }

      此處對用戶個人單獨的權限沒有去做處理,依照Abp的文檔有那麼一句話,大多數應用程序中,基於角色的已經足夠使用了,如果想聲明特定權限給用戶,那麼針對於用戶本身的角色權限則被覆蓋。    

     

     至此,修改整合用戶、角色和權限加入到系統中初步完成了,至於一些更為豐富的功能,待逐步加入中,車子再好,司機也得睡覺。

     

     倉庫地址:

    2019-11-17,望技術有成后能回來看見自己的腳步

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 【集合系列】- 紅黑樹實現分析

    【集合系列】- 紅黑樹實現分析

    一、故事的起因

    JDK1.8最重要的就是引入了紅黑樹的設計(當衝突的鏈表長度超過8個的時候),為什麼要這樣設計呢?好處就是避免在最極端的情況下衝突鏈表變得很長很長,在查詢的時候,效率會非常慢。

    • 紅黑樹查詢:其訪問性能近似於折半查找,時間複雜度O(logn);
    • 鏈表查詢:這種情況下,需要遍歷全部元素才行,時間複雜度O(n);

    本文主要是講解紅黑樹的實現,只有充分理解了紅黑樹,對於後面的分析才會更加順利。

    簡單的說,紅黑樹是一種近似平衡的二叉查找樹,其主要的優點就是“平衡“,即左右子樹高度幾乎一致,以此來防止樹退化為鏈表,通過這種方式來保障查找的時間複雜度為log(n)。

    關於紅黑樹的內容,網上給出的內容非常多,主要有以下幾個特性:

    • 1、每個節點要麼是紅色,要麼是黑色,但根節點永遠是黑色的;
    • 2、每個紅色節點的兩個子節點一定都是黑色;
    • 3、紅色節點不能連續(也即是,紅色節點的孩子和父親都不能是紅色);
    • 4、從任一節點到其子樹中每個恭弘=叶 恭弘子節點的路徑都包含相同數量的黑色節點;
    • 5、所有的恭弘=叶 恭弘節點都是是黑色的(注意這裏說恭弘=叶 恭弘子節點其實是上圖中的 NIL 節點);

    在樹的結構發生改變時(插入或者刪除操作),往往會破壞上述條件3或條件4,需要通過調整使得查找樹重新滿足紅黑樹的條件。

    二、調整方式

    上面已經說到當樹的結構發生改變時,紅黑樹的條件可能被破壞,需要通過調整使得查找樹重新滿足紅黑樹的條件。

    調整可以分為兩類:一類是顏色調整,即改變某個節點的顏色,這種比較簡單,直接將節點顏色進行轉換即可;另一類是結構調整,改變檢索樹的結構關係。結構調整主要包含兩個基本操作:左旋(Rotate Left)右旋(RotateRight)

    2.1、左旋

    左旋的過程是將p的右子樹繞p逆時針旋轉,使得p的右子樹成為p的父親,同時修改相關節點的引用,使左子樹的深度加1,右子樹的深度減1,通過這種做法來調整樹的穩定性。過程如下:

    以jdk1.8為例,打開HashMap的源碼部分,紅黑樹內部類TreeNode屬性分析:

    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
            //指向父節點的指針
            TreeNode<K,V> parent;
            //指向左孩子的指針
            TreeNode<K,V> left;
            //指向右孩子的指針
            TreeNode<K,V> right;
            //前驅指針,跟next屬性相反的指向
            TreeNode<K,V> prev;
            //是否為紅色節點
            boolean red;
            ......
    }

    左旋方法rotateLeft如下:

    /*
     * 左旋邏輯
     */
    static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                                  TreeNode<K,V> p) {
                //root:表示根節點
                //p:表示要調整的節點
                //r:表示p的右節點
                //pp:表示p的parent節點
                //rl:表示p的右孩子的左孩子節點
                TreeNode<K,V> r, pp, rl;
                //r判斷,如果r為空則旋轉沒有意義
                if (p != null && (r = p.right) != null) {
                    //多個等號的連接操作從右往左看,設置rl的父親為p
                    if ((rl = p.right = r.left) != null)
                        rl.parent = p;
                    //判斷p的父親,為空,為根節點,根節點的話就設置為黑色
                    if ((pp = r.parent = p.parent) == null)
                        (root = r).red = false;
                    //判斷p節點是左兒子還是右兒子
                    else if (pp.left == p)
                        pp.left = r;
                    else
                        pp.right = r;
                    r.left = p;
                    p.parent = r;
                }
                return root;
    }

    2.2、右旋

    了解了左旋轉之後,相應的就會有右旋,邏輯基本也是一樣,只是方向變了。右旋的過程是將p的左子樹繞p順時針旋轉,使得p的左子樹成為p的父親,同時修改相關節點的引用,使右子樹的深度加1,左子樹的深度減1,通過這種做法來調整樹的穩定性。實現過程如下:

    同樣的,右旋方法rotateRight如下:

    /*
     * 右旋邏輯
     */
    static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                                   TreeNode<K,V> p) {
                //root:表示根節點
                //p:表示要調整的節點
                //l:表示p的左節點
                //pp:表示p的parent節點
                //lr:表示p的左孩子的右孩子節點
                TreeNode<K,V> l, pp, lr;
                //l判斷,如果l為空則旋轉沒有意義
                if (p != null && (l = p.left) != null) {
                    //多個等號的連接操作從右往左看,設置lr的父親為p
                    if ((lr = p.left = l.right) != null)
                        lr.parent = p;
                    //判斷p的父親,為空,為根節點,根節點的話就設置為黑色
                    if ((pp = l.parent = p.parent) == null)
                        (root = l).red = false;
                    //判斷p節點是右兒子還是左兒子
                    else if (pp.right == p)
                        pp.right = l;
                    else
                        pp.left = l;
                    l.right = p;
                    p.parent = l;
                }
                return root;
    }

    三、操作示例介紹

    3.1、插入調整過程圖解

    3.2、刪除調整過程圖解

    3.3、查詢過程圖解

    四、總結

    至此,紅黑樹的實現就基本完成了,關於紅黑樹的結構,有很多種情況,情況也比較複雜,但是整體調整流程,基本都是先調整結構然後調整顏色,直到最後滿足紅黑樹特性要求為止。整篇文章,如果有理解不當之處,歡迎指正!

    五、參考

    1、
    2、

    作者:炸雞可樂
    出處:

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • Webpack 4 Tree Shaking 終極優化指南

    Webpack 4 Tree Shaking 終極優化指南

    幾個月前,我的任務是將我們組的 Vue.js 項目構建配置升級到 Webpack 4。我們的主要目標之一是利用 tree-shaking 的優勢,即 Webpack 去掉了實際上並沒有使用的代碼來減少包的大小。現在,tree-shaking 的好處將根據你的代碼庫而有所不同。由於我們的幾個架構決策,我們從公司內部的其他庫中提取了大量代碼,而我們只使用了其中的一小部分。

    我寫這篇文章是因為恰當地優化 Webpack 並不簡單。一開始我以為這是一種簡單的魔法,但後來我花了一個月的時間在網上搜索我遇到的一系列問題的答案。我希望通過這篇文章,其他人會更容易地處理類似問題。

    先說好處

    在討論技術細節之前,讓我先總結一下好處。不同的應用程序將看到不同程度的好處。主要的決定因素是應用程序中死代碼的數量。如果你沒有多少死代碼,那麼你就看不到 tree-shaking 的多少好處。我們項目里有很多死代碼。

    在我們部門,最大的問題是共享庫的數量。從簡單的自定義組件庫,到企業標準組件庫,再到莫名其妙地塞到一個庫中的大量代碼。很多都是技術債務,但一個大問題是我們所有的應用程序都在導入所有這些庫,而實際上每個應用程序都只需要其中的一小部分

    總的來說,一旦實現了 tree-shaking,我們的應用程序就會根據應用程序的不同,縮減率從25%到75%。平均縮減率為52%,主要是由這些龐大的共享庫驅動的,它們是小型應用程序中的主要代碼。

    同樣,具體情況會有所不同,但是如果你覺得你打的包中可能有很多不需要的代碼,這就是如何消除它們的方法。

    沒有示例代碼倉庫

    對不住了各位老鐵,我做的項目是公司的財產,所以我不能分享代碼到 GitHub 倉庫了。但是,我將在本文中提供簡化的代碼示例來說明我的觀點。

    因此,廢話少說,讓我們來看看如何編寫可實現 tree-shaking 的最佳 webpack 4 配置。

    什麼是死代碼

    很簡單:就是 Webpack 沒看到你使用的代碼。Webpack 跟蹤整個應用程序的 import/export 語句,因此,如果它看到導入的東西最終沒有被使用,它會認為那是“死代碼”,並會對其進行 tree-shaking 。

    死代碼並不總是那麼明確的。下面是一些死代碼和“活”代碼的例子,希望能讓你更明白。請記住,在某些情況下,Webpack 會將某些東西視為死代碼,儘管它實際上並不是。請參閱《副作用》一節,了解如何處理。

    // 導入並賦值給 JavaScript 對象,然後在下面的代碼中被用到
    // 這會被看作“活”代碼,不會做 tree-shaking
    import Stuff from './stuff';
    doSomething(Stuff);
    // 導入並賦值給 JavaScript 對象,但在接下來的代碼里沒有用到
    // 這就會被當做“死”代碼,會被 tree-shaking
    import Stuff from './stuff';
    doSomething();
    // 導入但沒有賦值給 JavaScript 對象,也沒有在代碼里用到
    // 這會被當做“死”代碼,會被 tree-shaking
    import './stuff';
    doSomething();
    // 導入整個庫,但是沒有賦值給 JavaScript 對象,也沒有在代碼里用到
    // 非常奇怪,這竟然被當做“活”代碼,因為 Webpack 對庫的導入和本地代碼導入的處理方式不同。
    import 'my-lib';
    doSomething();

    用支持 tree-shaking 的方式寫 import

    在編寫支持 tree-shaking 的代碼時,導入方式非常重要。你應該避免將整個庫導入到單個 JavaScript 對象中。當你這樣做時,你是在告訴 Webpack 你需要整個庫, Webpack 就不會搖它。

    以流行的庫 Lodash 為例。一次導入整個庫是一個很大的錯誤,但是導入單個的模塊要好得多。當然,Lodash 還需要其他的步驟來做 tree-shaking,但這是個很好的起點。

    // 全部導入 (不支持 tree-shaking)
    import _ from 'lodash';
    // 具名導入(支持 tree-shaking)
    import { debounce } from 'lodash';
    // 直接導入具體的模塊 (支持 tree-shaking)
    import debounce from 'lodash/lib/debounce';

    基本的 Webpack 配置

    使用 Webpack 進行 tree-shaking 的第一步是編寫 Webpack 配置文件。你可以對你的 webpack 做很多自定義配置,但是如果你想要對代碼進行 tree-shaking,就需要以下幾項。

    首先,你必須處於生產模式。Webpack 只有在壓縮代碼的時候會 tree-shaking,而這隻會發生在生產模式中。

    其次,必須將優化選項 “usedExports” 設置為true。這意味着 Webpack 將識別出它認為沒有被使用的代碼,並在最初的打包步驟中給它做標記。

    最後,你需要使用一個支持刪除死代碼的壓縮器。這種壓縮器將識別出 Webpack 是如何標記它認為沒有被使用的代碼,並將其剝離。TerserPlugin 支持這個功能,推薦使用。

    下面是 Webpack 開啟 tree-shaking 的基本配置:

    // Base Webpack Config for Tree Shaking
    const config = {
     mode: 'production',
     optimization: {
      usedExports: true,
      minimizer: [
       new TerserPlugin({...})
      ]
     }
    };

    有什麼副作用

    僅僅因為 Webpack 看不到一段正在使用的代碼,並不意味着它可以安全地進行 tree-shaking。有些模塊導入,只要被引入,就會對應用程序產生重要的影響。一個很好的例子就是全局樣式表,或者設置全局配置的JavaScript 文件。

    Webpack 認為這樣的文件有“副作用”。具有副作用的文件不應該做 tree-shaking,因為這將破壞整個應用程序。Webpack 的設計者清楚地認識到不知道哪些文件有副作用的情況下打包代碼的風險,因此默認地將所有代碼視為有副作用。這可以保護你免於刪除必要的文件,但這意味着 Webpack 的默認行為實際上是不進行 tree-shaking。

    幸運的是,我們可以配置我們的項目,告訴 Webpack 它是沒有副作用的,可以進行 tree-shaking。

    如何告訴 Webpack 你的代碼無副作用

    package.json 有一個特殊的屬性 sideEffects,就是為此而存在的。它有三個可能的值:

    true 是默認值,如果不指定其他值的話。這意味着所有的文件都有副作用,也就是沒有一個文件可以 tree-shaking。

    false 告訴 Webpack 沒有文件有副作用,所有文件都可以 tree-shaking。

    第三個值 […] 是文件路徑數組。它告訴 webpack,除了數組中包含的文件外,你的任何文件都沒有副作用。因此,除了指定的文件之外,其他文件都可以安全地進行 tree-shaking。

    每個項目都必須將 sideEffects 屬性設置為 false 或文件路徑數組。在我公司的工作中,我們的基本應用程序和我提到的所有共享庫都需要正確配置 sideEffects 標誌。

    下面是 sideEffects 標誌的一些代碼示例。儘管有 JavaScript 註釋,但這是 JSON 代碼:

    // 所有文件都有副作用,全都不可 tree-shaking
    {
     "sideEffects": true
    }
    // 沒有文件有副作用,全都可以 tree-shaking
    {
     "sideEffects": false
    }
    // 只有這些文件有副作用,所有其他文件都可以 tree-shaking,但會保留這些文件
    {
     "sideEffects": [
      "./src/file1.js",
      "./src/file2.js"
     ]
    }

    全局 CSS 與副作用

    首先,讓我們在這個上下文中定義全局 CSS。全局 CSS 是直接導入到 JavaScript 文件中的樣式表(可以是CSS、SCSS等)。它沒有被轉換成 CSS 模塊或任何類似的東西。基本上,import 語句是這樣的:

    // 導入全局 CSS
    import './MyStylesheet.css';

    因此,如果你做了上面提到的副作用更改,那麼在運行 webpack 構建時,你將立即注意到一個棘手的問題。以上述方式導入的任何樣式表現在都將從輸出中刪除。這是因為這樣的導入被 webpack 視為死代碼,並被刪除。

    幸運的是,有一個簡單的解決方案可以解決這個問題。Webpack 使用它的模塊規則系統來控制各種類型文件的加載。每種文件類型的每個規則都有自己的 sideEffects 標誌。這會覆蓋之前為匹配規則的文件設置的所有 sideEffects 標誌。

    所以,為了保留全局 CSS 文件,我們只需要設置這個特殊的 sideEffects 標誌為 true,就像這樣:

    // 全局 CSS 副作用規則相關的 Webpack 配置
    const config = {
     module: {
      rules: [
       {
        test: /regex/,
        use: [loaders],
        sideEffects: true
       }
      ]
     } 
    };

    Webpack 的所有模塊規則上都有這個屬性。處理全局樣式表的規則必須用上它,包括但不限於 CSS/SCSS/LESS/等等。

    什麼是模塊,模塊為什麼重要

    現在我們開始進入秘境。表面上看,編譯出正確的模塊類型似乎是一個簡單的步驟,但是正如下面幾節將要解釋的,這是一個會導致許多複雜問題的領域。這是我花了很長時間才弄明白的部分。

    首先,我們需要了解一下模塊。多年來,JavaScript 已經發展出了在文件之間以“模塊”的形式有效導入/導出代碼的能力。有許多不同的 JavaScript 模塊標準已經存在了多年,但是為了本文的目的,我們將重點關注兩個標準。一個是 “commonjs”,另一個是 “es2015”。下面是它們的代碼形式:

    // Commonjs
    const stuff = require('./stuff');
    module.exports = stuff;
    
    // es2015 
    import stuff from './stuff';
    export default stuff;

    默認情況下,Babel 假定我們使用 es2015 模塊編寫代碼,並轉換 JavaScript 代碼以使用 commonjs 模塊。這樣做是為了與服務器端 JavaScript 庫的廣泛兼容性,這些 JavaScript 庫通常構建在 NodeJS 之上(NodeJS 只支持 commonjs 模塊)。但是,Webpack 不支持使用 commonjs 模塊來完成 tree-shaking。

    現在,有一些插件(如 common-shake-plugin)聲稱可以讓 Webpack 有能力對 commonjs 模塊進行 tree-shaking,但根據我的經驗,這些插件要麼不起作用,要麼在 es2015 模塊上運行時,對 tree-shaking 的影響微乎其微。我不推薦這些插件。

    因此,為了進行 tree-shaking,我們需要將代碼編譯到 es2015 模塊。

    es2015 模塊 Babel 配置

    據我所知,Babel 不支持將其他模塊系統編譯成 es2015 模塊。但是,如果你是前端開發人員,那麼你可能已經在使用 es2015 模塊編寫代碼了,因為這是全面推薦的方法。

    因此,為了讓我們編譯的代碼使用 es2015 模塊,我們需要做的就是告訴 babel 不要管它們。為了實現這一點,我們只需將以下內容添加到我們的 babel.config.js 中(在本文中,你會看到我更喜歡JavaScript 配置而不是 JSON 配置):

    // es2015 模塊的基本 Babel 配置
    const config = {
     presets: [
      [
       '[@babel/preset-env](http://twitter.com/babel/preset-env)',
       {
        modules: false
       }
      ]
     ]
    };

    modules 設置為 false,就是告訴 babel 不要編譯模塊代碼。這會讓 Babel 保留我們現有的 es2015 import/export 語句。

    划重點:所有可需要 tree-shaking 的代碼必須以這種方式編譯。因此,如果你有要導入的庫,則必須將這些庫編譯為 es2015 模塊以便進行 tree-shaking 。如果它們被編譯為 commonjs,那麼它們就不能做 tree-shaking ,並且將會被打包進你的應用程序中。許多庫支持部分導入,lodash 就是一個很好的例子,它本身是 commonjs 模塊,但是它有一個 lodash-es 版本,用的是 es2015模塊。

    此外,如果你在應用程序中使用內部庫,也必須使用 es2015 模塊編譯。為了減少應用程序包的大小,必須將所有這些內部庫修改為以這種方式編譯。

    不好意思, Jest 罷工了

    其他測試框架情況類似,我們用的是 Jest。

    不管怎麼樣,如果你走到了這一步,你會發現 Jest 測試開始失敗了。你會像我當時一樣,看到日誌里出現各種奇怪的錯誤,慌的一批。別慌,我會帶你一步一步解決。

    出現這個結果的原因很簡單:NodeJS。Jest 是基於 NodeJS 開發的,而 NodeJS 不支持 es2015 模塊。為此有一些方法可以配置 Node,但是在 jest 上行不通。因此,我們卡在這裏了:Webpack 需要 es2015 進行 tree shaking,但是 Jest 無法在這些模塊上執行測試。

    就是為什麼我說進入了模塊系統的“秘境”。這是整個過程中耗費我最多時間來搞清楚的部分。建議你仔細閱讀這一節和後面幾節,因為我會給出解決方案。

    解決方案有兩個主要部分。第一部分針對項目本身的代碼,也就是跑測試的代碼。這部分比較容易。第二部分針對庫代碼,也就是來自其他項目,被編譯成 es2015 模塊並引入到當前項目的代碼。這部分比較複雜。

    解決項目本地 Jest 代碼

    針對我們的問題,babel 有一個很有用的特性:環境選項。通過配置可以運行在不同環境。在這裏,開發和生產環境我們需要 es2015 模塊,而測試環境需要 commonjs 模塊。還好,Babel 配置起來非常容易:

    // 分環境配置Babel 
    const config = {
     env: {
      development: {
       presets: [
        [
         '[@babel/preset-env](http://twitter.com/babel/preset-env)',
         {
          modules: false
         }
        ]
       ]
      },
      production: {
       presets: [
        [
         '[@babel/preset-env](http://twitter.com/babel/preset-env)',
         {
          modules: false
         }
        ]
       ]
      },
      test: {
       presets: [
        [
         '[@babel/preset-env](http://twitter.com/babel/preset-env)',
         {
          modules: 'commonjs'
         }
        ]
       ],
       plugins: [
        'transform-es2015-modules-commonjs' // Not sure this is required, but I had added it anyway
       ]
      }
     }
    };

    設置好之後,所有的項目本地代碼能夠正常編譯,Jest 測試能運行了。但是,使用 es2015 模塊的第三方庫代碼依然不能運行。

    解決 Jest 中的庫代碼

    庫代碼運行出錯的原因非常明顯,看一眼node_modules 目錄就明白了。這裏的庫代碼用的是 es2015 模塊語法,為了進行 tree-shaking。這些庫已經採用這種方式編譯過了,因此當 Jest 在單元測試中試圖讀取這些代碼時,就炸了。注意到沒有,我們已經讓 Babel 在測試環境中啟用 commonjs 模塊了呀,為什麼對這些庫不起作用呢?這是因為,Jest (尤其是 babel-jest) 在跑測試之前編譯代碼的時候,默認忽略任何來自node_modules 的代碼。

    這實際上是件好事。如果 Jest 需要重新編譯所有庫的話,將會大大增加測試處理時間。然而,雖然我們不想讓它重新編譯所有代碼,但我們希望它重新編譯使用 es2015 模塊的庫,這樣才能在單元測試里使用。

    幸好,Jest 在它的配置中為我們提供了解決方案。我想說,這部分確實讓我想了很久,並且我感覺沒必要搞得這麼複雜,但這是我能想到的唯一解決方案。

    配置 Jest 重新編譯庫代碼

    // 重新編譯庫代碼的 Jest 配置 
    const path = require('path');
    const librariesToRecompile = [
     'Library1',
     'Library2'
    ].join('|');
    const config = {
     transformIgnorePatterns: [
      `[\\\/]node_modules[\\\/](?!(${librariesToRecompile})).*$`
     ],
     transform: {
      '^.+\.jsx?$': path.resolve(__dirname, 'transformer.js')
     }
    };

    以上配置是 Jest 重新編譯你的庫所需要的。有兩個主要部分,我會一一解釋。

    transformIgnorePatterns 是 Jest 配置的一個功能,它是一個正則字符串數組。任何匹配這些正則表達式的代碼,都不會被 babel-jest 重新編譯。默認是一個字符串“node_modules”。這就是為什麼Jest 不會重新編譯任何庫代碼。

    當我們提供了自定義配置,就是告訴 Jest 重新編譯的時候如何忽略代碼。也就是為什麼你剛才看到的變態的正則表達式有一個負向先行斷言在裏面,目的是為了匹配除了庫以外的所有代碼。換句話說,我們告訴 Jest 忽略 node_modules 中除了指定庫之外的所有代碼。

    這又一次證明了 JavaScript 配置比 JSON 配置要好,因為我可以輕鬆地通過字符串操作,往正則表達式里插入庫名字的數組拼接。

    第二個是 transform 配置,他指向一個自定義的 babel-jest 轉換器。我不敢100%確定這個是必須的,但我還是加上了。設置它用於在重新編譯所有代碼時加載我們的 Babel 配置。

    // Babel-Jest 轉換器
    const babelJest = require('babel-jest');
    const path = require('path');
    const cwd = process.cwd();
    const babelConfig = require(path.resolve(cwd, 'babel.config'));
    module.exports = babelJest.createTransformer(babelConfig);

    這些都配置好后,你在測試代碼應該又能跑了。記住了,任何使用庫的 es2015 模塊都需要這樣配置,不然測試代碼跑不動。

    Npm/Yarn Link 就是魔鬼

    接下來輪到另一個痛點了:鏈接庫。使用 npm/yarn 鏈接的過程就是創建一個指向本地項目目錄的符號鏈接。結果表明,Babel 在重新編譯通過這種方式鏈接的庫時,會拋出很多錯誤。我之所以花了這麼長時間才弄清楚 Jest 這檔子事兒,原因之一就是我一直通過這種方式鏈接我的庫,出現了一堆錯誤。

    解決辦法就是:不要使用 npm/yarn link。用類似 “yalc” 這樣的工具,它可以連接本地項目,同時能模擬正常的 npm 安裝過程。它不但沒有 Babel 重編譯的問題,還能更好地處理傳遞性依賴。

    針對特定庫的優化。

    如果完成了以上所有步驟,你的應用基本上實現了比較健壯的 tree shaking。不過,為了進一步減少文件包大小,你還可以做一些事情。我會列舉一些特定庫的優化方法,但這絕對不是全部。它尤其能為我們提供靈感,做出一些更酷的事情。

    MomentJS 是出了名的大體積庫。幸好它可以剔除多語言包來減少體積。在下面的代碼示例中,我排除了 momentjs 所有的多語言包,只保留了基本部分,體積明顯小了很多。

    // 用 IgnorePlugin 移除多語言包
    const { IgnorePlugin } from 'webpack';
    const config = {
     plugins: [
      new IgnorePlugin(/^\.\/locale$/, /moment/)
     ]
    };

    Moment-Timezone 是 MomentJS 的老表,也是個大塊頭。它的體積基本上是一個帶有時區信息的超大 JSON 文件導致的。我發現只要保留本世紀的年份數據,就可以將體積縮小90%。這種情況需要用到一個特殊的 Webpack 插件。

    // MomentTimezone Webpack Plugin
    const MomentTimezoneDataPlugin = require('moment-timezone-data-webpack-plugin');
    const config = {
     plugins: [
      new MomentTimezoneDataPlugin({
       startYear: 2018,
       endYear: 2100
      })
     ]
    };

    Lodash 是另一個導致文件包膨脹的大塊頭。幸好有一個替代包 Lodash-es,它被編譯成 es2015 模塊,並帶有 sideEffects 標誌。用它替換 Lodash 可以進一步縮減包的大小。

    另外,Lodash-es,react-bootstrap 以及其他庫可以在 Babel transform imports 插件的幫助下實現瘦身。該插件從庫的 index.js 文件讀取 import 語句,並使其指向庫中特定文件。這樣就使 webpack 在解析模塊樹時更容易對庫做 tree shaking。下面的例子演示了它是如何工作的。

    // Babel Transform Imports
    // Babel config
    const config = {
     plugins: [
      [
       'transform-imports',
       {
        'lodash-es': {
         transform: 'lodash/${member}',
         preventFullImport: true
        },
        'react-bootstrap': {
         transform: 'react-bootstrap/es/${member}', // The es folder contains es2015 module versions of the files
         preventFullImport: true
        }
       }
      ]
     ]
    };
    // 這些庫不再支持全量導入,否則會報錯
    import _ from 'lodash-es';
    // 具名導入依然支持
    import { debounce } from 'loash-es';
    // 不過這些具名導入會被babel編譯成這樣子
    // import debounce from 'lodash-es/debounce';
    

    總結

    全文到此結束。這樣的優化可以極大地縮減打包后的大小。隨着前端架構開始有了新的方向(比如微前端),保持包大小最優化變得比以往更加重要。希望本文能給那些正在給應用程序做tree shaking的同學帶來一些幫助。

    交流

    歡迎掃碼關注微信公眾號“1024譯站”,為你奉上更多技術乾貨。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 上海推出新能源汽車充電樁綜合保險

    近期,上海保險業與國家電網積極合作,推出充電樁綜合保險產品。該產品包括充電樁財產保險和充電樁用電安全責任保險。充電樁財產保險保額最高1萬元,充電樁用電安全責任保險保額3萬元。

    合作初期,上海相關險企採取贈送方式,建立專項資金池,向新能源車主贈送推廣充電樁保險。同時也在國家電網營業廳設點,向預報樁客戶宣傳介紹充電樁綜合保險的相關產品和服務。

    據瞭解,2015年7月1日起,上海正式實施《上海市電動汽車充電基礎設施建設管理暫行規定》。7月1日後,上海市民必須提供政府備案的充電服務機構出具的“充電設施已裝證明”,才能在銷售企業辦理購買新能源汽車手續,必須安裝好充電樁後才能獲得國家和地方政府補貼、並申請到新能源車免費牌照。此外,《規定》也明確充電設施所有權人應當承擔充電設施維修更新養護及侵害第三者權益責任。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • 上汽、廣汽等十家車企聯合成立電動汽車產業聯盟

    近日,上汽、一汽、東風、長安、廣汽、北汽、重汽、華晨、奇瑞、江淮這十家國內車企聯合成立了“電動汽車產業聯盟”,意在聯手攻克節能與新能源汽車在產業化過程中遇到的種種難關。

    中國汽車工業協會秘書長董揚稱,聯盟是2015年7月11日成立的,銷售額排名居前十位的國內汽車企業集團“一把手”悉數參會,會上各方探討了電動汽車聯合行動的問題,並制定出針對十家企業的《電動汽車發展共同行動綱要》。

    根據綱要內容,確定了“積極引領、聯合行動、突出重點、創新發展”十六字戰略方針,並且成立了T10電動汽車領導小組、電動汽車標準項目工作組,在統一標準方面著手研究,打算如具體工業專案一樣開展工作。

    目前聯盟企業正在就關鍵零部件、關鍵總成聯合開發的問題進行調研,調研之後將形成統一規劃,今後的發展方式有可能是一部分技術難題大家共同攻關、投資生產,也有可能各自攻關,誰的成果好其他企業再去共用,儘量追求效益,聯合發展。

    據悉,今後該聯盟還將統一制定新能源汽車的相關標準,比如對電池的規格、電機以及電控系統的統一標準,並且將考慮國內的資源情況,而不是像傳統汽車那樣照搬國際標準。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

    ※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置台北網頁設計,各種案例分享

  • 日問周刊 | 全棧面試匯總 | 第二期

    日問周刊 | 全棧面試匯總 | 第二期

    勤學如春起之苗,不見其增,日有所長;輟學如磨刀之石,不見其損,日有所虧。

    我在 github 上新建了一個倉庫 ,每天至少一個問題。有關全棧,graphql,devops,微服務以及軟技能,促進職業成長,歡迎交流。

    以諸葛武侯的誡子書與君共勉

    夫君子之行,靜以修身,儉以養德。非澹泊無以明志,非寧靜無以致遠。夫學須靜也,才須學也,非學無以廣才,非志無以成學。淫慢則不能勵精,險躁則不能治性。年與時馳,意與日去,遂成枯落,多不接世,悲守窮廬,將復何及!

    【Q037】linux 有哪些發行版,你最喜歡哪一個

    原文鏈接,歡迎討論:

    開放問題,不過你至少得知道一個發行版…

    【Q036】http 狀態碼中 301,302和307有什麼區別

    原文鏈接,歡迎討論:

    • 301,Moved Permanently。永久重定向,該操作比較危險,需要謹慎操作:如果設置了301,但是一段時間后又想取消,但是瀏覽器中已經有了緩存,還是會重定向。
    • 302,Fount。臨時重定向,但是會在重定向的時候改變 method: 把 POST 改成 GET,於是有了 307
    • 307,Temporary Redirect。臨時重定向,在重定向時不會改變 method

    【Q035】http 常見的狀態碼有哪些

    原文鏈接,歡迎討論:

    【Q034】如何實現一個 loading 動畫

    原文鏈接,歡迎討論:

    【Q033】如何對接口進行限流]

    原文鏈接,歡迎討論:

    一般採用漏桶算法:

    1. 漏桶初始為空
    2. API 調用是在往漏桶里注水
    3. 漏桶會以一定速率出水
    4. 水滿時 API 拒絕調用

    可以使用 redis 的計數器實現

    1. 計數器初始為空
    2. API 調用計數器增加
    3. 給計數器設置過期時間,隔段時間清零,視為一定速率出水
    4. 計數器達到上限時,拒絕調用

    當然,這隻是大致思路,這時會有兩個問題要注意

    1. 最壞情況下的限流是額定限流速率的2倍
    2. 條件競爭問題

    不過實際實現時注意以下就好了(話說一般也是調用現成的三方庫做限流…),可以參考我以前的文章

    【Q032】js 中什麼是 softbind,如何實現

    原文鏈接,歡迎討論:

    【Q031】js 中如何實現 bind

    原文鏈接,歡迎討論:

    最簡單的 bind 一行就可以實現,而在實際面試過程中也不會考察你太多的邊界條件

    Function.prototype.fakeBind = function(obj) {
      return (...args) => this.apply(obj, args)
    }

    測試一下

    function f (arg) {
      console.log(this.a, arg)
    }
    
    // output: 3, 4
    f.bind({ a: 3 })(4)
    
    // output: 3, 4
    f.fakeBind({ a: 3 })(4)

    【Q030】linux 中如何打印所有網絡接口

    原文鏈接,歡迎討論:

    ifconfig

    ifconfig 是最簡單最常用,但是打印信息太多了

    $ ifconfig

    netstat

    netstatip 也挺好用,特別是它們還可以打印路由表

    $ netstat -i

    ip

    $ ip link
    
    $ ip addr

    【Q029】websocket 如何向特定的用戶組推送消息

    redis 處維護一個對象,記錄每個 group 所對應的 connections/sockets

    {
      'Class:201901': [student1Socket, student2Socket]
    }

    當 client 剛連入 server 時,便加入某個特定的組,或者叫 room,比如 student01,剛開始連入 server,可能要加入 room:Student:01Class:201901Group:10086

    $ who
    
    $ last

    一圖勝千言

    使用 jsonb_pretty 函數,示例如下

    > select jsonb_pretty('{"a": {"b": 4}}'::jsonb)
    +----------------+
    | jsonb_pretty   |
    |----------------|
    | {              |
    |     "a": {     |
    |         "b": 4 |
    |     }          |
    | }              |
    +----------------+
    SELECT 1
    Time: 0.018s

    一個簡單的 Promise 的粗糙實現,關鍵點在於

    1. pending 時, thenable 函數由一個隊列維護
    2. 當狀態變為 resolved(fulfilled) 時,隊列中所有 thenable 函數執行
    3. resolved 時, thenable 函數直接執行

    rejected 狀態同理

    class Prom {
      static resolve (value) {
        if (value && value.then) {
          return value 
        }
        return new Prom(resolve => resolve(value))
      }
    
      constructor (fn) {
        this.value = undefined
        this.reason = undefined
        this.status = 'PENDING'
    
        // 維護一個 resolve/pending 的函數隊列
        this.resolveFns = []
        this.rejectFns = []
    
        const resolve = (value) => {
          // 注意此處的 setTimeout
          setTimeout(() => {
            this.status = 'RESOLVED'
            this.value = value
            this.resolveFns.forEach(({ fn, resolve: res, reject: rej }) => res(fn(value)))
          })
        }
    
        const reject = (e) => {
          setTimeout(() => {
            this.status = 'REJECTED'
            this.reason = e
            this.rejectFns.forEach(({ fn, resolve: res, reject: rej }) => rej(fn(e)))
          })
        }
    
        fn(resolve, reject)
      }
    
    
      then (fn) {
        if (this.status === 'RESOLVED') {
          const result = fn(this.value)
          // 需要返回一個 Promise
          // 如果狀態為 resolved,直接執行
          return Prom.resolve(result)
        }
        if (this.status === 'PENDING') {
          // 也是返回一個 Promise
          return new Prom((resolve, reject) => {
            // 推進隊列中,resolved 后統一執行
            this.resolveFns.push({ fn, resolve, reject }) 
          })
        }
      }
    
      catch (fn) {
        if (this.status === 'REJECTED') {
          const result = fn(this.value)
          return Prom.resolve(result)
        }
        if (this.status === 'PENDING') {
          return new Prom((resolve, reject) => {
            this.rejectFns.push({ fn, resolve, reject }) 
          })
        }
      }
    }
    
    Prom.resolve(10).then(o => o * 10).then(o => o + 10).then(o => {
      console.log(o)
    })
    
    return new Prom((resolve, reject) => reject('Error')).catch(e => {
      console.log('Error', e)
    })

    首參不一樣,直接上 API

    React.cloneElement(
      element,
      [props],
      [...children]
    )
    
    React.createElement(
      type,
      [props],
      [...children]
    )

    它一般可以使用第三方庫 來實現,源碼很簡單,可以讀一讀

    主要有兩個要點

    1. 選中
    2. 複製

    選中

    選中主要利用了

    選中的代碼如下

    const selection = window.getSelection();
    const range = document.createRange();
    
    range.selectNodeContents(element);
    selection.removeAllRanges();
    selection.addRange(range);
    
    selectedText = selection.toString();

    取消選中的代碼如下

    window.getSelection().removeAllRanges();

    它有現成的第三方庫可以使用:

    複製

    複製就比較簡單了,execCommand

    document.exec('copy')

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • 科學警訊:熱浪使熊蜂瀕臨滅絕 連帶影響農糧產量

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!