一篇搞定前端高頻手撕算法題(36道)

關注公衆號「 執鳶者」,獲取大量教學視頻及 私人總結麪筋並進入 專業交流羣.
image.png

目前互聯網行業目前正在處於內卷狀態,各個大廠不斷提升招人門檻,前端工程師找工做也愈加艱難,爲了助力各位老鐵可以在面試過程當中脫穎而出,我結合本身的面試經驗,準備了這三十六道面試過程當中的手撕算法題,與各位共享。javascript

1、冒泡排序

冒泡排序的思路:遍歷數組,而後將最大數沉到最底部;<br/> 時間複雜度:O(N^2);<br/> 空間複雜度:O(1)
function BubbleSort(arr) {
    if(arr == null || arr.length <= 0){
        return [];
    }
    var len = arr.length;
    for(var end = len - 1; end > 0; end--){
        for(var i = 0; i < end; i++) {
            if(arr[i] > arr[i + 1]){
                swap(arr, i, i + 1);
            }
        }
    }
    return arr;
}
function swap(arr, i, j){
    // var temp = arr[i];
    // arr[i] = arr[j];
    // arr[j] = temp;
    //交換也能夠用異或運算符
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

2、選擇排序

選擇排序的實現思路:遍歷數組,把最小數放在頭部;<br/> 時間複雜度:O(N^2);<br/> 空間複雜度:O(1)
function SelectionSort(arr) {
    if(arr == null || arr.length < 0) {
        return [];
    }
    for(var i = 0; i < arr.length - 1; i++) {
        var minIndex = i;
        for(var j = i + 1; j < arr.length; j++) {
            minIndex = arr[j] < arr[minIndex] ? j : minIndex;
        }
        swap(arr, i, minIndex);
    }
    return arr;
}

function swap(arr, i, j) {
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

3、插入排序

插入排序實現思路:將一個新的數,和前面的比較,只要當前數小於前一個則和前一個交換位置,不然終止;<br/> 時間複雜度:O(N^2);<br/> 空間複雜度:O(1)
function insertSort(arr) {
    if(arr == null  || arr.length <= 0){
        return [];
    }
    var len = arr.length;
    for(var i = 1; i < len; i++) {
        for(var j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
            swap(arr, j, j + 1);
        }
    }
    return arr;
}

function swap(arr, i, j){
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

4、歸併排序

歸併排序的思路:<br/>1.先左側部分排好序<br/>2.再右側部分排好序<br/>3.再準備一個輔助數組,用外排的方式,小的開始填,直到有個動到末尾,將另外一個數組剩餘部分拷貝到末尾<br/>4.再將輔助數組拷貝回原數組<br/> 時間複雜度:O(N * logN)<br/> 空間複雜度:O(N)
// 遞歸實現

function mergeSort(arr){
    if(arr == null  || arr.length <= 0){
        return [];
    }
    sortProcess(arr, 0, arr.length - 1);
    return arr;
}

function sortProcess(arr, L, R){
    //遞歸的終止條件,就是左右邊界索引同樣
    if(L == R){
        return;
    }
    var middle = L + ((R - L) >> 1);//找出中間值
    sortProcess(arr, L, middle);//對左側部分進行遞歸
    sortProcess(arr, middle + 1, R);//對右側部分進行遞歸
    merge(arr, L, middle, R);//而後利用外排方式進行結合
}

function merge(arr, L, middle, R){
    var help = [];
    var l = L;
    var r = middle + 1;
    var index = 0;
    //利用外排方式進行
    while(l <= middle && r <= R){
        help[index++] = arr[l] < arr[r] ? arr[l++] : arr[r++];
    }
    while(l <= middle){
        help.push(arr[l++]);
    }
    while(r <= R){
        help.push(arr[r++]);
    }

    for(var i = 0; i < help.length; i++) {
        arr[L + i] = help[i];
    }
    //arr.splice(L, help.length, ...help);//這個利用了ES6的語法
}
// 循環實現

function mergeSort(arr){
    if(arr ==null || arr.length <= 0){
        return [];
    }
    var len = arr.length;
    //i每次乘2,是由於每次合併之後小組元素就變成兩倍個了
    for(var i = 1; i < len; i *= 2){
        var index = 0;//第一組的起始索引
        while( 2 * i  + index <= len){
            index += 2 * i;
            merge(arr, index - 2 * i, index - i, index);
        }
        //說明剩餘兩個小組,但其中一個小組數據的數量已經不足2的冪次方個
        if(index + i < len){
            merge(arr, index, index + i, len);
        }
    }
    return arr;
}

//利用外排的方式進行結合
function merge(arr, start, mid, end){
    //新建一個輔助數組
    var help = [];
    var l = start, r = mid;
    var i = 0;
    while(l < mid && r < end){
        help[i++] = arr[l] < arr[r] ? arr[l++] : arr[r++];
    }
    while(l < mid){
        help[i++] = arr[l++];
    }
    while(r < end){
        help[i++] = arr[r++];
    }
    for(var j = 0; j < help.length; j++){
        arr[start + j] = help[j];
    }
}

5、快速排序

快速排序實現思路:隨機取出一個值進行劃分,大於該值放右邊,小於該值放左邊(該算法在經典快排的基礎上通過荷蘭國旗思想和隨機思想進行了改造)<br/> 時間複雜度:O(N*logN) <br/> 空間複雜度:O(logN)
function quickSort(arr) {
    if(arr == null || arr.length <= 0){
        return [];
    }
    quick(arr, 0, arr.length - 1);
}

function quick(arr, L, R){
    //遞歸結束條件是L >= R
    if(L < R){
        //隨機找一個值,而後和最後一個值進行交換,將經典排序變爲快速排序
        swap(arr, L + Math.floor(Math.random() * (R - L + 1)), R);
        //利用荷蘭國旗問題得到劃分的邊界,返回的值是小於區域的最大索引和大於區域的最小索引,在這利用荷蘭國旗問題將等於區域部分就不用動了
        var tempArr = partition(arr, L, R, arr[R]);
        quick(arr, L, tempArr[0]);
        quick(arr, tempArr[1], R);
    }
}
//返回值是小於區域最後的索引和大於區域的第一個索引
function partition(arr, L, R, num){
    var less = L - 1;
    var more = R + 1;
    var cur = L;
    while(cur < more){
        if(arr[cur] < num){
            swap(arr, ++less, cur++);
        }else if(arr[cur] > num) {
            swap(arr, --more, cur);
        }else{
            cur++;
        }
    }
    return [less, more];
}
function swap(arr, i, j){
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

6、堆排序

堆排序思路:<br/>1.讓數組變成大根堆<br/>2.把最後一個位置和堆頂作交換<br/>3.則最大值在最後,則剩下部分作heapify,則從新調整爲大根堆,則堆頂位置和該部分最後位置作交換<br/>4.重複進行,直到減完,則這樣最後就調整完畢,整個數組排完序(爲一個升序)<br/> 時間複雜度:O(N * logN)<br/> 空間複雜度:O(1)
function heapSort(arr) {
    if(arr == null || arr.length <= 0) {
        return [];
    }

    //首先是創建大頂堆的過程
    for(var i = 0; i < arr.length; i++) {
        heapInsert(arr, i);
    }
    var size = arr.length;//這個值用來指定多少個數組成堆,當獲得一個排序的值後這個值減一
    //將堆頂和最後一個位置交換
    /**
     * 當大頂堆創建完成後,而後不斷將最後一個位置和堆頂交換;
     * 這樣最大值就到了最後,則剩下部分作heapify,從新調整爲大根堆,則堆頂位置和倒數第二個位置交換,重複進行,直到所有排序完畢*/
    //因爲前面已是大頂堆,因此直接交換
    swap(arr, 0, --size);
    while(size > 0) {
        //從新變成大頂堆
        heapify(arr, 0, size);
        //進行交換
        swap(arr, 0, --size);
    }
}

//加堆過程當中
function heapInsert(arr, index) {
    //比較當前位置和其父位置,若大於其父位置,則進行交換,並將索引移動到其父位置進行循環,不然跳過
    //結束條件是比父位置小或者到達根節點處
    while(arr[index] > arr[parseInt((index - 1) / 2)]){
        //進行交換
        swap(arr, index, parseInt((index - 1) / 2));
        index = parseInt((index - 1) / 2);
    }
}
//減堆過程
/**
 * size指的是這個數組前多少個數構成一個堆
 * 若是你想把堆頂彈出,則把堆頂和最後一個數交換,把size減1,而後從0位置經歷一次heapify,調整一下,剩餘部分變成大頂堆*/
function heapify(arr, index, size) {
    var left = 2 * index + 1;
    while(left < size) {
        var largest = (left + 1 < size && arr[left] < arr[left + 1]) ? left + 1 : left;
        largest = arr[index] > arr[largest] ? index : largest;

        //若是最大值索引和傳進來索引同樣,則該值到達指定位置,直接結束循環
        if(index == largest) {
            break;
        }

        //進行交換,並改變索引和其左子節點
        swap(arr, index, largest);
        index = largest;
        left = 2 * index + 1;
    }
}

function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

7、桶排序

桶排序會經歷三次遍歷:準備一個數組、遍歷一遍數組、重構一遍數組,是非基於比較的排序,下面以一個問題來闡述其思路。<br/> 問題:<br/> 給定一個數組,求若是排序以後,相鄰兩個數的最大差值,要求時間複雜度O(N),且要求不能用基於比較的排序<br/>
思路:<br/>1.準備桶:數組中有N個數就準備N+1個桶<br/>2.遍歷一遍數組,找到最大值max和最小值min
。若min = max,則差值=0;若min≠max,則最小值放在0號桶,最大值放在N號桶,剩下的數屬於哪一個範圍就進哪一個桶<br/>3.根據鴿籠原理,則確定有一個桶爲空桶,設計該桶的目的是爲了否認最大值在一個桶中,則最大差值的兩個數必定來自於兩個桶,但空桶兩側並不必定是最大值<br/>4.因此只記錄全部進入該桶的最小值min和最大值max和一個布爾值表示該桶有沒有值<br/>5.而後遍歷這個數組,若是桶是空的,則跳到下一個數,若是桶非空,則找前一個非空桶,則最大差值=當前桶min - 上一個非空桶max,用全局變量更新最大值<br/> 時間複雜度:O(N)<br/> 空間複雜度:O(N)
function maxGap(arr) {
    if(arr == null || arr.length <= 0) {
        return 0;
    }
    var len = arr.length;
    var max = -Infinity, min = Infinity;
    //遍歷一遍數組,找到最大值max和最小值min
    for(var i = 0; i < len; i++) {
        max = max > arr[i] ? max : arr[i];
        min = min > arr[i] ? arr[i] : min;
    }

    //若min = max,則差值爲0;
    if(min == max) {
        return 0;
    }

    var hasNum = new Array(len + 1);
    var mins = new Array(len + 1);
    var maxs = new Array(len + 1);

    var bid = 0;//指定桶的編號

    for(var i = 0; i < len; i++) {
        bid = bucket(arr[i], min, max, len);//得到該值是在哪一個桶//因爲有N+1個桶,因此間隔就是N個,因此此處除以的是len,而後經過這個函數獲得應該放到哪一個桶裏
        maxs[bid] = hasNum[bid] ? Math.max(arr[i], maxs[bid]) : arr[i];
        mins[bid] = hasNum[bid] ? Math.min(arr[i], mins[bid]) : arr[i];
        hasNum[bid] = true;
    }

    var res = 0;
    var lastMax = maxs[0];

    for(var i = 0; i < len + 1; i++) {
        if(hasNum[i]) {
            res = Math.max(mins[i] - lastMax, res);
            lastMax = maxs[i];
        }
    }
    return res;
}

//得到桶號
//這個函數用於判斷在哪一個桶中,參數分別爲值、最小值、最大值、桶間隔
function bucket(value, min, max, len) {
    return parseInt((value - min) / ((max - min) / len));
}

8、new

function New (Fn, ...arg) {
    // 一個新的對象被建立
    const result = {};
    // 該對象的__proto__屬性指向該構造函數的原型
    if (Fn.prototype !== null) {
        Object.setPrototypeOf(result, Fn.prototype);
    }
    // 將執行上下文(this)綁定到新建立的對象中
    const returnResult = Fn.apply(result, arg);
    // 若是構造函數有返回值,那麼這個返回值將取代第一步中新建立的對象。不然返回該對象
    if ((typeof returnResult === "object" || typeof returnResult === "function") && returnResult !== null) {
        return returnResult;
    }
    return result;
}

9、instanceof

function Instanceof(left, right) {
    let leftVal = Object.getPrototypeOf(left);
    const rightVal = right.prototype;

    while (leftVal !== null) {
        if (leftVal === rightVal)
            return true;
        leftVal = Object.getPrototypeOf(leftVal);
    }
    return false;
}

10、 Object.create()

Object.ObjectCreate = (proto, propertiesObject)=> {
    // 對輸入進行檢測
    if (typeof proto !== 'object' && typeof proto !== 'function' && proto !== null) {
        throw new Error(`Object prototype may only be an Object or null:${proto}`);
    }
    // 新建一個對象
    const result = {};
    // 將該對象的原型設置爲proto
    Object.setPrototypeOf(result, proto);
    // 將屬性賦值給該對象
    Object.defineProperties(result, propertiesObject);
    // 返回該對象
    return result;
}

11、 Object assign()

function ObjectAssign(target, ...sources) {
    // 對第一個參數的判斷,不能爲undefined和null
    if (target === undefined || target === null) {
        throw new TypeError('cannot convert first argument to object');
    }

    // 將第一個參數轉換爲對象(不是對象轉換爲對象)
    const targetObj = Object(target);
    // 將源對象(source)自身的全部可枚舉屬性複製到目標對象(target)
    for (let i = 0; i < sources.length; i++) {
        let source = sources[i];
        // 對於undefined和null在源角色中不會報錯,會直接跳過
        if (source !== undefined && source !== null) {
            // 將源角色轉換成對象
            // 須要將源角色自身的可枚舉屬性(包含Symbol值的屬性)進行復制
            // Reflect.ownKeys(obj)  返回一個數組,包含對象自身的全部屬性,無論屬性名是Symbol仍是字符串,也不論是否可枚舉
            const keysArray = Reflect.ownKeys(Object(source));
            for (let nextIndex = 0; nextIndex < keysArray.length; nextIndex ++) {
                const nextKey = keysArray[nextIndex];
                // 去除不可枚舉屬性
                const desc = Object.getOwnPropertyDescriptor(source, nextKey);
                if (desc !== undefined && desc.enumerable) {
                    // 後面的屬性會覆蓋前面的屬性
                    targetObj[nextKey] = source[nextKey];
                }
            }
        }
    }

    return targetObj;
}
// 因爲掛載到Object的assign是不可枚舉的,直接掛載上去是可枚舉的,因此採用這種方式
if (typeof Object.myAssign !== 'function') {
    Object.defineProperty(Object, "myAssign", {
        value : ObjectAssign,
        writable: true,
        enumerable: false,
        configurable: true
    });
}

12、 map

Array.prototype.myMap = function(fn) {
    // 判斷輸入的第一個參數是否是函數
    if (typeof fn !== 'function') {
        throw new TypeError(fn + 'is not a function');
    }

    // 獲取須要處理的數組內容
    const arr = this;
    const len = arr.length;
    // 新建一個空數組用於裝載新的內容
    const temp = new Array(len);

    // 對數組中每一個值進行處理
    for (let i = 0; i < len; i++) {
        // 獲取第二個參數,改變this指向
        let result = fn.call(arguments[1], arr[i], i, arr);
        temp[i] = result;
    }
    // 返回新的結果
    return temp;
}

十3、 filter

Array.prototype.myFilter = function (fn) {
    if (typeof fn !== 'function') {
        throw new TypeError(`${fn} is not a function`);
    }

    // 獲取該數組
    const arr = this;
    // 獲取該數組長度
    const len = this.length >>> 0;
    // 新建一個新的數組用於放置該內容
    const temp = [];

    // 對數組中每一個值進行處理
    for (let i = 0; i < len; i++) {
        // 處理時注意this指向
        const result = fn.call(arguments[1], arr[i], i, arr);
        result && temp.push(arr[i]);
    }

    return temp;
}

十4、 reduce

Array.prototype.myReduce = function(fn) {
    if (typeof fn !== 'function') {
        throw new TypeError(`${fn} is not a function`);
    }

    const arr = this;
    const len = arr.length >>> 0;
    let value;// 最終返回的值
    let k = 0;// 當前索引

    if (arguments.length >= 2) {
        value = arguments[1];
    } else {
        // 當數組爲稀疏數組時,判斷數組當前是否有元素,若是沒有索引加一
        while (k < len && !( k in arr)) {
            k++;
        }
        // 若是數組爲空且初始值不存在則報錯
        if (k >= len) {
            throw new TypeError('Reduce of empty array with no initial value');
        }
        value = arr[k++];
    }
    while (k < len) {
        if (k in arr) {
            value = fn(value, arr[k], k, arr);
        }
        k++;
    }

    return value;
}

十5、 flat

// 使用reduce和concat
Array.prototype.flat1 = function () {
    return this.reduce((acc, val) => acc.concat(val), []);
}
// 使用reduce + concat + isArray +recursivity
Array.prototype.flat2 = function (deep = 1) {
    const flatDeep = (arr, deep = 1) => {
        // return arr.reduce((acc, val) => Array.isArray(val) && deep > 0 ? [...acc, ...flatDeep(val, deep - 1)] : [...acc, val], []);
        return deep > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, deep - 1) : val), []) : arr.slice();
    }

    return flatDeep(this, deep);
}
// 使用forEach + concat + isArray +recursivity
// forEach 遍歷數組會自動跳過空元素
Array.prototype.flat3 = function (deep = 1) {
    const result = [];
    (function flat(arr, deep) {
        arr.forEach((item) => {
            if (Array.isArray(item) && deep > 0) {
                flat(item, deep - 1);
            } else {
                result.push(item);
            }
        })
    })(this, deep);

    return result;
}
// 使用for of + concat + isArray +recursivity
// for of 遍歷數組會自動跳過空元素
Array.prototype.flat4 = function (deep = 1) {
    const result = [];
    (function flat(arr, deep) {
        for(let item of arr) {
            if (Array.isArray(item) && deep > 0) {
                flat(item, deep - 1);
            } else {
                // 去除空元素,由於void 表達式返回的都是undefined,不適用undefined是由於undefined在局部變量會被重寫
                item !== void 0 && result.push(item);
            }
        }
    })(this, deep);

    return result;
}
// 使用堆棧stack
Array.prototype.flat5 = function(deep = 1) {
    const stack = [...this];
    const result = [];
    while (stack.length > 0) {
        const next = stack.pop();
        if (Array.isArray(next)) {
            stack.push(...next);
        } else {
            result.push(next);
        }
    }

    // 反轉恢復原來順序
    return result.reverse();
}

十6、 call

Function.prototype.call1 = function(context, ...args) {
    // 獲取第一個參數(注意第一個參數爲null或undefined是,this指向window),構建對象
    context = context ? Object(context) : window;
    // 將對應函數傳入該對象中
    context.fn = this;
    // 獲取參數並執行相應函數
    let result = context.fn(...args);
    delete context.fn;

十7、 apply

Function.prototype.apply1 = function(context, arr) {
    context = context ? Object(context) : window;
    context.fn = this;

    let result = arr ? context.fn(...arr) : context.fn();

    delete context.fn;

    return result;
}

十8、 bind

Function.prototype.bind1 = function (context, ...args) {
    if (typeof this !== 'function') {
        throw new TypeError('The bound object needs to be a function');
    }

    const self = this;
    const fNOP = function() {};
    const fBound = function(...fBoundArgs) {
        // 指定this
        // 看成爲構造函數時,this 指向實例,此時 this instanceof fBound 結果爲 true
        return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]);
    }

    //  修改返回函數的 prototype 爲綁定函數的 prototype,爲了不直接修改this的原型,因此新建了一個fNOP函數做爲中介
    if (this.prototype) {
        fNOP.prototype = this.prototype;
    }
    fBound.prototype = new fNOP();

    return fBound;
}

