<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深入探索:用 TypeScript 手寫(xiě) 20 個(gè)常見(jiàn)數(shù)組方法

          共 9901字,需瀏覽 20分鐘

           ·

          2023-09-29 17:14

          ff211e5469a522f9c18f169a273ba766.webp

          第一步:分門(mén)別類(lèi)

          一口氣寫(xiě)出20個(gè)數(shù)組方法有點(diǎn)難度,我們可以在腦海里對(duì)數(shù)組方法進(jìn)行分類(lèi),同一類(lèi)操作歸為一類(lèi),這樣寫(xiě)是不是更加簡(jiǎn)單了呢?

          1. 添加元素類(lèi):push、unshift
          2. 刪除元素類(lèi):pop、shift、splice
          3. 數(shù)組轉(zhuǎn)字符串類(lèi):toString、join
          4. 遍歷類(lèi):forEach、reduce、reduceRight、map、filter、some、every
          5. 排序:sort
          6. 拼接:concat
          7. 索引:indexOf、lastIndexOf

          一口氣寫(xiě)了整整19個(gè),就是不夠那20個(gè),看來(lái)我不夠資格說(shuō)”前端已死“,來(lái)查一查差哪些:

          1. 翻轉(zhuǎn):reverse
          2. 淺拷貝:slice

          為什么寫(xiě)這些?因?yàn)檫@些是vscode中lib.es5.d.ts中定義的數(shù)組方法

          第二步:實(shí)現(xiàn)數(shù)組接口

          數(shù)組需要接收一個(gè)泛型參數(shù),用來(lái)動(dòng)態(tài)獲取數(shù)組中元素類(lèi)型

              
              interface MyArray<T> {
              
          }

          第三步:方法定義

          首先是元素添加類(lèi)方法:push、unshift,千萬(wàn)不要忘了他們有返回值,返回值是新數(shù)組的length

              
                push(...args: T[]): number;
            unshift(...args: T[]): number;

          刪除元素類(lèi)方法,前兩個(gè)比較好寫(xiě),它們的返回值都是刪除的那個(gè)元素,但是需要注意的是空數(shù)組調(diào)用后返回undefined;

              
               pop(): T | undefined;
           shift(): T | undefined;
           /**錯(cuò)誤的寫(xiě)法:splice(start: number, deleteNum: number, ...args: T[]): T[];**/

          splice這樣寫(xiě)還有問(wèn)題,因?yàn)閟plice只有第一個(gè)參數(shù)是必傳,這樣就需要寫(xiě)多個(gè)聲明了

              
               splice(start: number, deleteNum?: number): T[];
           splice(start: number, deleteNum: number, ...args: T[]): T[];

          然后是數(shù)組轉(zhuǎn)字符串類(lèi):toString、join,沒(méi)有難度直接寫(xiě)

              
               join(param?: string): string;
           toString(): string;

          遍歷類(lèi):forEach、reduce、reduceRight、map、filter、some、every 我們一個(gè)一個(gè)地來(lái)寫(xiě),首先是forEach方法,這個(gè)方法我們常用的就只有回調(diào)函數(shù),但是其實(shí)還有一個(gè)參數(shù)可以指定回調(diào)函數(shù)的this

              
              forEach(callbackFn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;

          reduce這個(gè)方法可以實(shí)現(xiàn)累加器,也是我們最常用的方法之一,reduceRight與reduce的區(qū)別就在于它是從右往左遍歷

              
               reduce(callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;

          map方法遍歷數(shù)組并且會(huì)返回一個(gè)新的數(shù)組,它是一個(gè)純函數(shù)

              
                map(callbackFn: (value: T, index: number, array: T[]) => T, thisArg?: any): T[];

          后面的一些遍歷方法我們就不再贅述,基本上都遵從回調(diào)函數(shù),this綁定參數(shù),這種固定模式

          后面的一些方法都比較簡(jiǎn)單,最后把寫(xiě)好的方法定義都匯總起來(lái):

              
              interface MyArray<T> {
            length: number;
            // 數(shù)組添加元素
            push(...args: T[]): number;
            unshift(...args: T[]): number;

            // 數(shù)組刪除元素
            pop(): T | undefined;
            shift(): T | undefined;
            splice(start?: number, deleteNum?: number): T[];
            splice(start: number, deleteNum?: number): T[];
            splice(start: number, deleteNum: number, ...args: T[]): T[];

            // 數(shù)組索引
            indexOf(item: T): number;
            lastIndexOf(item: T): number;

            // 數(shù)組遍歷
            forEach(callbackFn: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
            reduce(callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
            reduceRight(callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
            some(callbackFn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
            every(callbackFn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
            map(callbackFn: (value: T, index: number, array: T[]) => T, thisArg?: any): T[];

            //   數(shù)組與字符串
            join(param?: string): string;
            toString(): string;
            toLocalString(): string;

            //   數(shù)組排序
            sort(callbackFn: (a: T, b: T) => number): T[];

            // 數(shù)組扁平化
            flat(deepSize: number): T[];

            //   數(shù)組的拼接
            concat(...args: T[]): T[];

            //   數(shù)組的拷貝
            slice(start?: number, end?: number): T[];

            //   數(shù)組翻轉(zhuǎn)
            reverse(): T[];
          }

          前面都是前奏,現(xiàn)在開(kāi)始今天的正題,手寫(xiě)數(shù)組的這些方法。

          第四步 實(shí)現(xiàn)這些方法

          首先我們修改一下接口定義的名稱(chēng):IMyArray,然后定義MyArray類(lèi)實(shí)現(xiàn)該接口,編輯器會(huì)自動(dòng)將上面的方法注入

              
              class MyArray<T> implements IMyArray<T> {

          }

          先實(shí)現(xiàn)push方法:

              
              push(...args: T[]): number {
              const len = args.length;
              for (let i = 0; i < len; i++) {
                this[this.length++] = args[i];
              }
              return this.length;
          }

          其實(shí)我們實(shí)現(xiàn)的是一個(gè)類(lèi)數(shù)組,只不過(guò)含有數(shù)組的所有方法,這里經(jīng)常會(huì)使用類(lèi)數(shù)組來(lái)考察對(duì)push的理解,比如這道題:

              
              const obj = {  
              0:1,  
              3:2,  
              length:2,  
              push:[].push  
          }  
          obj.push(3);

          然后實(shí)現(xiàn)一個(gè)splice,注意splice是一個(gè)原地修改數(shù)組的方法,所以我們不能借助額外的空間實(shí)現(xiàn),這里我們還是使用Array.prototype.splice的方式來(lái)實(shí)現(xiàn),類(lèi)數(shù)組不能通過(guò)length屬性刪除元素

              
              Array.prototype.splice = function splice(start: number, deleteNum = 1, ...rest: any[]{
            if (start === undefined) {
              return [];
            }

            const that = this;
            let returnValue: any[] = [];
            // 將begin到end的元素全部往前移動(dòng)
            function moveAhead(begin: number, end: number, step: number{
              const deleteArr: any[] = [];
              // 可以從前往后遍歷
              for (let i = begin; i < end && i + step < end; i++) {
                if (i < begin + step) {
                  deleteArr.push(that[i]);
                }
                that[i] = that[i + step];
              }
              return deleteArr;
            }
            function pushAtIdx(idx: number, ...items: any[]{
              const len = items.length;
              const lenAfter = that.length;
              // 在idx處添加len個(gè)元素,首先需要把所有元素后移len位,然后替換中間那些元素
              for (let i = idx; i < idx + len; i++) {
                if (i < lenAfter) {
                  that[i + len] = that[i];
                }

                if (i - idx < len) {
                  that[i] = items[i - idx];
                }
              }
            }
            if (deleteNum >= 1) {
              returnValue = moveAhead(Math.max(start, 0), that.length, deleteNum);
              that.length -= deleteNum;
            }

            pushAtIdx(start, ...rest);
            return returnValue;
          };

          后面的實(shí)現(xiàn)我們都是用數(shù)組來(lái)實(shí)現(xiàn),比如實(shí)現(xiàn)其中某一個(gè)遍歷的方法,我們就實(shí)現(xiàn)比較復(fù)雜的比如reduce,reduce的實(shí)現(xiàn)比較簡(jiǎn)單

              
              Array.prototype.reduce = function <T>(
            callbackFn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T,
            initialValue?: T
          ): T 
          {
            // reduce如果有初始值那么以初始值開(kāi)始計(jì)算,如果沒(méi)有初始值那么用數(shù)組第一項(xiàng)作為初始值
            let startIndex = 0;
            const len = this.length;
            let ans = initialValue;
            if (initialValue === undefined) {
              ans = this[0];
              startIndex = 1;
            }

            for (let i = startIndex; i < len; i++) {
              ans = callbackFn(ans, this[i], i, this);
            }
            return ans;
          };

          然后再實(shí)現(xiàn)一個(gè)reverse數(shù)組翻轉(zhuǎn)方法,我們可以遍歷前一半的數(shù)據(jù),然后分別與后面一半進(jìn)行交換,這樣就完成了原地翻轉(zhuǎn):

              
              Array.prototype.reverse = function () {
            const len = this.length;
            const that = this;
            function swap(a, b{
              const tmp = that[a];
              that[a] = that[b];
              that[b] = tmp;
            }
            for (let i = 0; i < len >> 1; i++) {
              swap(i, len - i - 1);
            }
            return this;
          };

          至于sort和flat方法這些都有很多實(shí)現(xiàn)方式,我們可以參考一下V8官方的文檔;從文檔中我們可以發(fā)現(xiàn):

          c0ca7fa7abad5d8bf807e4b439e42a41.webp

          之前的sort方法是基于快排,并且是一種不穩(wěn)定的排序算法,后來(lái)V8將sort遷移到了Torque,tq是一種特殊的DSL,利用Timsort算法實(shí)現(xiàn)了穩(wěn)定的排序,Timsort可以看成一種穩(wěn)定的歸并排序

          總結(jié)

          我們先從數(shù)組的20個(gè)方法為切入點(diǎn),研究了這些方法的ts定義,用法,順便手寫(xiě)模擬了一下它們,然后對(duì)于比較復(fù)雜的sort算法我們了解了一下它的原理,顯然sort算法已經(jīng)不是那個(gè)以前用快排實(shí)現(xiàn)的不穩(wěn)定的排序算法了,現(xiàn)在是一種穩(wěn)定的排序算法,并且基于歸并排序,所以歸并排序我們一定要掌握好;另外這種由淺入深的學(xué)習(xí)方法,值得大家去實(shí)踐。

          作者:螞小蟻 https://juejin.cn/post/7216994437246844985

          ? ?

          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青娱乐操逼视频 | 大鸡巴免费在线观看 | 亚洲天堂网在线观看 | 操B视频网站 | 撸一撸在线视频 |