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

{"type":"doc","content":[{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號“"},{"type":"text","marks":[{"type":"strong"}],"text":"執鳶者"},{"type":"text","text":"”,獲取大量教學視頻及"},{"type":"text","marks":[{"type":"strong"}],"text":"私人總結麪筋"},{"type":"text","text":"並進入"},{"type":"text","marks":[{"type":"strong"}],"text":"專業交流羣"},{"type":"text","text":"."}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6b/6be2245ef297cc2414ce826223de8232.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前互聯網行業目前正在處於內卷狀態,各個大廠不斷提高招人門檻,前端工程師找工作也越發艱難,爲了助力各位老鐵能夠在面試過程中脫穎而出,我結合自己的面試經驗,準備了這三十六道面試過程中的手撕算法題,與各位共享。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、冒泡排序"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"冒泡排序的思路:遍歷數組,然後將最大數沉到最底部;
"},{"type":"text","marks":[{"type":"strong"}],"text":"時間複雜度:O(N^2);"},{"type":"text","text":"
"},{"type":"text","marks":[{"type":"strong"}],"text":"空間複雜度:O(1)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function BubbleSort(arr) {\n if(arr == null || arr.length <= 0){\n return [];\n }\n var len = arr.length;\n for(var end = len - 1; end > 0; end--){\n for(var i = 0; i < end; i++) {\n if(arr[i] > arr[i + 1]){\n swap(arr, i, i + 1);\n }\n }\n }\n return arr;\n}\nfunction swap(arr, i, j){\n // var temp = arr[i];\n // arr[i] = arr[j];\n // arr[j] = temp;\n //交換也可以用異或運算符\n arr[i] = arr[i] ^ arr[j];\n arr[j] = arr[i] ^ arr[j];\n arr[i] = arr[i] ^ arr[j];\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、選擇排序"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"選擇排序的實現思路:遍歷數組,把最小數放在頭部;
"},{"type":"text","marks":[{"type":"strong"}],"text":"時間複雜度:O(N^2);"},{"type":"text","text":"
"},{"type":"text","marks":[{"type":"strong"}],"text":"空間複雜度:O(1)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function SelectionSort(arr) {\n if(arr == null || arr.length < 0) {\n return [];\n }\n for(var i = 0; i < arr.length - 1; i++) {\n var minIndex = i;\n for(var j = i + 1; j < arr.length; j++) {\n minIndex = arr[j] < arr[minIndex] ? j : minIndex;\n }\n swap(arr, i, minIndex);\n }\n return arr;\n}\n\nfunction swap(arr, i, j) {\n arr[i] = arr[i] ^ arr[j];\n arr[j] = arr[i] ^ arr[j];\n arr[i] = arr[i] ^ arr[j];\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、插入排序"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"插入排序實現思路:將一個新的數,和前面的比較,只要當前數小於前一個則和前一個交換位置,否則終止;
"},{"type":"text","marks":[{"type":"strong"}],"text":"時間複雜度:O(N^2);"},{"type":"text","text":"
"},{"type":"text","marks":[{"type":"strong"}],"text":"空間複雜度:O(1)"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function insertSort(arr) {\n if(arr == null || arr.length <= 0){\n return [];\n }\n var len = arr.length;\n for(var i = 1; i < len; i++) {\n for(var j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {\n swap(arr, j, j + 1);\n }\n }\n return arr;\n}\n\nfunction swap(arr, i, j){\n var temp = arr[i];\n arr[i] = arr[j];\n arr[j] = temp;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"四、歸併排序"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歸併排序的思路:
1.先左側部分排好序
2.再右側部分排好序
3.再準備一個輔助數組,用外排的方式,小的開始填,直到有個動到末尾,將另一個數組剩餘部分拷貝到末尾
4.再將輔助數組拷貝回原數組
*"},{"type":"text","marks":[{"type":"italic"}],"text":"時間複雜度:O(N "},{"type":"text","text":" logN)"},{"type":"text","marks":[{"type":"strong"}],"text":"
"},{"type":"text","text":"空間複雜度:O(N)**"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 遞歸實現\n\nfunction mergeSort(arr){\n if(arr == null || arr.length <= 0){\n return [];\n }\n sortProcess(arr, 0, arr.length - 1);\n return arr;\n}\n\nfunction sortProcess(arr, L, R){\n //遞歸的終止條件,就是左右邊界索引一樣\n if(L == R){\n return;\n }\n var middle = L + ((R - L) >> 1);//找出中間值\n sortProcess(arr, L, middle);//對左側部分進行遞歸\n sortProcess(arr, middle + 1, R);//對右側部分進行遞歸\n merge(arr, L, middle, R);//然後利用外排方式進行結合\n}\n\nfunction merge(arr, L, middle, R){\n var help = [];\n var l = L;\n var r = middle + 1;\n var index = 0;\n //利用外排方式進行\n while(l <= middle && r <= R){\n help[index++] = arr[l] < arr[r] ? arr[l++] : arr[r++];\n }\n while(l <= middle){\n help.push(arr[l++]);\n }\n while(r <= R){\n help.push(arr[r++]);\n }\n\n for(var i = 0; i < help.length; i++) {\n arr[L + i] = help[i];\n }\n //arr.splice(L, help.length, ...help);//這個利用了ES6的語法\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 循環實現\n\nfunction mergeSort(arr){\n if(arr ==null || arr.length <= 0){\n return [];\n }\n var len = arr.length;\n //i每次乘2,是因爲每次合併以後小組元素就變成兩倍個了\n for(var i = 1; i < len; i *= 2){\n var index = 0;//第一組的起始索引\n while( 2 * i + index <= len){\n index += 2 * i;\n merge(arr, index - 2 * i, index - i, index);\n }\n //說明剩餘兩個小組,但其中一個小組數據的數量已經不足2的冪次方個\n if(index + i < len){\n merge(arr, index, index + i, len);\n }\n }\n return arr;\n}\n\n//利用外排的方式進行結合\nfunction merge(arr, start, mid, end){\n //新建一個輔助數組\n var help = [];\n var l = start, r = mid;\n var i = 0;\n while(l < mid && r < end){\n help[i++] = arr[l] < arr[r] ? arr[l++] : arr[r++];\n }\n while(l < mid){\n help[i++] = arr[l++];\n }\n while(r < end){\n help[i++] = arr[r++];\n }\n for(var j = 0; j < help.length; j++){\n arr[start + j] = help[j];\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"五、快速排序"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"快速排序實現思路:隨機取出一個值進行劃分,大於該值放右邊,小於該值放左邊(該算法在經典快排的基礎上經過荷蘭國旗思想和隨機思想進行了改造)
*"},{"type":"text","marks":[{"type":"italic"}],"text":"時間複雜度:O(N"},{"type":"text","text":"logN)"},{"type":"text","marks":[{"type":"strong"}],"text":"
"},{"type":"text","text":"空間複雜度:O(logN)**"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function quickSort(arr) {\n if(arr == null || arr.length <= 0){\n return [];\n }\n quick(arr, 0, arr.length - 1);\n}\n\nfunction quick(arr, L, R){\n //遞歸結束條件是L >= R\n if(L < R){\n //隨機找一個值,然後和最後一個值進行交換,將經典排序變爲快速排序\n swap(arr, L + Math.floor(Math.random() * (R - L + 1)), R);\n //利用荷蘭國旗問題獲得劃分的邊界,返回的值是小於區域的最大索引和大於區域的最小索引,在這利用荷蘭國旗問題將等於區域部分就不用動了\n var tempArr = partition(arr, L, R, arr[R]);\n quick(arr, L, tempArr[0]);\n quick(arr, tempArr[1], R);\n }\n}\n//返回值是小於區域最後的索引和大於區域的第一個索引\nfunction partition(arr, L, R, num){\n var less = L - 1;\n var more = R + 1;\n var cur = L;\n while(cur < more){\n if(arr[cur] < num){\n swap(arr, ++less, cur++);\n }else if(arr[cur] > num) {\n swap(arr, --more, cur);\n }else{\n cur++;\n }\n }\n return [less, more];\n}\nfunction swap(arr, i, j){\n var temp = arr[i];\n arr[i] = arr[j];\n arr[j] = temp;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"六、堆排序"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"堆排序思路:
1.讓數組變成大根堆
2.把最後一個位置和堆頂做交換
3.則最大值在最後,則剩下部分做heapify,則重新調整爲大根堆,則堆頂位置和該部分最後位置做交換
4.重複進行,直到減完,則這樣最後就調整完畢,整個數組排完序(爲一個升序)
*"},{"type":"text","marks":[{"type":"italic"}],"text":"時間複雜度:O(N "},{"type":"text","text":" logN)"},{"type":"text","marks":[{"type":"strong"}],"text":"
"},{"type":"text","text":"空間複雜度:O(1)**"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function heapSort(arr) {\n if(arr == null || arr.length <= 0) {\n return [];\n }\n\n //首先是建立大頂堆的過程\n for(var i = 0; i < arr.length; i++) {\n heapInsert(arr, i);\n }\n var size = arr.length;//這個值用來指定多少個數組成堆,當得到一個排序的值後這個值減一\n //將堆頂和最後一個位置交換\n /**\n * 當大頂堆建立完成後,然後不斷將最後一個位置和堆頂交換;\n * 這樣最大值就到了最後,則剩下部分做heapify,重新調整爲大根堆,則堆頂位置和倒數第二個位置交換,重複進行,直到全部排序完畢*/\n //由於前面已經是大頂堆,所以直接交換\n swap(arr, 0, --size);\n while(size > 0) {\n //重新變成大頂堆\n heapify(arr, 0, size);\n //進行交換\n swap(arr, 0, --size);\n }\n}\n\n//加堆過程中\nfunction heapInsert(arr, index) {\n //比較當前位置和其父位置,若大於其父位置,則進行交換,並將索引移動到其父位置進行循環,否則跳過\n //結束條件是比父位置小或者到達根節點處\n while(arr[index] > arr[parseInt((index - 1) / 2)]){\n //進行交換\n swap(arr, index, parseInt((index - 1) / 2));\n index = parseInt((index - 1) / 2);\n }\n}\n//減堆過程\n/**\n * size指的是這個數組前多少個數構成一個堆\n * 如果你想把堆頂彈出,則把堆頂和最後一個數交換,把size減1,然後從0位置經歷一次heapify,調整一下,剩餘部分變成大頂堆*/\nfunction heapify(arr, index, size) {\n var left = 2 * index + 1;\n while(left < size) {\n var largest = (left + 1 < size && arr[left] < arr[left + 1]) ? left + 1 : left;\n largest = arr[index] > arr[largest] ? index : largest;\n\n //如果最大值索引和傳進來索引一樣,則該值到達指定位置,直接結束循環\n if(index == largest) {\n break;\n }\n\n //進行交換,並改變索引和其左子節點\n swap(arr, index, largest);\n index = largest;\n left = 2 * index + 1;\n }\n}\n\nfunction swap(arr, i, j) {\n var temp = arr[i];\n arr[i] = arr[j];\n arr[j] = temp;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"七、桶排序"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"桶排序會經歷三次遍歷:準備一個數組、遍歷一遍數組、重構一遍數組,是非基於比較的排序,下面以一個問題來闡述其思路。
"},{"type":"text","marks":[{"type":"strong"}],"text":"問題:"},{"type":"text","text":"
給定一個數組,求如果排序之後,相鄰兩個數的最大差值,要求時間複雜度O(N),且要求不能用基於比較的排序
"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"思路:"},{"type":"text","text":"
1.準備桶:數組中有N個數就準備N+1個桶
2.遍歷一遍數組,找到最大值max和最小值min"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"。若min = max,則差值=0;若min≠max,則最小值放在0號桶,最大值放在N號桶,剩下的數屬於哪個範圍就進哪個桶
3.根據鴿籠原理,則肯定有一個桶爲空桶,設計該桶的目的是爲了否定最大值在一個桶中,則最大差值的兩個數一定來自於兩個桶,但空桶兩側並不一定是最大值
4.所以只記錄所有進入該桶的最小值min和最大值max和一個布爾值表示該桶有沒有值
5.然後遍歷這個數組,如果桶是空的,則跳到下一個數,如果桶非空,則找前一個非空桶,則最大差值=當前桶min - 上一個非空桶max,用全局變量更新最大值
"},{"type":"text","marks":[{"type":"strong"}],"text":"時間複雜度:O(N)"},{"type":"text","text":"
"},{"type":"text","marks":[{"type":"strong"}],"text":"空間複雜度:O(N)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function maxGap(arr) {\n if(arr == null || arr.length <= 0) {\n return 0;\n }\n var len = arr.length;\n var max = -Infinity, min = Infinity;\n //遍歷一遍數組,找到最大值max和最小值min\n for(var i = 0; i < len; i++) {\n max = max > arr[i] ? max : arr[i];\n min = min > arr[i] ? arr[i] : min;\n }\n\n //若min = max,則差值爲0;\n if(min == max) {\n return 0;\n }\n\n var hasNum = new Array(len + 1);\n var mins = new Array(len + 1);\n var maxs = new Array(len + 1);\n\n var bid = 0;//指定桶的編號\n\n for(var i = 0; i < len; i++) {\n bid = bucket(arr[i], min, max, len);//獲得該值是在哪個桶//由於有N+1個桶,所以間隔就是N個,所以此處除以的是len,然後通過這個函數得到應該放到哪個桶裏\n maxs[bid] = hasNum[bid] ? Math.max(arr[i], maxs[bid]) : arr[i];\n mins[bid] = hasNum[bid] ? Math.min(arr[i], mins[bid]) : arr[i];\n hasNum[bid] = true;\n }\n\n var res = 0;\n var lastMax = maxs[0];\n\n for(var i = 0; i < len + 1; i++) {\n if(hasNum[i]) {\n res = Math.max(mins[i] - lastMax, res);\n lastMax = maxs[i];\n }\n }\n return res;\n}\n\n//獲得桶號\n//這個函數用於判斷在哪個桶中,參數分別爲值、最小值、最大值、桶間隔\nfunction bucket(value, min, max, len) {\n return parseInt((value - min) / ((max - min) / len));\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"八、new"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function New (Fn, ...arg) {\n // 一個新的對象被創建\n const result = {};\n // 該對象的__proto__屬性指向該構造函數的原型\n if (Fn.prototype !== null) {\n Object.setPrototypeOf(result, Fn.prototype);\n }\n // 將執行上下文(this)綁定到新創建的對象中\n const returnResult = Fn.apply(result, arg);\n // 如果構造函數有返回值,那麼這個返回值將取代第一步中新創建的對象。否則返回該對象\n if ((typeof returnResult === \"object\" || typeof returnResult === \"function\") && returnResult !== null) {\n return returnResult;\n }\n return result;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"九、instanceof"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function Instanceof(left, right) {\n let leftVal = Object.getPrototypeOf(left);\n const rightVal = right.prototype;\n\n while (leftVal !== null) {\n if (leftVal === rightVal)\n return true;\n leftVal = Object.getPrototypeOf(leftVal);\n }\n return false;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十、 Object.create()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Object.ObjectCreate = (proto, propertiesObject)=> {\n // 對輸入進行檢測\n if (typeof proto !== 'object' && typeof proto !== 'function' && proto !== null) {\n throw new Error(`Object prototype may only be an Object or null:${proto}`);\n }\n // 新建一個對象\n const result = {};\n // 將該對象的原型設置爲proto\n Object.setPrototypeOf(result, proto);\n // 將屬性賦值給該對象\n Object.defineProperties(result, propertiesObject);\n // 返回該對象\n return result;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十一、 Object assign()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function ObjectAssign(target, ...sources) {\n // 對第一個參數的判斷,不能爲undefined和null\n if (target === undefined || target === null) {\n throw new TypeError('cannot convert first argument to object');\n }\n\n // 將第一個參數轉換爲對象(不是對象轉換爲對象)\n const targetObj = Object(target);\n // 將源對象(source)自身的所有可枚舉屬性複製到目標對象(target)\n for (let i = 0; i < sources.length; i++) {\n let source = sources[i];\n // 對於undefined和null在源角色中不會報錯,會直接跳過\n if (source !== undefined && source !== null) {\n // 將源角色轉換成對象\n // 需要將源角色自身的可枚舉屬性(包含Symbol值的屬性)進行復制\n // Reflect.ownKeys(obj) 返回一個數組,包含對象自身的所有屬性,不管屬性名是Symbol還是字符串,也不管是否可枚舉\n const keysArray = Reflect.ownKeys(Object(source));\n for (let nextIndex = 0; nextIndex < keysArray.length; nextIndex ++) {\n const nextKey = keysArray[nextIndex];\n // 去除不可枚舉屬性\n const desc = Object.getOwnPropertyDescriptor(source, nextKey);\n if (desc !== undefined && desc.enumerable) {\n // 後面的屬性會覆蓋前面的屬性\n targetObj[nextKey] = source[nextKey];\n }\n }\n }\n }\n\n return targetObj;\n}\n// 由於掛載到Object的assign是不可枚舉的,直接掛載上去是可枚舉的,所以採用這種方式\nif (typeof Object.myAssign !== 'function') {\n Object.defineProperty(Object, \"myAssign\", {\n value : ObjectAssign,\n writable: true,\n enumerable: false,\n configurable: true\n });\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十二、 map"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Array.prototype.myMap = function(fn) {\n // 判斷輸入的第一個參數是不是函數\n if (typeof fn !== 'function') {\n throw new TypeError(fn + 'is not a function');\n }\n\n // 獲取需要處理的數組內容\n const arr = this;\n const len = arr.length;\n // 新建一個空數組用於裝載新的內容\n const temp = new Array(len);\n\n // 對數組中每個值進行處理\n for (let i = 0; i < len; i++) {\n // 獲取第二個參數,改變this指向\n let result = fn.call(arguments[1], arr[i], i, arr);\n temp[i] = result;\n }\n // 返回新的結果\n return temp;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十三、 filter"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Array.prototype.myFilter = function (fn) {\n if (typeof fn !== 'function') {\n throw new TypeError(`${fn} is not a function`);\n }\n\n // 獲取該數組\n const arr = this;\n // 獲取該數組長度\n const len = this.length >>> 0;\n // 新建一個新的數組用於放置該內容\n const temp = [];\n\n // 對數組中每個值進行處理\n for (let i = 0; i < len; i++) {\n // 處理時注意this指向\n const result = fn.call(arguments[1], arr[i], i, arr);\n result && temp.push(arr[i]);\n }\n\n return temp;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十四、 reduce"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Array.prototype.myReduce = function(fn) {\n if (typeof fn !== 'function') {\n throw new TypeError(`${fn} is not a function`);\n }\n\n const arr = this;\n const len = arr.length >>> 0;\n let value;// 最終返回的值\n let k = 0;// 當前索引\n\n if (arguments.length >= 2) {\n value = arguments[1];\n } else {\n // 當數組爲稀疏數組時,判斷數組當前是否有元素,如果沒有索引加一\n while (k < len && !( k in arr)) {\n k++;\n }\n // 如果數組爲空且初始值不存在則報錯\n if (k >= len) {\n throw new TypeError('Reduce of empty array with no initial value');\n }\n value = arr[k++];\n }\n while (k < len) {\n if (k in arr) {\n value = fn(value, arr[k], k, arr);\n }\n k++;\n }\n\n return value;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十五、 flat"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 使用reduce和concat\nArray.prototype.flat1 = function () {\n return this.reduce((acc, val) => acc.concat(val), []);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 使用reduce + concat + isArray +recursivity\nArray.prototype.flat2 = function (deep = 1) {\n const flatDeep = (arr, deep = 1) => {\n // return arr.reduce((acc, val) => Array.isArray(val) && deep > 0 ? [...acc, ...flatDeep(val, deep - 1)] : [...acc, val], []);\n return deep > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, deep - 1) : val), []) : arr.slice();\n }\n\n return flatDeep(this, deep);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 使用forEach + concat + isArray +recursivity\n// forEach 遍歷數組會自動跳過空元素\nArray.prototype.flat3 = function (deep = 1) {\n const result = [];\n (function flat(arr, deep) {\n arr.forEach((item) => {\n if (Array.isArray(item) && deep > 0) {\n flat(item, deep - 1);\n } else {\n result.push(item);\n }\n })\n })(this, deep);\n\n return result;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 使用for of + concat + isArray +recursivity\n// for of 遍歷數組會自動跳過空元素\nArray.prototype.flat4 = function (deep = 1) {\n const result = [];\n (function flat(arr, deep) {\n for(let item of arr) {\n if (Array.isArray(item) && deep > 0) {\n flat(item, deep - 1);\n } else {\n // 去除空元素,因爲void 表達式返回的都是undefined,不適用undefined是因爲undefined在局部變量會被重寫\n item !== void 0 && result.push(item);\n }\n }\n })(this, deep);\n\n return result;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 使用堆棧stack\nArray.prototype.flat5 = function(deep = 1) {\n const stack = [...this];\n const result = [];\n while (stack.length > 0) {\n const next = stack.pop();\n if (Array.isArray(next)) {\n stack.push(...next);\n } else {\n result.push(next);\n }\n }\n\n // 反轉恢復原來順序\n return result.reverse();\n}\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十六、 call"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Function.prototype.call1 = function(context, ...args) {\n // 獲取第一個參數(注意第一個參數爲null或undefined是,this指向window),構建對象\n context = context ? Object(context) : window;\n // 將對應函數傳入該對象中\n context.fn = this;\n // 獲取參數並執行相應函數\n let result = context.fn(...args);\n delete context.fn;\n "}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十七、 apply"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Function.prototype.apply1 = function(context, arr) {\n context = context ? Object(context) : window;\n context.fn = this;\n\n let result = arr ? context.fn(...arr) : context.fn();\n\n delete context.fn;\n\n return result;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十八、 bind"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Function.prototype.bind1 = function (context, ...args) {\n if (typeof this !== 'function') {\n throw new TypeError('The bound object needs to be a function');\n }\n\n const self = this;\n const fNOP = function() {};\n const fBound = function(...fBoundArgs) {\n // 指定this\n // 當作爲構造函數時,this 指向實例,此時 this instanceof fBound 結果爲 true\n return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]);\n }\n\n // 修改返回函數的 prototype 爲綁定函數的 prototype,爲了避免直接修改this的原型,所以新建了一個fNOP函數作爲中介\n if (this.prototype) {\n fNOP.prototype = this.prototype;\n }\n fBound.prototype = new fNOP();\n\n return fBound;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"十九、 防抖"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function debounce(fn, wait, immediate) {\n let timer = null;\n return function(...args) {\n // 立即執行的功能(timer爲空表示首次觸發)\n if (immediate && !timer) {\n fn.apply(this, args);\n }\n // 有新的觸發,則把定時器清空\n timer && clearTimeout(timer);\n // 重新計時\n timer = setTimeout(() => {\n fn.apply(this, args);\n }, wait)\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十、 節流"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 時間戳版本\nfunction throttle(fn, wait) {\n // 上一次執行時間\n let previous = 0;\n return function(...args) {\n // 當前時間\n let now = +new Date();\n if (now - previous > wait) {\n previous = now;\n fn.apply(this, args);\n }\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 定時器版本\nfunction throttle(fn, wait) {\n let timer = null;\n return function(...args) {\n if (!timer) {\n timer = setTimeout(() => {\n fn.apply(this, args);\n timer = null;\n }, wait)\n }\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十一、深拷貝"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 乞巧版\nfunction cloneDeep1(source) {\n return JSON.parse(JSON.stringify(source));\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 遞歸版\nfunction cloneDeep2(source) {\n // 如果輸入的爲基本類型,直接返回\n if (!(typeof source === 'object' && source !== null)) {\n return source;\n }\n\n // 判斷輸入的爲數組函數對象,進行相應的構建\n const target = Array.isArray(source) ? [] : {};\n\n for (let key in source) {\n // 判斷是否是自身屬性\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n if (typeof source === 'object' && source !== null) {\n target[key] = cloneDeep2(source[key]);\n } else {\n target[key] = source[key];\n }\n }\n }\n\n return target;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 循環方式\nfunction cloneDeep3(source) {\n if (!(typeof source === 'object' && source !== null)) {\n return source;\n }\n\n const root = Array.isArray(source) ? [] : {};\n // 定義一個棧\n const loopList = [{\n parent: root,\n key: undefined,\n data: source,\n }];\n\n while (loopList.length > 0) {\n // 深度優先\n const node = loopList.pop();\n const parent = node.parent;\n const key = node.key;\n const data = node.data;\n\n // 初始化賦值目標,key爲undefined則拷貝到父元素,否則拷貝到子元素\n let res = parent;\n if (typeof key !== 'undefined') {\n res = parent[key] = Array.isArray(data) ? [] : {};\n }\n\n for (let key in data) {\n if (data.hasOwnProperty(key)) {\n if (typeof data[key] === 'object' && data !== null) {\n loopList.push({\n parent: res,\n key: key,\n data: data[key],\n });\n } else {\n res[key] = data[key];\n }\n }\n }\n }\n\n return root;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十二、 根據Promise/A+規範實現Promise"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"人家有相關標準,我們就要遵守,畢竟遵紀守法纔是好公民,現在只能硬着頭皮把這個標準過一遍。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面就是基於Promise/A+規範實現的代碼,已經經過promises-aplus-tests庫進行了驗證。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"const PENDING = 'pending';\nconst FULFILLED = 'fulfilled';\nconst REJECTED = 'rejected';\n/**\n * Promise構造函數\n * excutor: 內部同步執行的函數\n */\nclass Promise {\n constructor(excutor) {\n const self = this;\n self.status = PENDING;\n self.onFulfilled = [];// 成功的回調\n self.onRejected = [];// 失敗的回調\n\n // 異步處理成功調用的函數\n // PromiseA+ 2.1 狀態只能由Pending轉爲fulfilled或rejected;fulfilled狀態必須有一個value值;rejected狀態必須有一個reason值。\n function resolve(value) {\n if (self.status === PENDING) {\n self.status = FULFILLED;\n self.value = value;\n // PromiseA+ 2.2.6.1 相同promise的then可以被調用多次,當promise變爲fulfilled狀態,全部的onFulfilled回調按照原始調用then的順序執行\n self.onFulfilled.forEach(fn => fn());\n }\n }\n\n function reject(reason) {\n if (self.status === PENDING) {\n self.status = REJECTED;\n self.reason = reason;\n // PromiseA+ 2.2.6.2 相同promise的then可以被調用多次,當promise變爲rejected狀態,全部的onRejected回調按照原始調用then的順序執行\n self.onRejected.forEach(fn => fn());\n }\n }\n\n try {\n excutor(resolve, reject);\n } catch (e) {\n reject(e);\n }\n }\n\n then(onFulfilled, onRejected) {\n // PromiseA+ 2.2.1 onFulfilled和onRejected是可選參數\n // PromiseA+ 2.2.5 onFulfilled和onRejected必須被作爲函數調用\n // PromiseA+ 2.2.7.3 如果onFulfilled不是函數且promise1狀態是fulfilled,則promise2有相同的值且也是fulfilled狀態\n // PromiseA+ 2.2.7.4 如果onRejected不是函數且promise1狀態是rejected,則promise2有相同的值且也是rejected狀態\n onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;\n onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };\n\n const self = this;\n const promise = new Promise((resolve, reject) => {\n const handle = (callback, data) => {\n // PromiseA+ 2.2.4 onFulfilled或者onRejected需要在自己的執行上下文棧裏被調用,所以此處用setTimeout\n setTimeout(() => {\n try {\n // PromiseA+ 2.2.2 如果onFulfilled是函數,則在fulfilled狀態之後調用,第一個參數爲value\n // PromiseA+ 2.2.3 如果onRejected是函數,則在rejected狀態之後調用,第一個參數爲reason\n const x = callback(data);\n // PromiseA+ 2.2.7.1 如果onFulfilled或onRejected返回一個x值,運行這[[Resolve]](promise2, x)\n resolvePromise(promise, x, resolve, reject);\n } catch (e) {\n // PromiseA+ 2.2.7.2 onFulfilled或onRejected拋出一個異常e,promise2必須以e的理由失敗\n reject(e);\n }\n })\n }\n if (self.status === PENDING) {\n self.onFulfilled.push(() => {\n handle(onFulfilled, self.value);\n });\n\n self.onRejected.push(() => {\n handle(onRejected, self.reason);\n })\n } else if (self.status === FULFILLED) {\n setTimeout(() => {\n handle(onFulfilled, self.value);\n })\n } else if (self.status === REJECTED) {\n setTimeout(() => {\n handle(onRejected, self.reason);\n })\n }\n })\n\n return promise;\n }\n}\n\nfunction resolvePromise(promise, x, resolve, reject) {\n // PromiseA+ 2.3.1 如果promise和x引用同一對象,會以TypeError錯誤reject promise\n if (promise === x) {\n reject(new TypeError('Chaining Cycle'));\n }\n\n if (x && typeof x === 'object' || typeof x === 'function') {\n // PromiseA+ 2.3.3.3.3 如果resolvePromise和rejectPromise都被調用,或者對同一個參數進行多次調用,那麼第一次調用優先,以後的調用都會被忽略。 \n let used;\n try {\n // PromiseA+ 2.3.3.1 let then be x.then\n // PromiseA+ 2.3.2 調用then方法已經包含了該條(該條是x是promise的處理)。\n let then = x.then;\n\n if (typeof then === 'function') {\n // PromiseA+ 2.3.3.3如果then是一個函數,用x作爲this調用它。第一個參數是resolvePromise,第二個參數是rejectPromise\n // PromiseA+ 2.3.3.3.1 如果resolvePromise用一個值y調用,運行[[Resolve]](promise, y)\n // PromiseA+ 2.3.3.3.2 如果rejectPromise用一個原因r調用,用r拒絕promise。\n then.call(x, (y) => {\n if (used) return;\n used = true;\n resolvePromise(promise, y, resolve, reject)\n }, (r) => {\n if (used) return;\n used = true;\n reject(r);\n })\n } else {\n // PromiseA+ 如果then不是一個函數,變爲fulfilled狀態並傳值爲x\n if (used) return;\n used = true;\n resolve(x);\n }\n } catch (e) {\n // PromiseA+ 2.3.3.2 如果檢索屬性x.then拋出異常e,則以e爲原因拒絕promise\n // PromiseA+ 2.3.3.4 如果調用then拋出異常,但是resolvePromise或rejectPromise已經執行,則忽略它\n if (used) return;\n used = true;\n reject(e);\n }\n\n } else {\n // PromiseA+ 2.3.4 如果x不是一個對象或函數,狀態變爲fulfilled並傳值x\n resolve(x);\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十三、 Promise.resolve()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 將現有對象轉爲 Promise 對象\n static resolve(value) {\n // 如果參數是 Promise 實例,那麼Promise.resolve將不做任何修改、原封不動地返回這個實例。\n if (value instanceof Promise) return value;\n\n // 參數是一個thenable對象(具有then方法的對象),Promise.resolve方法會將這個對象轉爲 Promise 對象,然後就立即執行thenable對象的then方法。\n if (typeof value === 'object' || typeof value === 'function') {\n try {\n let then = value.then;\n if (typeof then === 'function') {\n return new Promise(then.bind(value));\n }\n } catch (e) {\n return new Promise((resolve, reject) => {\n reject(e);\n })\n }\n }\n\n // 參數不是具有then方法的對象,或根本就不是對象,Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。\n return new Promise((resolve, reject) => {\n resolve(value);\n })\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十四、 Promise.reject()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 返回一個新的 Promise 實例,該實例的狀態爲rejected。\n static reject(reason) {\n return new Promise((resolve, reject) => {\n reject(reason);\n })\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十五、 Promise.all()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 用於將多個 Promise 實例,包裝成一個新的 Promise 實例。只有所有狀態都變爲fulfilled,p的狀態纔會是fulfilled\n static all(promises) {\n const values = [];\n let resolvedCount = 0;\n return new Promise((resolve, reject) => {\n promises.forEach((p, index) => {\n Promise.resolve(p).then(value => {\n resolvedCount++;\n values[index] = value;\n if (resolvedCount === promises.length) {\n resolve(values);\n }\n }, reason => {\n reject(reason);\n })\n })\n })\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十六、 Promise.race()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 只要有一個實例率先改變狀態,狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給回調函數。\n static race(promises) {\n return new Promise((resolve, reject) => {\n promises.forEach((p, index) => {\n Promise.resolve(p).then(value => {\n resolve(value);\n }, reason => {\n reject(reason);\n })\n })\n })\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十七、 Promise.catch()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 是.then(null, rejection)或.then(undefined, rejection)的別名,用於指定發生錯誤時的回調函數。\n catch(onRejected) {\n return this.then(undefined, onRejected);\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十八、 Promise.finally()"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"class Promise {\n // ...\n // 用於指定不管 Promise 對象最後狀態如何,都會執行的操作。\n finally(callback) {\n return this.then(\n value => Promise.resolve(callback()).then(() => value),\n reason => Promise.resolve(callback()).then(() => { throw reason })\n )\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二十九、Async實現原理"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是Async的實現原理,即將Generator函數作爲參數放入run函數中,最終實現自動執行並返回Promise對象。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function run(genF) {\n // 返回值是Promise\n return new Promise((resolve, reject) => {\n const gen = genF();\n function step(nextF) {\n let next;\n try {\n // 執行該函數,獲取一個有着value和done兩個屬性的對象\n next = nextF();\n } catch (e) {\n // 出現異常則將該Promise變爲rejected狀態\n reject(e);\n }\n\n // 判斷是否到達末尾,Generator函數到達末尾則將該Promise變爲fulfilled狀態\n if (next.done) {\n return resolve(next.value);\n }\n\n // 沒到達末尾,則利用Promise封裝該value,直到執行完畢,反覆調用step函數,實現自動執行\n Promise.resolve(next.value).then((v) => {\n step(() => gen.next(v))\n }, (e) => {\n step(() => gen.throw(e))\n })\n }\n\n step(() => gen.next(undefined));\n })\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三十、發佈訂閱模式"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7968c4cc1b12588e2a92d7fa45a96f6.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 發佈訂閱(TypeScript版)\ninterface Publish {\n registerObserver(eventType : string, subscribe : Subscribe) : void;\n remove(eventType : string, subscribe ?: Subscribe) : void;\n notifyObservers(eventType : string) : void;\n}\ninterface SubscribesObject{\n [key : string] : Array\n}\nclass ConcretePublish implements Publish {\n private subscribes : SubscribesObject;\n\n constructor() {\n this.subscribes = {};\n }\n\n registerObserver(eventType : string, subscribe : Subscribe) : void {\n if (!this.subscribes[eventType]) {\n this.subscribes[eventType] = [];\n }\n\n this.subscribes[eventType].push(subscribe);\n }\n\n remove(eventType : string, subscribe ?: Subscribe) : void {\n const subscribeArray = this.subscribes[eventType];\n if (subscribeArray) {\n if (!subscribe) {\n delete this.subscribes[eventType];\n } else {\n for (let i = 0; i < subscribeArray.length; i++) {\n if (subscribe === subscribeArray[i]) {\n subscribeArray.splice(i, 1);\n }\n }\n }\n }\n }\n\n notifyObservers(eventType : string, ...args : any[]) : void {\n const subscribes = this.subscribes[eventType];\n if (subscribes) {\n subscribes.forEach(subscribe => subscribe.update(...args))\n }\n }\n}\n\ninterface Subscribe {\n update(...value : any[]) : void;\n}\n\nclass ConcreteSubscribe1 implements Subscribe {\n public update(...value : any[]) : void {\n console.log('已經執行更新操作1,值爲', ...value);\n }\n}\nclass ConcreteSubscribe2 implements Subscribe {\n public update(...value : any[]) : void {\n console.log('已經執行更新操作2,值爲', ...value);\n }\n}\n\nfunction main() {\n const publish = new ConcretePublish();\n const subscribe1 = new ConcreteSubscribe1();\n const subscribe2 = new ConcreteSubscribe2();\n\n publish.registerObserver('1', subscribe1);\n publish.registerObserver('2', subscribe2);\n\n publish.notifyObservers('2', '22222');\n}\n\nmain();\n"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三十一、懶加載"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)首先,不要將圖片地址放到src屬性中,而是放到其它屬性(data-original)中。
"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)頁面加載完成後,根據scrollTop判斷圖片是否在用戶的視野內,如果在,則將data-original屬性中的值取出存放到src屬性中。
"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)在滾動事件中重複判斷圖片是否進入視野,如果進入,則將data-original屬性中的值取出存放到src屬性中。
"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"elementNode.getAttribute(name):方法通過名稱獲取屬性的值。
"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"elementNode.setAttribute(name, value):方法創建或改變某個新屬性。
"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"elementNode.removeAttribute(name):方法通過名稱刪除屬性的值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"//懶加載代碼實現\nvar viewHeight = document.documentElement.clientHeight;//可視化區域的高度\n\nfunction lazyload () {\n //獲取所有要進行懶加載的圖片\n let eles = document.querySelectorAll('img[data-original][lazyload]');//獲取屬性名中有data-original的\n Array.prototype.forEach.call(eles, function(item, index) {\n let rect;\n if(item.dataset.original === '') {\n return;\n }\n\n rect = item.getBoundingClientRect();\n\n //圖片一進入可視區,動態加載\n if(rect.bottom >= 0 && rect.top < viewHeight) {\n !function () {\n let img = new Image();\n img.src = item.dataset.original;\n img.onload = function () {\n item.src = img.src;\n }\n item.removeAttribute('data-original');\n item.removeAttribute('lazyload');\n }();\n }\n })\n}\n\nlazyload();\n\ndocument.addEventListener('scroll', lazyload);"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三十二、FileReader使用"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function uploadMulFile(uploadFile) {\n return new Promise((resolve, reject) => {\n let fileLength = 0;\n let reader = new FileReader();\n reader.readAsText(uploadFile[fileLength]);\n reader.onabort = function(e) {\n console.log(\"文件讀取異常\");\n }\n reader.onerror = function(e) {\n console.log(\"文件讀取錯誤\");\n }\n\n reader.onload = function(e){\n if(e.target.result) {\n\n fileLength++;\n if(fileLength < uploadFile.length) {\n reader.readAsText(uploadFile[fileLength]);\n }else{\n resolve({\n carArr,\n crossArr,\n roadArr\n })\n }\n }\n }\n })\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三十三、Ajax使用(非Promise版)"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function ajax(url) {\n var XHR;\n //進行性能檢測\n if(window.ActiveXObject) {\n XHR = new ActiveXObject(\"Microsoft.XMLHTTP\");//兼容IE//IE瀏覽器中使用的請求的方法,需實例化\n }else if(window.XMLHttpRequest) {\n XHR = new XMLHttpRequest();//標準瀏覽器中的使用的請求方法,需實例化\n }else{\n XHR = null;\n }\n\n if(XHR) {\n XHR.onreadystatechange = function() {\n //readyState:Ajax請求服務的狀態\n if(XHR.readyState == 4) {\n //status:頁面的響應碼\n if(XHR.status == 200) {\n //返回的數據以string的形式返回\n console.log(XHR.responseText);\n }\n }\n };\n\n XHR.open(\"get\", url);//open(‘method’,‘url’,boolean);參數1:請求方式;參數2:請求文件的地址;參數3:設置是否異步,true表示異步, 默認值爲 true 所以可以不寫。\n XHR.send();\n }\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三十四、Ajax使用(Promise版)"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"//Promise形式\nfunction ajaxPromise(method, url, data) {\n var xhr = null;\n if(window.ActiveXObject) {\n xhr = new ActiveXObject(\"Microsoft.XMLHTTP\");\n }else{\n xhr = new XMLHttpRequest();\n }\n\n return new Promise(function(resolve, reject) {\n xhr.onreadystatechange = function(){\n if(xhr.readyState === 4 ) {\n if(xhr.status === 200) {\n resolve(JSON.parse(xhr.responseText));\n }else{\n reject(xhr.status);\n }\n }\n };\n\n if(method.toUpperCase() == \"GET\") {\n var arr = [];\n for(var key in data) {\n arr.push(key + '=' + data[key]);\n }\n\n var getData = arr.join('&');\n xhr.open(\"GET\", url + \"?\" + getData, true);//true表示異步\n xhr.send(null);\n }else if(method.toUpperCase() == \"POST\") {\n xhr.open(\"POST\", url, true);\n xhr.responseType = \"json\";\n xhr.setRequestHeader('Content', 'application/x-www-form-urlencoded;charset=utf-8');\n xhr.send(data);\n }\n\n\n })\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三十五、JsonP"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"function jsonp(url, onsuccess, onerror, charset) {\n var hash = Math.random().toString().slice(2);\n window['jsonp' + hash] = function(data) {\n if(onsuccess && typeof onsuccess == 'function') {\n onsuccess(data);\n }\n }\n\n var script = createScript(url + \"?callback=jsonp\" + hash, charset); // 動態產檢一個script標籤\n\n //監聽加載成功的事件,獲取數據,這個位置用了兩個事件onload和onreadystatechange是爲了兼容IE,因爲IE9之前不支持onload事件,只支持onreadystatechange事件\n script.onload = script.onreadystatechange = function() {\n //若不存在readyState事件則證明不是IE瀏覽器,可以直接執行,若是的話,必須等到狀態變爲loaded或complete纔可以執行\n if(!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') {\n script.onload = script.onreadystatechange = null;\n // 移除該script的DOM對象\n if(script.parentNode) {\n script.parentNode.removeChild(script);\n }\n\n //刪除函數或變量\n window['jsonp' + hash] = null;\n }\n }\n\n script.onerror = function () {\n if(onerror && typeof onerror == 'function') {\n onerror();\n }\n }\n\n document.getElementsByTagName('head')[0].appendChild(script);//往html中增加這個標籤,目的是把請求發送出去\n}\n\nfunction createScript(url, charset) {\n var script = document.createElement('script');\n script.setAttribute('type', 'text/javascript');\n charset && script.setAttribute('charset', charset);\n script.setAttribute('src', url);\n script.async = true;\n}"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三十六、將一個字符串轉換爲駝峯形式"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"//方式一:操作字符串數組\nfunction transformStr2Hump1(str) {\n if(str == null) {\n return \"\";\n }\n var strArr = str.split('-');\n for(var i = 1; i < strArr.length; i++) {\n strArr[i] = strArr[i].charAt(0).toUpperCase() + strArr[i].substring(1);\n }\n return strArr.join('');\n}\n\n//方式二:操作字符數組\nfunction transformStr2Hump2(str) {\n if(str == null) {\n return \"\";\n }\n var strArr =str.split('');\n for(var i = 0; i < strArr.length; i++) {\n if(strArr[i] == \"-\"){\n //刪除-\n strArr.splice(i, 1);\n //將該處改爲大寫\n if(i < strArr.length) {\n strArr[i] = strArr[i].toUpperCase();\n }\n }\n }\n return strArr.join(\"\");\n}\n\n//方式三:利用正則\nfunction transformStr2Hump3(str) {\n if(str == null) {\n return \"\";\n }\n var reg = /-(\\w)/g;//匹配字母或數字或下劃線或漢字\n return str.replace(reg, function($0, $1) {\n return $1.toUpperCase();\n })\n}"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎大家關注公衆號(回覆“代碼實現”獲取本節的思維導圖,回覆“書籍”獲取大量前端學習資料,回覆“前端視頻”獲取大量前端教學視頻)"}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9a/9a3d2734b53d10fd2fd73b6e994d989d.jpeg","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章