十9、 防抖

function debounce(fn, wait, immediate) {
    let timer = null;
    return function(...args) {
        // 當即執行的功能(timer爲空表示首次觸發)
        if (immediate && !timer) {
            fn.apply(this, args);
        }
        // 有新的觸發,則把定時器清空
        timer && clearTimeout(timer);
        // 從新計時
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, wait)
    }
}

二10、 節流

// 時間戳版本
function throttle(fn, wait) {
    // 上一次執行時間
    let previous = 0;
    return function(...args) {
        // 當前時間
        let now = +new Date();
        if (now - previous > wait) {
            previous = now;
            fn.apply(this, args);
        }
    }
}
// 定時器版本
function throttle(fn, wait) {
    let timer = null;
    return function(...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args);
                timer = null;
            }, wait)
        }
    }
}

二11、深拷貝

// 乞巧版
function cloneDeep1(source) {
    return JSON.parse(JSON.stringify(source));
}
// 遞歸版
function cloneDeep2(source) {
    // 若是輸入的爲基本類型,直接返回
    if (!(typeof source === 'object' && source !== null)) {
        return source;
    }

    // 判斷輸入的爲數組函數對象,進行相應的構建
    const target = Array.isArray(source) ? [] : {};

    for (let key in source) {
        // 判斷是不是自身屬性
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (typeof source === 'object' && source !== null) {
                target[key] = cloneDeep2(source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }

    return target;
}
// 循環方式
function cloneDeep3(source) {
    if (!(typeof source === 'object' && source !== null)) {
        return source;
    }

    const root = Array.isArray(source) ? [] : {};
    // 定義一個棧
    const loopList = [{
        parent: root,
        key: undefined,
        data: source,
    }];

    while (loopList.length > 0) {
        // 深度優先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化賦值目標,key爲undefined則拷貝到父元素,不然拷貝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = Array.isArray(data) ? [] : {};
        }

        for (let key in data) {
            if (data.hasOwnProperty(key)) {
                if (typeof data[key] === 'object' && data !== null) {
                    loopList.push({
                        parent: res,
                        key: key,
                        data: data[key],
                    });
                } else {
                    res[key] = data[key];
                }
            }
        }
    }

    return root;
}

二12、 根據Promise/A+規範實現Promise

人家有相關標準,咱們就要遵照,畢竟遵紀守法纔是好公民,如今只能硬着頭皮把這個標準過一遍。

下面就是基於Promise/A+規範實現的代碼,已經通過promises-aplus-tests庫進行了驗證。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
/**
 * Promise構造函數
 * excutor: 內部同步執行的函數
 */
class Promise {
    constructor(excutor) {
        const self = this;
        self.status = PENDING;
        self.onFulfilled = [];// 成功的回調
        self.onRejected = [];// 失敗的回調

        // 異步處理成功調用的函數
        // PromiseA+ 2.1 狀態只能由Pending轉爲fulfilled或rejected;fulfilled狀態必須有一個value值;rejected狀態必須有一個reason值。
        function resolve(value) {
            if (self.status === PENDING) {
                self.status = FULFILLED;
                self.value = value;
                 // PromiseA+ 2.2.6.1 相同promise的then能夠被調用屢次,當promise變爲fulfilled狀態,所有的onFulfilled回調按照原始調用then的順序執行
                self.onFulfilled.forEach(fn => fn());
            }
        }

        function reject(reason) {
            if (self.status === PENDING) {
                self.status = REJECTED;
                self.reason = reason;
                // PromiseA+ 2.2.6.2 相同promise的then能夠被調用屢次,當promise變爲rejected狀態,所有的onRejected回調按照原始調用then的順序執行
                self.onRejected.forEach(fn => fn());
            }
        }

        try {
            excutor(resolve, reject);
        } catch (e) {
            reject(e);
        }
    }

    then(onFulfilled, onRejected) {
        // PromiseA+ 2.2.1 onFulfilled和onRejected是可選參數
        // PromiseA+ 2.2.5 onFulfilled和onRejected必須被做爲函數調用
        // PromiseA+ 2.2.7.3 若是onFulfilled不是函數且promise1狀態是fulfilled,則promise2有相同的值且也是fulfilled狀態
        // PromiseA+ 2.2.7.4 若是onRejected不是函數且promise1狀態是rejected,則promise2有相同的值且也是rejected狀態
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

        const self = this;
        const promise = new Promise((resolve, reject) => {
            const handle = (callback, data) => {
                // PromiseA+ 2.2.4 onFulfilled或者onRejected須要在本身的執行上下文棧裏被調用,因此此處用setTimeout
                setTimeout(() => {
                    try {
                         // PromiseA+ 2.2.2 若是onFulfilled是函數,則在fulfilled狀態以後調用,第一個參數爲value
                        // PromiseA+ 2.2.3 若是onRejected是函數,則在rejected狀態以後調用,第一個參數爲reason
                        const x = callback(data);
                        // PromiseA+ 2.2.7.1 若是onFulfilled或onRejected返回一個x值,運行這[[Resolve]](promise2, x)
                        resolvePromise(promise, x, resolve, reject);
                    } catch (e) {
                        // PromiseA+ 2.2.7.2 onFulfilled或onRejected拋出一個異常e,promise2必須以e的理由失敗
                        reject(e);
                    }
                })
            }
            if (self.status === PENDING) {
                self.onFulfilled.push(() => {
                    handle(onFulfilled, self.value);
                });

                self.onRejected.push(() => {
                    handle(onRejected, self.reason);
                })
            } else if (self.status === FULFILLED) {
                setTimeout(() => {
                    handle(onFulfilled, self.value);
                })
            } else if (self.status === REJECTED) {
                setTimeout(() => {
                    handle(onRejected, self.reason);
                })
            }
        })

        return promise;
    }
}

function resolvePromise(promise, x, resolve, reject) {
    // PromiseA+ 2.3.1 若是promise和x引用同一對象,會以TypeError錯誤reject promise
    if (promise === x) {
        reject(new TypeError('Chaining Cycle'));
    }

    if (x && typeof x === 'object' || typeof x === 'function') {
        // PromiseA+ 2.3.3.3.3 若是resolvePromise和rejectPromise都被調用,或者對同一個參數進行屢次調用,那麼第一次調用優先,之後的調用都會被忽略。 
        let used;
        try {
            // PromiseA+ 2.3.3.1 let then be x.then
            // PromiseA+ 2.3.2 調用then方法已經包含了該條(該條是x是promise的處理)。
            let then = x.then;

            if (typeof then === 'function') {
                // PromiseA+ 2.3.3.3若是then是一個函數,用x做爲this調用它。第一個參數是resolvePromise,第二個參數是rejectPromise
                // PromiseA+ 2.3.3.3.1 若是resolvePromise用一個值y調用,運行[[Resolve]](promise, y)
                // PromiseA+ 2.3.3.3.2 若是rejectPromise用一個緣由r調用,用r拒絕promise。
                then.call(x, (y) => {
                    if (used) return;
                    used = true;
                    resolvePromise(promise, y, resolve, reject)
                }, (r) => {
                    if (used) return;
                    used = true;
                    reject(r);
                })
            } else {
                // PromiseA+ 若是then不是一個函數,變爲fulfilled狀態並傳值爲x
                if (used) return;
                used = true;
                resolve(x);
            }
        } catch (e) {
            // PromiseA+ 2.3.3.2 若是檢索屬性x.then拋出異常e,則以e爲緣由拒絕promise
            // PromiseA+ 2.3.3.4 若是調用then拋出異常,可是resolvePromise或rejectPromise已經執行,則忽略它
            if (used) return;
            used = true;
            reject(e);
        }

    } else {
        // PromiseA+ 2.3.4 若是x不是一個對象或函數,狀態變爲fulfilled並傳值x
        resolve(x);
    }
}

二十3、 Promise.resolve()

class Promise {
    // ...
    // 將現有對象轉爲 Promise 對象
    static resolve(value) {
        // 若是參數是 Promise 實例,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。
        if (value instanceof Promise) return value;

        // 參數是一個thenable對象(具備then方法的對象),Promise.resolve方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then方法。
        if (typeof value === 'object' || typeof value === 'function') {
            try {
                let then = value.then;
                if (typeof then === 'function') {
                    return new Promise(then.bind(value));
                }
            } catch (e) {
                return new Promise((resolve, reject) => {
                    reject(e);
                })
            }
        }

        // 參數不是具備then方法的對象,或根本就不是對象,Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。
        return new Promise((resolve, reject) => {
            resolve(value);
        })
    }
}

二十4、 Promise.reject()

class Promise {
    // ...
    // 返回一個新的 Promise 實例,該實例的狀態爲rejected。
    static reject(reason) {
        return new Promise((resolve, reject) => {
            reject(reason);
        })
    }
}

二十5、 Promise.all()

class Promise {
    // ...
    // 用於將多個 Promise 實例,包裝成一個新的 Promise 實例。只有全部狀態都變爲fulfilled,p的狀態纔會是fulfilled
    static all(promises) {
        const values = [];
        let resolvedCount = 0;
        return new Promise((resolve, reject) => {
            promises.forEach((p, index) => {
                Promise.resolve(p).then(value => {
                    resolvedCount++;
                    values[index] = value;
                    if (resolvedCount === promises.length) {
                        resolve(values);
                    }
                }, reason => {
                    reject(reason);
                })
            })
        })
    }
}

二十6、 Promise.race()

class Promise {
    // ...
     // 只要有一個實例率先改變狀態,狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給回調函數。
    static race(promises) {
        return new Promise((resolve, reject) => {
            promises.forEach((p, index) => {
                Promise.resolve(p).then(value => {
                    resolve(value);
                }, reason => {
                    reject(reason);
                })
            })
        })
    }
}

二十7、 Promise.catch()

class Promise {
    // ...
    // 是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。
    catch(onRejected) {
        return this.then(undefined, onRejected);
    }
}

二十8、 Promise.finally()

class Promise {
    // ...
    // 用於指定無論 Promise 對象最後狀態如何,都會執行的操做。
    finally(callback) {
        return this.then(
            value => Promise.resolve(callback()).then(() => value),
            reason => Promise.resolve(callback()).then(() => { throw reason })
        )
    }
}

二十9、Async實現原理

這是Async的實現原理,即將Generator函數做爲參數放入run函數中,最終實現自動執行並返回Promise對象。
function run(genF) {
    // 返回值是Promise
    return new Promise((resolve, reject) => {
        const gen = genF();
        function step(nextF) {
            let next;
            try {
                // 執行該函數,獲取一個有着value和done兩個屬性的對象
                next = nextF();
            } catch (e) {
                // 出現異常則將該Promise變爲rejected狀態
                reject(e);
            }

            // 判斷是否到達末尾,Generator函數到達末尾則將該Promise變爲fulfilled狀態
            if (next.done) {
                return resolve(next.value);
            }

            // 沒到達末尾,則利用Promise封裝該value,直到執行完畢,反覆調用step函數,實現自動執行
            Promise.resolve(next.value).then((v) => {
                step(() => gen.next(v))
            }, (e) => {
                step(() => gen.throw(e))
            })
        }

        step(() => gen.next(undefined));
    })
}

三10、發佈訂閱模式

// 發佈訂閱(TypeScript版)
interface Publish {
    registerObserver(eventType : string, subscribe : Subscribe) : void;
    remove(eventType : string, subscribe ?: Subscribe) : void;
    notifyObservers(eventType : string) : void;
}
interface SubscribesObject{
    [key : string] : Array<Subscribe>
}
class ConcretePublish implements Publish {
    private subscribes : SubscribesObject;

    constructor() {
        this.subscribes = {};
    }

    registerObserver(eventType : string, subscribe : Subscribe) : void {
        if (!this.subscribes[eventType]) {
            this.subscribes[eventType] = [];
        }

        this.subscribes[eventType].push(subscribe);
    }

    remove(eventType : string, subscribe ?: Subscribe) : void {
        const subscribeArray = this.subscribes[eventType];
        if (subscribeArray) {
            if (!subscribe) {
                delete this.subscribes[eventType];
            } else {
                for (let i = 0; i < subscribeArray.length; i++) {
                    if (subscribe === subscribeArray[i]) {
                        subscribeArray.splice(i, 1);
                    }
                }
            }
        }
    }

    notifyObservers(eventType : string, ...args : any[]) : void {
        const subscribes = this.subscribes[eventType];
        if (subscribes) {
            subscribes.forEach(subscribe => subscribe.update(...args))
        }
    }
}

interface Subscribe {
    update(...value : any[]) : void;
}

class ConcreteSubscribe1 implements Subscribe {
    public update(...value : any[]) : void {
        console.log('已經執行更新操做1,值爲', ...value);
    }
}
class ConcreteSubscribe2 implements Subscribe {
    public update(...value : any[]) : void {
        console.log('已經執行更新操做2,值爲', ...value);
    }
}

function main() {
    const publish = new ConcretePublish();
    const subscribe1 = new ConcreteSubscribe1();
    const subscribe2 = new ConcreteSubscribe2();

    publish.registerObserver('1', subscribe1);
    publish.registerObserver('2', subscribe2);

    publish.notifyObservers('2', '22222');
}

main();

三11、懶加載

1)首先,不要將圖片地址放到src屬性中,而是放到其它屬性(data-original)中。<br/>
2)頁面加載完成後,根據scrollTop判斷圖片是否在用戶的視野內,若是在,則將data-original屬性中的值取出存放到src屬性中。<br/>
3)在滾動事件中重複判斷圖片是否進入視野,若是進入,則將data-original屬性中的值取出存放到src屬性中。<br/>
elementNode.getAttribute(name):方法經過名稱獲取屬性的值。<br/>
elementNode.setAttribute(name, value):方法建立或改變某個新屬性。<br/>
elementNode.removeAttribute(name):方法經過名稱刪除屬性的值。
//懶加載代碼實現
var viewHeight = document.documentElement.clientHeight;//可視化區域的高度

function lazyload () {
    //獲取全部要進行懶加載的圖片
    let eles = document.querySelectorAll('img[data-original][lazyload]');//獲取屬性名中有data-original的
    Array.prototype.forEach.call(eles, function(item, index) {
        let rect;
        if(item.dataset.original === '') {
            return;
        }

        rect = item.getBoundingClientRect();

        //圖片一進入可視區,動態加載
        if(rect.bottom >= 0 && rect.top < viewHeight) {
            !function () {
                let img = new Image();
                img.src = item.dataset.original;
                img.onload = function () {
                    item.src = img.src;
                }
                item.removeAttribute('data-original');
                item.removeAttribute('lazyload');
            }();
        }
    })
}

lazyload();

document.addEventListener('scroll', lazyload);

三12、FileReader使用

function uploadMulFile(uploadFile) {
    return new Promise((resolve, reject) => {
        let fileLength = 0;
        let reader = new FileReader();
        reader.readAsText(uploadFile[fileLength]);
        reader.onabort = function(e) {
            console.log("文件讀取異常");
        }
        reader.onerror = function(e) {
            console.log("文件讀取錯誤");
        }

        reader.onload = function(e){
            if(e.target.result) {

                fileLength++;
                if(fileLength < uploadFile.length) {
                    reader.readAsText(uploadFile[fileLength]);
                }else{
                    resolve({
                        carArr,
                        crossArr,
                        roadArr
                    })
                }
            }
        }
    })
}

三十3、Ajax使用(非Promise版)

function ajax(url) {
    var XHR;
    //進行性能檢測
    if(window.ActiveXObject) {
        XHR = new ActiveXObject("Microsoft.XMLHTTP");//兼容IE//IE瀏覽器中使用的請求的方法,需實例化
    }else if(window.XMLHttpRequest) {
        XHR = new XMLHttpRequest();//標準瀏覽器中的使用的請求方法,需實例化
    }else{
        XHR = null;
    }

    if(XHR) {
        XHR.onreadystatechange = function() {
            //readyState:Ajax請求服務的狀態
            if(XHR.readyState == 4) {
                //status:頁面的響應碼
                if(XHR.status == 200) {
                    //返回的數據以string的形式返回
                    console.log(XHR.responseText);
                }
            }
        };

        XHR.open("get", url);//open(‘method’,‘url’,boolean);參數1:請求方式;參數2:請求文件的地址;參數3:設置是否異步,true表示異步, 默認值爲 true 因此能夠不寫。
        XHR.send();
    }
}

三十4、Ajax使用(Promise版)

//Promise形式
function ajaxPromise(method, url, data) {
    var xhr = null;
    if(window.ActiveXObject) {
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
    }else{
        xhr = new XMLHttpRequest();
    }

    return new Promise(function(resolve, reject) {
        xhr.onreadystatechange = function(){
            if(xhr.readyState === 4 ) {
                if(xhr.status === 200) {
                    resolve(JSON.parse(xhr.responseText));
                }else{
                    reject(xhr.status);
                }
            }
        };

        if(method.toUpperCase() == "GET") {
            var arr = [];
            for(var key in data) {
                arr.push(key + '=' + data[key]);
            }

            var getData = arr.join('&');
            xhr.open("GET", url + "?" + getData, true);//true表示異步
            xhr.send(null);
        }else if(method.toUpperCase() == "POST") {
            xhr.open("POST", url, true);
            xhr.responseType = "json";
            xhr.setRequestHeader('Content', 'application/x-www-form-urlencoded;charset=utf-8');
            xhr.send(data);
        }


    })
}

三十5、JsonP

function jsonp(url, onsuccess, onerror, charset) {
    var hash = Math.random().toString().slice(2);
    window['jsonp' + hash] = function(data) {
        if(onsuccess && typeof onsuccess == 'function') {
            onsuccess(data);
        }
    }

    var script = createScript(url + "?callback=jsonp" + hash, charset); // 動態產檢一個script標籤

    //監聽加載成功的事件,獲取數據,這個位置用了兩個事件onload和onreadystatechange是爲了兼容IE,由於IE9以前不支持onload事件,只支持onreadystatechange事件
    script.onload = script.onreadystatechange = function() {
        //若不存在readyState事件則證實不是IE瀏覽器,能夠直接執行,如果的話,必須等到狀態變爲loaded或complete才能夠執行
        if(!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') {
            script.onload = script.onreadystatechange = null;
            // 移除該script的DOM對象
            if(script.parentNode) {
                script.parentNode.removeChild(script);
            }

            //刪除函數或變量
            window['jsonp' + hash] = null;
        }
    }

    script.onerror = function () {
        if(onerror && typeof onerror == 'function') {
            onerror();
        }
    }

    document.getElementsByTagName('head')[0].appendChild(script);//往html中增長這個標籤,目的是把請求發送出去
}

function createScript(url, charset) {
    var script = document.createElement('script');
    script.setAttribute('type', 'text/javascript');
    charset && script.setAttribute('charset', charset);
    script.setAttribute('src', url);
    script.async = true;
}

三十6、將一個字符串轉換爲駝峯形式

//方式一:操做字符串數組
function transformStr2Hump1(str) {
    if(str == null) {
        return "";
    }
    var strArr = str.split('-');
    for(var i = 1; i < strArr.length; i++) {
        strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1);
    }
    return strArr.join('');
}

//方式二:操做字符數組
function transformStr2Hump2(str) {
    if(str == null) {
        return "";
    }
    var strArr  =str.split('');
    for(var i = 0; i < strArr.length; i++) {
        if(strArr[i] == "-"){
            //刪除-
            strArr.splice(i, 1);
            //將該處改成大寫
            if(i < strArr.length) {
                strArr[i] = strArr[i].toUpperCase();
            }
        }
    }
    return strArr.join("");
}

//方式三:利用正則
function transformStr2Hump3(str) {
    if(str == null) {
        return "";
    }
    var reg = /-(\w)/g;//匹配字母或數字或下劃線或漢字
    return str.replace(reg, function($0, $1) {
        return $1.toUpperCase();
    })
}
歡迎你們關注公衆號(回覆「書籍」獲取大量前端學習資料,回覆「前端視頻」獲取大量前端教學視頻)
相關文章
相關標籤/搜索