JavaScript常見(jiàn)手寫(xiě)題超全匯總
前言
之前有陸續(xù)寫(xiě)過(guò)一些 JavaScript
手寫(xiě)題的文章,但是時(shí)間有點(diǎn)久了而且感覺(jué)寫(xiě)的太散了,于是我把之前的文章都刪了,用這一篇文章做一個(gè)匯總,面試前看看 就醬~
對(duì)于手寫(xiě)題的理解:
手寫(xiě)題其實(shí)對(duì)面試者的要求會(huì)高一點(diǎn),因?yàn)椴粌H要考我們對(duì)于 API 的使用情況,還考我們對(duì)原理的理解,而且還要求寫(xiě)出來(lái)。所以我這里的建議是:
- 對(duì)于考點(diǎn)要知道是干嘛的,比如讓實(shí)現(xiàn)一個(gè)柯理化函數(shù),如果不知道什么是柯理化那肯定是實(shí)現(xiàn)不了的。
- 要對(duì)其使用了如指掌,比如考
forEach
的實(shí)現(xiàn),很多人都不知道forEach
第一個(gè)參數(shù)callbackFn
其實(shí)是有三個(gè)參數(shù)的,另外forEach
還有一個(gè)可選的thisArg
參數(shù)。 - 最后才是對(duì)原理的理解,建議可以記思路但不要死記代碼哈。
- 寫(xiě)完之后可以多檢測(cè),想想是否還有一些邊界情況值得考慮,會(huì)給面試官好的映象。
1. 實(shí)現(xiàn)一個(gè) compose 函數(shù)
compose
是組合的意思,它的作用故名思議就是將多個(gè)函數(shù)組合起來(lái)調(diào)用。
我們可以把 compose
理解為了方便我們連續(xù)執(zhí)行方法,把自己調(diào)用傳值的過(guò)程封裝了起來(lái),我們只需要給 compose
函數(shù)我們要執(zhí)行哪些方法,他會(huì)自動(dòng)的執(zhí)行。
實(shí)現(xiàn):
const add1 = (num) => { return num + 1; }; const mul5 = (num) => { return num * 5; }; const sub8 = (num) => { return num - 8; }; const compose = function (...args) { return function (num) { return args.reduceRight((res, cb) => cb(res), num); }; }; console.log(compose(sub8, mul5, add1)(1)); // 2
上面的例子,我要將一個(gè)數(shù)加1乘5再減8,可以一個(gè)一個(gè)函數(shù)調(diào)用,但是那樣會(huì)很麻煩,使用 compose
會(huì)簡(jiǎn)單很多。
compose
在函數(shù)式編程非常常見(jiàn),Vue
中就有很多地方使用 compose
,學(xué)會(huì)它有助于我們閱讀源碼。
注意 compose
是從右往左的順序執(zhí)行函數(shù)的,下面說(shuō)的 pipe
函數(shù) 是從左往右的順序執(zhí)行函數(shù)。 pipe
和 compose
都是函數(shù)式編程中的基本概念,能讓代碼變得更好~~
2. 實(shí)現(xiàn)一個(gè) pipe 函數(shù)
pipe
函數(shù)和 compose
函數(shù)功能一樣,只不過(guò)是從左往右執(zhí)行順序,只用將上面 reduceRight
方法改為 reduce
即可
const add1 = (num) => { return num + 1; }; const mul5 = (num) => { return num * 5; }; const sub8 = (num) => { return num - 8; }; const compose = function (...args) { return function (num) { return args.reduce((res, cb) => cb(res), num); }; }; console.log(compose(sub8, mul5, add1)(1)); // -34
3. 實(shí)現(xiàn)一個(gè) forEach 函數(shù)
forEach()
方法能對(duì)數(shù)組的每個(gè)元素執(zhí)行一次給定的函數(shù)。
需要注意的點(diǎn)有,
- 掛載到
Array
的原型上 - 具有
callBackFn
,thisArg
兩個(gè)參數(shù) callBackFn
是一個(gè)函數(shù), 且具有三個(gè)參數(shù)- 返回
undefined
Array.prototype.myForEach = function (callBackFn, thisArg) { if (typeof callBackFn !== "function") { throw new Error("callBackFn must be function"); } thisArg = thisArg || this; const len = thisArg.length; for (let i = 0; i < len; i++) { callBackFn.call(thisArg, thisArg[i], i, thisArg); } };
4. 實(shí)現(xiàn)一個(gè) map 函數(shù)
map()
方法創(chuàng)建一個(gè)新數(shù)組,這個(gè)新數(shù)組由原數(shù)組中的每個(gè)元素都調(diào)用一次提供的函數(shù)后的返回值組成。
需要注意的點(diǎn)有,
- 掛載到
Array
的原型上 - 具有
callBackFn
,thisArg
兩個(gè)參數(shù) callBackFn
是一個(gè)函數(shù), 且具有三個(gè)參數(shù)- 返回一個(gè)新數(shù)組,每個(gè)元素都是回調(diào)函數(shù)的返回值
Array.prototype.myMap = function (callbackFn, thisArg) { if (typeof callbackFn !== "function") { throw new Error("callbackFn must be function"); } const arr = []; thisArg = thisArg || this; const len = thisArg.length; for (let i = 0; i < len; i++) { arr.push(callbackFn.call(thisArg, thisArg[i], i, thisArg)); } return arr; };
5. 實(shí)現(xiàn)一個(gè) filter 函數(shù)
filter()
方法創(chuàng)建給定數(shù)組一部分的淺拷貝,其包含通過(guò)所提供函數(shù)實(shí)現(xiàn)的測(cè)試的所有元素。
需要注意的點(diǎn)有,
- 掛載到
Array
的原型上 - 具有
callBackFn
,thisArg
兩個(gè)參數(shù) callBackFn
是一個(gè)函數(shù), 且具有三個(gè)參數(shù)- 返回一個(gè)新數(shù)組,每個(gè)元素都需要通過(guò)回調(diào)函數(shù)測(cè)試,且為淺拷貝
Array.prototype.myFilter = function (callbackFn, thisArg) { if (typeof callbackFn !== "function") { throw new Error("must be function"); } const len = this.length; thisArg = thisArg || this const _newArr = []; for (let i = 0; i < len; i++) { if (callbackFn.call(thisArg, thisArg[i], i, thisArg)) { if (typeof thisArg[i] === "object") { _newArr.push(Object.create(thisArg[i])); } else { _newArr.push(thisArg[i]); } } } return _newArr; };
6. 自定義函數(shù):在對(duì)象中找出符合規(guī)則的屬性
這個(gè)其實(shí)跟 filter
挺像的,只不過(guò)一個(gè)是在數(shù)組中過(guò)濾元素,一個(gè)是在對(duì)象中過(guò)濾屬性。
需要注意的點(diǎn)有,
- 掛載到
Object
的原型上 - 具有
callBackFn
,thisArg
兩個(gè)參數(shù) callBackFn
是一個(gè)函數(shù), 且具有三個(gè)參數(shù)- 返回一個(gè)新數(shù)組,每個(gè)元素都需要通過(guò)回調(diào)函數(shù)測(cè)試,且為對(duì)象的屬性名。
Object.prototype.filterProperty = function (callbackFn, thisArg) { if (typeof callbackFn !== "function") { throw new Error("must be function"); } thisArg = thisArg || this; const propArray = []; for (let prop in thisArg) { if (callbackFn.call(thisArg, prop, thisArg[prop], thisArg)) { propArray.push(prop); } } return propArray; };
7. 實(shí)現(xiàn)一個(gè) bind 方法
bind()
方法創(chuàng)建一個(gè)新的函數(shù),在 bind()
被調(diào)用時(shí),這個(gè)新函數(shù)的 this
被指定為 bind()
的第一個(gè)參數(shù),而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用。
需要注意的點(diǎn)有,
- 掛載到
Function
的原型上 - 具有 thisArgs 和 原函數(shù)所需的其他參數(shù)
thisArgs
傳遞的任何值都需轉(zhuǎn)換為對(duì)象bind
中輸入的原函數(shù)所需的參數(shù)需要在返回函數(shù)中能接上,意思就是下面兩種方式都要支持
foo.bind(obj,1,2)(3) foo.bind(obj,1,2,3)
- 返回一個(gè)原函數(shù)的拷貝,并擁有指定的 this 值和初始參數(shù)。
Function.prototype.myBind = function (thisArgs, ...args1) { thisArgs = Object(thisArgs) const _self = this; // const args1 = Array.prototype.slice.call(arguments, 1); return function (...args2) { // const args2 = Array.prototype.slice.call(arguments, 1); return _self.apply(thisArgs, args1.concat(args2)); }; };
8. 實(shí)現(xiàn)一個(gè) call 方法
call()
方法使用一個(gè)指定的 this
值和單獨(dú)給出的一個(gè)或多個(gè)參數(shù)來(lái)調(diào)用一個(gè)函數(shù)。
需要注意的點(diǎn)有,
- 掛載到
Function
的原型上 - 具有
thisArgs
和 原函數(shù)所需的其他參數(shù) - 需要判斷
thisArgs
是否為undefined
, 如果為undefined
要他賦值全局的對(duì)象。 - 返回 函數(shù)調(diào)用結(jié)果
Function.prototype.myCall = function (thisArg, ...args) { if (thisArg) { thisArg = Object(thisArg); } else { thisArg = typeof window !== "undefined" ? window : global; } thisArg._fn = this; const result = thisArg._fn(...args); delete thisArg._fn; return result; };
9. 實(shí)現(xiàn)一個(gè) apply 方法
apply()
方法調(diào)用一個(gè)具有給定 this
值的函數(shù),以及以一個(gè)數(shù)組(或一個(gè)類數(shù)組對(duì)象)的形式提供的參數(shù)。
Function.prototype.myApply = function (thisArg, args) { if (thisArg) { thisArg = Object(thisArg); } else { thisArg = typeof window !== "undefined" ? window : global; } let result; if (!args) { result = thisArg._fn(); } else { result = thisArg._fn(...args); } delete thisArg._fn; return result; };
:::note{title="備注"} 雖然這個(gè)函數(shù)的語(yǔ)法與 call()
幾乎相同,但根本區(qū)別在于,call()
接受一個(gè)參數(shù)列表,而 apply()
接受一個(gè)參數(shù)的單數(shù)組。 :::
10. 實(shí)現(xiàn)一個(gè)能自動(dòng)柯里化的函數(shù)
10.1 什么是柯里化?
- 柯里化(英語(yǔ):Currying)是函數(shù)式編程里一個(gè)非常重要的概念。
- 是把接受多個(gè)參數(shù)的函數(shù),變成接受一個(gè)單一參數(shù)的函數(shù),并且會(huì)返回一個(gè)函數(shù),這個(gè)返回函數(shù)接收余下的參數(shù)。
- 柯里化聲稱 "如果你固定某些參數(shù),你將得到接受余下參數(shù)的一個(gè)函數(shù)"
舉個(gè)例子
下面有兩個(gè)函數(shù) foo
和 bar
,他們兩個(gè)調(diào)用的結(jié)果都相同, 1 + 2 + 3 + 4 = 10,但是調(diào)用的方式卻不同。
function foo(a,b,c,d) { return a + b + c + d } function bar(a) { return function(b) { return function(c) { return function(d) { return a + b + c + d } } } foo(1,2,3,4) // 10 bar(1)(2)(3)(4) // 10
將函數(shù)foo變成bar函數(shù)的過(guò)程就叫柯里化
上面的 bar
函數(shù)還可以簡(jiǎn)寫(xiě)成
const bar = a => b => c => d => a + b + c + d
10.2 為什么需要柯里化?
10.2.1 單一職責(zé)的原則
- 在函數(shù)式編程中,我們其實(shí)往往希望一個(gè)函數(shù)處理的問(wèn)題盡可能的單一,而不是將一大堆的處理過(guò)程交給一個(gè)函數(shù)來(lái)處理;
- 那么我們是否就可以將每次傳入的參數(shù)在單一的函數(shù)中進(jìn)行處理,處理完后就在下一個(gè)函數(shù)中再使用處理后的結(jié)果
我們把上面的例子改一下 現(xiàn)在我們需要對(duì)函數(shù)參數(shù)做一些操作,a = a +2
, b = b * 2
, c = -c
如果全都寫(xiě)在一個(gè)函數(shù)中是下面這樣的
function add (a,b,c) { a = a + 2 b = b * 2 c = -c return a + b + c }
而柯里化后
function add(a,b,c) { a = a + 2 return function(b,c) { b = b * 2 return function(c) { c = -c return a + b + c } } }
很明顯,柯里化后的函數(shù)單一性更強(qiáng)了,比如在最外層函數(shù)的邏輯就是對(duì)a進(jìn)行操作,第二層函數(shù)就是對(duì)b進(jìn)行操作,最內(nèi)層就是對(duì)c進(jìn)行操作
這是柯里化的第一個(gè)好處:更具有單一性
10.2.2 邏輯的復(fù)用
我們簡(jiǎn)化一下第一個(gè)例子,現(xiàn)在需要一個(gè)加法函數(shù)
function add(a,b){ return a + b } add(5,1) add(5,2) add(5,3) add(5,4)
可以發(fā)現(xiàn)每次都是5加上另一個(gè)數(shù)字,每次都要傳5其實(shí)是沒(méi)必要的
- 柯里化的優(yōu)化
function add(a,b) { // 復(fù)用的邏輯 console.log("+",a) return function(b) { return a + b } } const add5 = add(5) add5(2) add5(3) add5(5)
可以看到在外層的函數(shù)中的代碼被復(fù)用了,也可以說(shuō)是定制化了一個(gè)加5的函數(shù)
10.3. 最終實(shí)現(xiàn)
- 上面的幾個(gè)例子都是我們手動(dòng)寫(xiě)的柯里化函數(shù)。
- 有沒(méi)有辦法寫(xiě)一個(gè)函數(shù) 傳入一個(gè)函數(shù)自動(dòng)的返回一個(gè)柯里化函數(shù)?
(終于到了手寫(xiě)了^_^)
- 我們要實(shí)現(xiàn)一個(gè)函數(shù),這個(gè)函數(shù)能將普通的函數(shù)轉(zhuǎn)換成柯里化函數(shù),所以這個(gè)函數(shù)的框架就有了。
function currying(fn) { function curried(...args) { } return curried }
- 因?yàn)樵瘮?shù)的參數(shù)我們不確定,所以需要遞歸的組合原函數(shù)的參數(shù),直到 curried函數(shù)的參數(shù) 等于原函數(shù)fn的參數(shù)長(zhǎng)度時(shí),結(jié)束遞歸。
function currying(fn) { function curried(...args) { // 判斷當(dāng)前已接收的參數(shù)個(gè)數(shù)是否與fn函數(shù)一致 // 1.當(dāng)已經(jīng)傳入的參數(shù)數(shù)量 大于等于 需要的參數(shù)時(shí),就執(zhí)行函數(shù) if(args.length >= fn.length) { return fn.apply(this, args) } else { // 2.沒(méi)達(dá)到個(gè)數(shù)時(shí),需要返回一個(gè)新的函數(shù),繼續(xù)來(lái)接受參數(shù) return function curried2(...args2) { // 接收到參數(shù)后,需要遞歸調(diào)用curried來(lái)檢查函數(shù)的個(gè)數(shù)是否達(dá)到 return curried.apply(this, [...args, ...args2]) } } } return curried }
- 測(cè)試
function add (a,b,c,d) { return a + b + c + d } const curryingFn = currying(add); console.log(add(1, 2, 3, 4)); // 10 console.log(curryingFn(1)(2)(3)(4)); // 10 console.log(curryingFn(1, 2)(3)(4)); // 10 console.log(curryingFn(1, 2, 3)(4)); // 10 console.log(curryingFn(1, 2, 3, 4)); // 10
11. 實(shí)現(xiàn)一個(gè)防抖和節(jié)流函數(shù)
這題我會(huì)帶大家先認(rèn)識(shí)一下防抖和節(jié)流,然后在一步一步的實(shí)現(xiàn)一下,會(huì)有很多個(gè)版本,一步一步完善,但其實(shí)在實(shí)際的時(shí)候能基本實(shí)現(xiàn)v2版本就差不多了。
11.1 什么是防抖和節(jié)流?
11.1.1 認(rèn)識(shí)防抖 debounce 函數(shù)
場(chǎng)景:在實(shí)際開(kāi)發(fā)中,常常會(huì)碰到點(diǎn)擊一個(gè)按鈕請(qǐng)求網(wǎng)絡(luò)接口的場(chǎng)景,這時(shí)用戶如果因?yàn)?strong>手抖多點(diǎn)了幾下按鈕,就會(huì)出現(xiàn)短時(shí)間內(nèi)多次請(qǐng)求接口的情況,實(shí)際上這會(huì)造成性能的消耗,我們其實(shí)只需要監(jiān)聽(tīng)最后一次的按鈕,但是我們并不知道哪一次會(huì)是最后一次,就需要做個(gè)延時(shí)觸發(fā)的操作,比如這次點(diǎn)擊之后的300毫秒內(nèi)沒(méi)再點(diǎn)擊就視為最后一次。這就是防抖函數(shù)使用的場(chǎng)景
總結(jié)防抖函數(shù)的邏輯
- 當(dāng)事件觸發(fā)時(shí),相應(yīng)的函數(shù)并不會(huì)立即觸發(fā),而是等待一定的時(shí)間;
- 當(dāng)事件密集觸發(fā)時(shí),函數(shù)的觸發(fā)會(huì)被頻繁的推遲;
- 只有等待了一段時(shí)間也沒(méi)事件觸發(fā),才會(huì)真正的響應(yīng)函數(shù)
11.1.2 認(rèn)識(shí)節(jié)流 throttle 函數(shù)
場(chǎng)景:開(kāi)發(fā)中我們會(huì)有這樣的需求,在鼠標(biāo)移動(dòng)的時(shí)候做一些監(jiān)聽(tīng)的邏輯比如發(fā)送網(wǎng)絡(luò)請(qǐng)求,但是我們知道 document.onmousemove
監(jiān)聽(tīng)鼠標(biāo)移動(dòng)事件觸發(fā)頻率是很高的,我們希望按照一定的頻率觸發(fā),比如3秒請(qǐng)求一次。不管中間document.onmousemove
監(jiān)聽(tīng)到多少次只執(zhí)行一次。這就是節(jié)流函數(shù)的使用場(chǎng)景
總結(jié)節(jié)流函數(shù)的邏輯
- 當(dāng)事件觸發(fā)時(shí),會(huì)執(zhí)行這個(gè)事件的響應(yīng)函數(shù);
- 如果這個(gè)事件會(huì)被頻繁觸發(fā),那么節(jié)流函數(shù)會(huì)按照一定的頻率來(lái)執(zhí)行;
- 不管在這個(gè)中間有多少次觸發(fā)這個(gè)事件,執(zhí)行函數(shù)的頻繁總是固定的;
11.2. 實(shí)現(xiàn)防抖函數(shù)
11.2.1 基本實(shí)現(xiàn)v-1
const debounceElement = document.getElementById("debounce"); const handleClick = function (e) { console.log("點(diǎn)擊了一次"); }; // debounce防抖函數(shù) function debounce(fn, delay) { // 定一個(gè)定時(shí)器對(duì)象,保存上一次的定時(shí)器 let timer = null // 真正執(zhí)行的函數(shù) function _debounce() { // 取消上一次的定時(shí)器 if (timer) { clearTimeout(timer); } // 延遲執(zhí)行 timer = setTimeout(() => { fn() }, delay); } return _debounce; } debounceElement.onclick = debounce(handleClick, 300);
2.2 this-參數(shù)v-2
上面 handleClick
函數(shù)有兩個(gè)問(wèn)題,一個(gè)是 this
指向的是 window
,但其實(shí)應(yīng)該指向 debounceElement
,還一個(gè)問(wèn)題就是是無(wú)法傳遞傳遞參數(shù)。
優(yōu)化:
const debounceElement = document.getElementById("debounce"); const handleClick = function (e) { console.log("點(diǎn)擊了一次", e, this); }; function debounce(fn, delay) { let timer = null; function _debounce(...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, args) // 改變this指向 傳遞參數(shù) }, delay); } return _debounce; } debounceElement.onclick = debounce(handleClick, 300);
2.3 可選是否立即執(zhí)行v-3
有些時(shí)候我們想點(diǎn)擊按鈕的第一次就立即執(zhí)行,該怎么做呢?
優(yōu)化:
const debounceElement = document.getElementById("debounce"); const handleClick = function (e) { console.log("點(diǎn)擊了一次", e, this); }; // 添加一個(gè)immediate參數(shù) 選擇是否立即調(diào)用 function debounce(fn, delay, immediate = false) { let timer = null; let isInvoke = false; // 是否調(diào)用過(guò) function _debounce(...args) { if (timer) { clearTimeout(timer); } // 如果是第一次調(diào)用 立即執(zhí)行 if (immediate && !isInvoke) { fn.apply(this.args); isInvoke = true; } else { // 如果不是第一次調(diào)用 延遲執(zhí)行 執(zhí)行完重置isInvoke timer = setTimeout(() => { fn.apply(this, args); isInvoke = false; }, delay); } } return _debounce; } debounceElement.onclick = debounce(handleClick, 300, true);
2.4 取消功能v-4
有些時(shí)候我們?cè)O(shè)置延遲時(shí)間很長(zhǎng),在這段時(shí)間內(nèi)想取消之前點(diǎn)擊按鈕的事件該怎么做呢?
優(yōu)化:
const debounceElement = document.getElementById("debounce"); const cancelElemetnt = document.getElementById("cancel"); const handleClick = function (e) { console.log("點(diǎn)擊了一次", e, this); }; function debounce(fn, delay, immediate = false) { let timer = null; let isInvoke = false; function _debounce(...args) { if (timer) { clearTimeout(timer); } if (immediate && !isInvoke) { fn.apply(this.args); isInvoke = true; } else { timer = setTimeout(() => { fn.apply(this, args); isInvoke = false; }, delay); } } // 在_debounce新增一個(gè)cancel方法 用來(lái)取消定時(shí)器 _debounce.cancel = function () { clearTimeout(timer); timer = null; }; return _debounce; } const debonceClick = debounce(handleClick, 5000, false); debounceElement.onclick = debonceClick; cancelElemetnt.onclick = function () { console.log("取消了事件"); debonceClick.cancel(); };
2.5 返回值v-5(最終版本)
最后一個(gè)問(wèn)題,上面 handleClick
如果有返回值我們應(yīng)該怎么接收到呢
優(yōu)化:用Promise回調(diào)
const debounceElement = document.getElementById("debounce"); const cancelElemetnt = document.getElementById("cancel"); const handleClick = function (e) { console.log("點(diǎn)擊了一次", e, this); return "handleClick返回值"; }; function debounce(fn, delay, immediate = false) { let timer = null; let isInvoke = false; function _debounce(...args) { return new Promise((resolve, reject) => { if (timer) clearTimeout(timer); if (immediate && !isInvoke) { try { const result = fn.apply(this, args); isInvoke = true; resolve(result); // 正確的回調(diào) } catch (err) { reject(err); // 錯(cuò)誤的回調(diào) } } else { timer = setTimeout(() => { try { const result = fn.apply(this, args); isInvoke = false; resolve(result); // 正確的回調(diào) } catch (err) { reject(err); // 錯(cuò)誤的回調(diào) } }, delay); } }); } _debounce.cancel = function () { clearTimeout(timer); timer = null; }; return _debounce; } const debonceClick = debounce(handleClick, 300, true); // 創(chuàng)建一個(gè)debonceCallBack用于測(cè)試返回的值 const debonceCallBack = function (...args) { debonceClick.apply(this, args).then((res) => { console.log({ res }); }); }; debounceElement.onclick = debonceCallBack; cancelElemetnt.onclick = () => { console.log("取消了事件"); debonceClick.cancel(); };
11.3. 實(shí)現(xiàn)節(jié)流函數(shù)
11.3.1 基本實(shí)現(xiàn)v-1
這里說(shuō)一下最主要的邏輯,只要 這次監(jiān)聽(tīng)鼠標(biāo)移動(dòng)事件處觸發(fā)的時(shí)間減去上次觸發(fā)的時(shí)間大于我們?cè)O(shè)置的間隔就執(zhí)行想要執(zhí)行的操作就行了
nowTime−lastTime>intervalnowTime - lastTime > intervalnowTime−lastTime>interval
nowTime
:這次監(jiān)聽(tīng)鼠標(biāo)移動(dòng)事件處觸發(fā)的時(shí)間
lastTime
:監(jiān)聽(tīng)鼠標(biāo)移動(dòng)事件處觸發(fā)的時(shí)間
interval
:我們?cè)O(shè)置的間隔
const handleMove = () => { console.log("監(jiān)聽(tīng)了一次鼠標(biāo)移動(dòng)事件"); }; const throttle = function (fn, interval) { // 記錄當(dāng)前事件觸發(fā)的時(shí)間 let nowTime; // 記錄上次觸發(fā)的時(shí)間 let lastTime = 0; // 事件觸發(fā)時(shí),真正執(zhí)行的函數(shù) function _throttle() { // 獲取當(dāng)前觸發(fā)的時(shí)間 nowTime = new Date().getTime(); // 當(dāng)前觸發(fā)時(shí)間減去上次觸發(fā)時(shí)間大于設(shè)定間隔 if (nowTime - lastTime > interval) { fn(); lastTime = nowTime; } } return _throttle; }; document.onmousemove = throttle(handleMove, 1000);
11.3.2 this-參數(shù)v-2
和防抖一樣,上面的代碼也會(huì)有 this
指向問(wèn)題 以及 參數(shù)傳遞
優(yōu)化:
const handleMove = (e) => { console.log("監(jiān)聽(tīng)了一次鼠標(biāo)移動(dòng)事件", e, this); }; const throttle = function (fn, interval) { let nowTime; let lastTime = 0; function _throttle(...args) { nowTime = new Date().getTime(); if (nowTime - lastTime > interval) { fn.apply(this, args); lastTime = nowTime; } } return _throttle; }; document.onmousemove = throttle(handleMove, 1000);
11.3.3 可選是否立即執(zhí)行v-3
上面的函數(shù)第一次默認(rèn)是立即觸發(fā)的,如果我們想自己設(shè)定第一次是否立即觸發(fā)該怎么做呢?
優(yōu)化:
const handleMove = (e) => { console.log("監(jiān)聽(tīng)了一次鼠標(biāo)移動(dòng)事件", e, this); }; const throttle = function (fn, interval, leading = true) { let nowTime; let lastTime = 0; function _throttle(...args) { nowTime = new Date().getTime(); // leading為flase表示不希望立即執(zhí)行函數(shù) // lastTime為0表示函數(shù)沒(méi)執(zhí)行過(guò) if (!leading && lastTime === 0) { lastTime = nowTime; } if (nowTime - lastTime > interval) { fn.apply(this, args); lastTime = nowTime; } } return _throttle; }; document.onmousemove = throttle(handleMove, 3000, false);
11.3.4 可選最后一次是否執(zhí)行v-4(最終版本)
如果最后一次監(jiān)聽(tīng)的移動(dòng)事件與上一次執(zhí)行的時(shí)間不到設(shè)定的時(shí)間間隔,函數(shù)是不會(huì)執(zhí)行的,但是有時(shí)我們希望無(wú)論到?jīng)]到設(shè)定的時(shí)間間隔都能執(zhí)行函數(shù),該怎么做呢?
我們的邏輯是:因?yàn)槲覀儾恢滥囊淮螘?huì)是最后一次,所以每次都設(shè)置一個(gè)定時(shí)器,定時(shí)器的時(shí)間間隔是距離下一次執(zhí)行函數(shù)的時(shí)間;然后在每次進(jìn)來(lái)都清除上次的定時(shí)器。這樣就能保證如果這一次是最后一次,那么等到下一次執(zhí)行函數(shù)的時(shí)候就必定會(huì)執(zhí)行最后一次設(shè)定的定時(shí)器。
const handleMove = (e) => { console.log("監(jiān)聽(tīng)了一次鼠標(biāo)移動(dòng)事件", e, this); }; // trailing用來(lái)選擇最后一次是否執(zhí)行 const throttle = function (fn,interval,leading = true,trailing = false) { let nowTime; let lastTime = 0; let timer; function _throttle(...args) { nowTime = new Date().getTime(); // leading為flase表示不希望立即執(zhí)行函數(shù) // lastTime為0表示函數(shù)沒(méi)執(zhí)行過(guò) if (!leading && lastTime === 0) { lastTime = nowTime; } if (timer) { clearTimeout(timer); timer = null; } if (nowTime - lastTime >= interval) { fn.apply(this, args); lastTime = nowTime; return; } // 如果選擇了最后一次執(zhí)行 就設(shè)置一個(gè)定時(shí)器 if (trailing && !timer) { timer = setTimeout(() => { fn.apply(this, args); timer = null; lastTime = 0; }, interval - (nowTime - lastTime)); } } return _throttle; }; document.onmousemove = throttle(handleMove, 3000, true, true);
12.實(shí)現(xiàn)一個(gè)深拷貝
這題會(huì)考察面試者對(duì)邊界情況的考慮,比如深拷貝的對(duì)象會(huì)有很多類型,你是不是都考慮到了呢?
我的建議是最好能實(shí)現(xiàn)到最終版本。一般會(huì)考察的重點(diǎn)就是看你有沒(méi)有解決循環(huán)引用的問(wèn)題。
還有一個(gè)問(wèn)題是
12.1. 基本實(shí)現(xiàn) v-1
// 深拷貝函數(shù),參數(shù)是一個(gè)待拷貝的對(duì)象 function deepClone(originValue) { // 如果傳入的是null或者不是對(duì)象類型, 那么直接返回 if(originValue == null || typeof originValue !== 'object') { return originValue } // 創(chuàng)建一個(gè)新對(duì)象,遞歸拷貝屬性 const newObj = {} for(const key in originValue) { // 不拷貝原型上的 if(originValue.hasOwnProterty(key)) { newObj[key] = deepClone(originValue[key]) } } return newObj }
測(cè)試代碼
const obj = { name: 'zhangsan', address: { city: 'hangzhou' } } const newObj = deepClone(obj) obj.address.city = 'beijing' console.log(newObj) // { name: 'zhangsan', address: { city: 'hangzhou' } }
可以看到obj.address.city改成了'beijing'但是newObj.address.city還是hangzhou。說(shuō)明已經(jīng)對(duì)屬性是對(duì)象類型作了深拷貝
12.2. 增加數(shù)組類型v-2
function deepClone(originValue) { if(originValue == null || typeof originValue !== 'object') { return originValue } // 判斷傳入的是數(shù)組還是對(duì)象 const newObj = Array.isArray(originValue) ? [] : {} for(const key in originValue) { newObj[key] = deepClone(originValue[key]) } return newObj }
測(cè)試代碼
const obj = { name: 'zhangsan', address: { city: 'hangzhou' }, friends: ['zhangsan', 'lisi'] } const newObj = deepClone(obj) obj.address.city = 'beijing' obj.friends[0] = 'wangwu' console.log(newObj) // { // name: 'zhangsan', // address: { city: 'hangzhou' }, // friends: [ 'zhangsan', 'lisi' ] //}
可以看到obj.friends[0]改成了'wangwu'但是newObj.friends[0]還是zhangsan。說(shuō)明已經(jīng)對(duì)屬性是數(shù)組類型作了深拷貝
12.3. 增加函數(shù)類型v-3
函數(shù)一般認(rèn)為只使用來(lái)進(jìn)行代碼的復(fù)用,不需要進(jìn)行深拷貝,但若實(shí)現(xiàn)會(huì)一下更好。
function deepClone(originValue) { // 判斷是否是函數(shù)類型 if (originValue instanceof Function) { // 將函數(shù)轉(zhuǎn)成字符串 let str = originValue.toString() // 截取函數(shù)體內(nèi)容字符串 let subStr = str.substring(str.indexOf("{"), 1, str.lastIndexOf("}")) // 利用截取函數(shù)體內(nèi)容的字符串和函數(shù)的構(gòu)造器重新生成函數(shù)并返回 return new Function(subStr) } if(originValue == null || typeof originValue !== 'object') { return originValue } // 判斷傳入的是數(shù)組還是對(duì)象 const newObj = Array.isArray(originValue) ? [] : {} for(const key in originValue) { newObj[key] = deepClone(originValue[key]) } return newObj }
測(cè)試
const obj = { foo: function () { console.log("function"); }, }; const newObj = deepClone(obj); console.log(obj.foo === newObj.foo); // false
12.4. 增加Set、Map、Date類型v-4
function deepClone(originValue) { // 判斷是否是函數(shù)類型 if (originValue instanceof Function) { // 將函數(shù)轉(zhuǎn)成字符串 let str = originValue.toString() // 截取函數(shù)體內(nèi)容字符串 let subStr = str.substring(str.indexOf("{"), 1, str.lastIndexOf("}")) // 利用截取函數(shù)體內(nèi)容的字符串和函數(shù)的構(gòu)造器重新生成函數(shù)并返回 return new Function(subStr) } // 判斷是否是Date類型 if(originValue instanceof Date) { return new Date(originValue.getTime()) } // 判斷是否是Set類型(淺拷貝) if(originValue instanceof Set) { return new Set([...originValue]) } // 判斷是否是Map類型(淺拷貝) if(originValue instanceof Map) { return new Map([...originValue]) } if(originValue == null && typeof originValue !== 'object') { return originValue } const newObj = Array.isArray(originValue) ? [] : {} for(const key in originValue) { newObj[key] = deepClone(originValue[key]) } return newObj }
測(cè)試
const obj = { name: 'zhangsan', address: { city: 'hangzhou' }, friends: ['zhangsan', 'lisi'], set: new Set([1,2,3]), map: new Map([['aaa',111], ['bbb', '222']]), createTime: new Date() } const newObj = deepClone(obj) console.log(newObj.set === obj.set) // false console.log(newObj.map === obj.map) // false console.log(newObj.createTime === obj.createTime) // false
12.5. 增加Symbol類型v-5
function deepClone(originValue) { // 判斷是否是函數(shù)類型 if (originValue instanceof Function) { // 將函數(shù)轉(zhuǎn)成字符串 let str = originValue.toString() // 截取函數(shù)體內(nèi)容字符串 let subStr = str.substring(str.indexOf("{"), 1, str.lastIndexOf("}")) // 利用截取函數(shù)體內(nèi)容的字符串和函數(shù)的構(gòu)造器重新生成函數(shù)并返回 return new Function(subStr) } // 判斷是否是Date類型 if(originValue instanceof Date) { return new Date(originValue.getTime()) } // 判斷是否是Set類型(淺拷貝) if(originValue instanceof Set) { return new Set([...originValue]) } // 判斷是否是Map類型(淺拷貝) if(originValue instanceof Map) { return new Map([...originValue]) } // 判斷是否是Smybol類型 if(typeof originValue === 'symbol') { return Symbol(originValue.description) } if(originValue == null && typeof originValue !== 'object') { return originValue } const newObj = Array.isArray(originValue) ? [] : {} for(const key in originValue) { newObj[key] = deepClone(originValue[key]) } // 對(duì)key是Symbol做處理 const symbolKeys = Object.getOwnPropertySymbols(originValue) for(const key of symbolKeys) { newObj[key] = deepClone(originValue[key]) } return newObj }
測(cè)試
const s1 = Symbol('aaa') const s2 = Symbol('bbb') const obj = { name: 'zhangsan', address: { city: 'hangzhou' }, friends: ['zhangsan', 'lisi'], set: new Set([1,2,3]), map: new Map([['aaa',111], ['bbb', '222']]), createTime: new Date(), eating: function() { console.log(this.name + ' is eating') }, s1: s1, [s2]: {a: 1} } const newObj = deepClone(obj) console.log(obj.s1 === newObj.s1) // flase console.log(obj[s2] === newObj[s2]) // false
12.6. 增加循環(huán)引用(最終版)
// 參數(shù)中設(shè)置一個(gè)WeakMap用來(lái)保存 function deepClone(originValue, map = new WeakMap()) { // 判斷是否是函數(shù)類型 if (originValue instanceof Function) { // 將函數(shù)轉(zhuǎn)成字符串 let str = originValue.toString() // 截取函數(shù)體內(nèi)容字符串 let subStr = str.substring(str.indexOf("{"), 1, str.lastIndexOf("}")) // 利用截取函數(shù)體內(nèi)容的字符串和函數(shù)的構(gòu)造器重新生成函數(shù)并返回 return new Function(subStr) } // 判斷是否是Date類型 if(originValue instanceof Date) { return new Date(originValue.getTime()) } // 判斷是否是Set類型(淺拷貝) if(originValue instanceof Set) { return new Set([...originValue]) } // 判斷是否是Map類型(淺拷貝) if(originValue instanceof Map) { return new Map([...originValue]) } // 判斷是否是Smybol類型 if(typeof originValue === 'symbol') { return Symbol(originValue.description) } if(originValue == null && typeof originValue !== 'object') { return originValue } // 判斷之前是否存過(guò),如果有則直接獲取返回 if(map.has(originValue)) { return map.get(originValue) } const newObj = Array.isArray(originValue) ? [] : {} // 創(chuàng)建的newObj放到map里 map.set(originValue, newObj) for(const key in originValue) { newObj[key] = deepClone(originValue[key], map) } // 對(duì)key是Symbol做處理 const symbolKeys = Object.getOwnPropertySymbols(originValue) for(const key of symbolKeys) { newObj[key] = deepClone(originValue[key], map) } return newObj }
測(cè)試
const s1 = Symbol('aaa') const s2 = Symbol('bbb') const obj = { name: 'zhangsan', address: { city: 'hangzhou' }, friends: ['zhangsan', 'lisi'], set: new Set([1,2,3]), map: new Map([['aaa',111], ['bbb', '222']]), createTime: new Date(), eating: function() { console.log(this.name + ' is eating') }, s1: s1, [s2]: {a: 1} } obj.info= obj const newObj = deepClone(obj) console.log(newObj)
13. 實(shí)現(xiàn)一個(gè) new 函數(shù)
創(chuàng)建一個(gè)空對(duì)象obj
- 創(chuàng)建一個(gè)空對(duì)象
obj
- 將該對(duì)象的原型指向構(gòu)造函數(shù)的原型
- 將這個(gè)新對(duì)象綁定到函數(shù)的
this
上 - 執(zhí)行構(gòu)造函數(shù),把構(gòu)造函數(shù)的屬性添加到新對(duì)象
- 若無(wú)顯式返回對(duì)象或函數(shù),才返回
obj
對(duì)象
function _new(TargetClass, ...args) { // 1. 創(chuàng)建空對(duì)象,并將新對(duì)象的__proto__屬性指向構(gòu)造函數(shù)的prototype const obj = Object.create(TargetClass.prototype) // 2. 將這個(gè)新對(duì)象綁定到函數(shù)的this上,執(zhí)行構(gòu)造函數(shù) const ret =TargetClass.apply(obj, args) return typeof ret === "object" ? ret : obj; } // 測(cè)試 function Foo(name) { this.name = name; } Foo.prototype.sayHello = function () { console.log("this is " + this.name); }; const f = _new(Foo, "hello"); f.sayHello(); // this is hello console.log(f); // Foo { name: 'hello' }
14. 實(shí)現(xiàn)繼承
14.1. 通過(guò)原型鏈實(shí)現(xiàn)繼承
// 父類: 公共屬性和方法 function Person() { this.name = "yjw" } // 父類定義一個(gè)吃的方法 Person.prototype.eating = function() { console.log(this.name + ' is eating') } // 子類: 特有屬性和方法 function Student() { this.sno = '001' } // Student的原型對(duì)象指向一個(gè)Person的實(shí)例對(duì)象per const per = new Person() Student.prototype = per // 子類定義一個(gè)學(xué)習(xí)的方法 Student.prototype.studying = function() { console.log(this.name + ' is studying') } const stu = new Student() console.log(stu) console.log(stu.name) // stu對(duì)象中沒(méi)有name屬性 會(huì)去他的原型對(duì)象per上找 per對(duì)象上有name屬性 stu.eating() // stu對(duì)象中沒(méi)有eating方法 會(huì)去他的原型對(duì)象per上找per對(duì)象上也沒(méi)eating方法 再往上去per的原型對(duì)象上找 per的原型對(duì)象上有eating方法 stu.studying()
- 原型鏈實(shí)現(xiàn)繼承的弊端
- 通過(guò)直接打印對(duì)象是看不到這個(gè)屬性的(繼承屬性看不到)
- 屬性被多個(gè)對(duì)象共享,如果這個(gè)屬性是一個(gè)引用類型,會(huì)造成問(wèn)題 (修改引用類型 會(huì)互相影響)
- 不能給
Person
傳遞參數(shù),因?yàn)檫@個(gè)對(duì)象是一次性創(chuàng)建的(沒(méi)辦法定制化)
2. 借用構(gòu)造函數(shù)繼承
// 父類: 公共屬性和方法 function Person(name) { this.name = name } // 父類定義一個(gè)吃的方法 Person.prototype.eating = function() { console.log(this.name + ' is eating') } // 子類: 特有屬性和方法 function Student(name, sno) { // 借用了父類的構(gòu)造函數(shù) Person.call(this, name) this.sno = sno } // Student的原型對(duì)象指向一個(gè)Person的實(shí)例對(duì)象per const per = new Person() Student.prototype = per // 子類定義一個(gè)學(xué)習(xí)的方法 Student.prototype.studying = function() { console.log(this.name + ' is studying') }
借用構(gòu)造函數(shù)繼承解決了上面的三個(gè)問(wèn)題。但還是不夠完美
- 存在的問(wèn)題
- 會(huì)調(diào)用兩次父類的構(gòu)造函數(shù)
- 子類對(duì)象的原型對(duì)象上會(huì)多出沒(méi)用的屬性
3. 寄生組合式繼承
// 父類: 公共屬性和方法 function Person(name) { this.name = name } // 父類定義一個(gè)吃的方法 Person.prototype.eating = function() { console.log(this.name + ' is eating') } // 子類: 特有屬性和方法 function Student(name, sno) { // 借用了父類的構(gòu)造函數(shù) Person.call(this, name) this.sno = sno } Student.prototype = Object.create(Person.prototype) // 原型式繼承 不用new Person()多調(diào)用父類構(gòu)造函數(shù)了 Object.defineProperty(Student.prototype, "constructor", { enumerable: false, configurable: true, writable: true, value: Student }) // 改構(gòu)造函數(shù)名稱 // 子類定義一個(gè)學(xué)習(xí)的方法 Student.prototype.studying = function() { console.log(this.name + ' is studying') }
// 上面的 Object.create(Person.prototype) 也可以寫(xiě)成 Object.setPrototypeOf(Student.prototype, Person.prototype) // 也可以寫(xiě)成 function object(o) { function F() {} F.prototype = o return new F() } Student.prototype = object(Person.prototyp)
- 注意:不要讓子類型的原型對(duì)象 = 父類型的原型對(duì)象,因?yàn)檫@么做意味著以后修改了子類型原型對(duì)象的某個(gè)引用類型的時(shí)候, 父類型原生對(duì)象的引用類型也會(huì)被修改
15. 手寫(xiě)實(shí)現(xiàn)一個(gè) Promise 類
15.1. Promise 的結(jié)構(gòu)設(shè)計(jì)
class MyPromise { /** * Promise規(guī)范定義 構(gòu)造函數(shù)接收一個(gè)回調(diào)函數(shù)executor作為參數(shù) * 這個(gè)executor回調(diào)函數(shù)又接收兩個(gè)回調(diào)函數(shù)作為參數(shù) 一個(gè)是resolve(成功時(shí)回調(diào)) 一個(gè)是reject(時(shí)表示回調(diào)) */ constructor(executor) { // Promise 三種狀態(tài) 分別是pending(進(jìn)行中)、fulfilled(已成功)和rejected(已失敗) this.status = 'pending' this.value = undefined this.reason = undefined const resolve = (value) => { if(this.status === 'pending') { // 調(diào)用resolve 狀態(tài)改為fulfilled this.status = 'fulfilled' // 保存resolve回調(diào)的參數(shù) this.value = value } } const reject = (reason) => { if(this.status === 'pending') { // reject 狀態(tài)改為rejected this.status = 'rejected' // 保存reject回調(diào)的參數(shù) this.reason = reason } } // 傳入的回調(diào)函數(shù)是會(huì)直接執(zhí)行的 executor(resolve,reject) } }
15.2. then 方法的設(shè)計(jì)
class MyPromise { constructor(executor) { this.status = 'pending' this.value = undefined this.reason = undefined const resolve = (value) => { if(this.status === 'pending') { // 延遲調(diào)用(微任務(wù)) queueMicrotask(() => { this.status = 'fulfilled' this.value = value this.onFulfilled && this.onFulfilled(this.value) }, 0) } } const reject = (reason) => { if(this.status === 'pending') { // 延遲調(diào)用(微任務(wù)) queueMicrotask(() => { this.status = 'rejected' this.reason = reason this.onRejected && this.onRejected(this.reason) }, 0) } } executor(resolve,reject) } then(onFulfilled, onRejected) { onFulfilled && this.onFulfilled = onFulfilled onRejected && this.onRejected = onRejected } } const promise = new MyPromise((resolve, reject) => { resolve('resolve') reject('reject') }) promise.then(res => { console.log({res}) }, err => { console.log({err}) })
上面 then
方法還有幾個(gè)點(diǎn)需要優(yōu)化
- 目前不支持多次調(diào)用(解決方法:將
then
方法中的回調(diào)函數(shù)保存到數(shù)組中) - 不支持鏈?zhǔn)秸{(diào)用 (解決方法:
then
方法中返回Promise
) - 如果
then
方法在resolve
已經(jīng)執(zhí)行后再執(zhí)行,目前then
中的方法不能調(diào)用 (解決方法:then
方法中做判斷,如果調(diào)用的時(shí)候狀態(tài)已經(jīng)確定下來(lái),直接調(diào)用)
setTimeout(() => { promise.then(res =>{ console.log({res}) }) }, 10000)
15.3. then 方法的優(yōu)化
// 封裝一個(gè)函數(shù) const execFunctionWithCatchError = (exeFn, value, resolve, reject) => { try { const result = exeFn(value) resolve(result) } catch(err) { reject(err) } } class MyPromise { constructor(executor) { this.status = 'pending' this.value = undefined this.reason = undefined this.onFulfilledFns = [] this.onRejectFns = [] const resolve = (value) => { if(this.status === 'pending') { queueMicrotask(() => { if(this.status !== 'pending') return this.status = 'fulfilled' this.value = value this.onFulfilledFns.forEach(fn => { fn && fn(this.value) }) }, 0) } } const reject = (reason) => { if(this.status === 'pending') { queueMicrotask(() => { if(this.status !== 'pending') return this.status = 'rejected' this.reason = reason this.onRejectFns.forEach(fn => { fn && fn(this.reason) }) }, 0) } } try { executor(resolve,reject) } catch(err) { reject(err) } } then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { // 如果在then調(diào)用的時(shí)候狀態(tài)已經(jīng)確定下來(lái),直接調(diào)用 if(this.status === 'fulfilled' && onFulfilled) { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) } // 如果在then調(diào)用的時(shí)候狀態(tài)已經(jīng)確定下來(lái),直接調(diào)用 if(this.status === 'rejected' && onRejected) { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) } if(this.status === 'pending') { // 將成功的回調(diào)和失敗的回調(diào)都放到數(shù)組中 if(onFulfilled) this.onFulfilledFns.push(() => { execFunctionWithCatchError(onFulfilled, this.value, resolve, reject) }) if(onRejected) this.onRejectFns.push(() => { execFunctionWithCatchError(onRejected, this.reason, resolve, reject) }) } }) } }
15.4. catch 方法的實(shí)現(xiàn)
catch(onRejected) { return this.then(undefined, onRejected) } then(onFulfilled, onRejected) { // 當(dāng)onRejected為空時(shí) 我們手動(dòng)拋出一個(gè)錯(cuò)誤 onRejected = onRejected || (err => { throw err }) return new return new MyPromise((resolve, reject) => { ... }) }
15.5. finally 方法的實(shí)現(xiàn)
finally(onFinally) { // 不管成功和失敗都調(diào)用onFinally this.then(onFinally, onFinally) } then(onFulfilled, onRejected) { // 當(dāng)onRejected為空時(shí) 我們手動(dòng)拋出一個(gè)錯(cuò)誤 onRejected = onRejected || (err => { throw err }) // 當(dāng)onFulfilled為空時(shí) 將上一個(gè)promise的value傳下去 onFulfilled = onFulfilled || (value => value) return new return new MyPromise((resolve, reject) => { ... }) }
15.6. resolve 和 reject 的實(shí)現(xiàn)
static resolve(value) { return new MyPromise((resolve) => resolve(value)) } static reject(reason) { return new MyPromise((resolve, reject) => reject(reason)) }
15.7. all 和 allSettled 的實(shí)現(xiàn)
all
: 參數(shù)是一個(gè) promises數(shù)組 返回一個(gè) promise在所有
promise
執(zhí)行成功后返回所有promise
結(jié)果的一個(gè)數(shù)組 有一個(gè)promise
失敗就reject
不管有沒(méi)有
promise
rejected
,都返回所有promise
結(jié)果
static all(promises) { return new MyPromise((resolve, reject) => { const values = [] promises.forEach(promise => { promise.then(res => { values.push(res) if(values.length === promises.length) { resolve(values) } }, err => { reject(err) }) }) }) } static allSettled(promises) { return new MyPromise((resolve, reject) => { const result = [] promises.forEach(promise => { promise.then(res => { result.push({state: 'resolved', value: res}) if(result.length === promises.length) { resolve(result) } }, err => { result.push({state: 'rejected', reason: err}) if(result.length === promises.length) { resolve(result) } }) }) }) }
15.8. race 和 any 的實(shí)現(xiàn)
- race: 返回一個(gè)
promise
,只要一個(gè)promise
有結(jié)果 立刻返回(競(jìng)賽) - any: 必須等到有一個(gè)正確的結(jié)果才返回, 沒(méi)有正確的結(jié)果會(huì)返回一個(gè)合并的異常
static race(promises) { return new MyPromise((resolve, reject) => { promises.forEach(promise => { promise.then(res => { resolve(res) }, err => { reject(err) }) }) }) } static any(promises) { return new MyPromise((resolve, reject) => { const reasons = [] promises.forEach(promise => { promise.then(res => { resolve(res) }, err => { reasons.push(err) if(reasons.length === promises.length) { reject(reasons) } }) }) }) }
15.9. 總結(jié)
- 構(gòu)造函數(shù)里的邏輯:
- 定義狀態(tài)
- 定義
resolve
、reject
回調(diào) resolve
執(zhí)行微任務(wù)隊(duì)列:改變狀態(tài)、獲取value、then傳入執(zhí)行成功回調(diào)reject
執(zhí)行微任務(wù)隊(duì)列:改變狀態(tài)、獲取reason、then傳入執(zhí)行失敗回調(diào)
- then 方法的邏輯:
- 判斷
onFulfilled
、onRejected
為空給默認(rèn)值 - 返回
Promise
resovle/reject
支持鏈?zhǔn)秸{(diào)用、 - 判斷之前的
promise
狀態(tài)是否確定 確定的話onFufilled/onRejected
直接執(zhí)行 - 添加到數(shù)組中
push(() => { 執(zhí)行onFulfilled/onRejected 直接執(zhí)行代碼 })
總結(jié)
到此這篇關(guān)于JavaScript常見(jiàn)手寫(xiě)題超全匯總的文章就介紹到這了,更多相關(guān)JS常見(jiàn)手寫(xiě)題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- javascript 星級(jí)評(píng)分效果(手寫(xiě))
- 純JavaScript手寫(xiě)圖片輪播代碼
- Angularjs 手寫(xiě)日歷的實(shí)現(xiàn)代碼(不用插件)
- JS實(shí)現(xiàn)手寫(xiě)parseInt的方法示例
- 自己動(dòng)手寫(xiě)的javascript前端等待控件
- JS手寫(xiě)一個(gè)自定義Promise操作示例
- JS實(shí)現(xiàn)手寫(xiě) forEach算法示例
- 如何從零開(kāi)始利用js手寫(xiě)一個(gè)Promise庫(kù)詳解
- 一些手寫(xiě)JavaScript常用的函數(shù)匯總
相關(guān)文章
幾種設(shè)置表單元素中文本輸入框不可編輯的方法總結(jié)
這篇文章主要是對(duì)幾種設(shè)置表單元素中文本輸入框不可編輯的方法進(jìn)行了總結(jié)介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-11-11微信小程序中做用戶登錄與登錄態(tài)維護(hù)的實(shí)現(xiàn)詳解
微信小程序的運(yùn)行環(huán)境不是在瀏覽器下運(yùn)行的。所以不能以cookie來(lái)維護(hù)登錄態(tài)。下面這篇文章主要給大家介紹了微信小程序中如何做用戶登錄與登錄態(tài)維護(hù)的相關(guān)資料,文中介紹的非常詳細(xì),需要的朋友可以參考學(xué)習(xí)。2017-05-05JSON傳遞bool類型數(shù)據(jù)的處理方式介紹
如果服務(wù)器端生成的JSON中有bool類型的數(shù)據(jù)時(shí),到客戶端解析時(shí)出現(xiàn)了小小的問(wèn)題,下面簡(jiǎn)單為大家介紹下正確的處理方式2013-09-09encode腳本和normal腳本混用的問(wèn)題與解決方法
encode腳本和normal腳本混用的問(wèn)題與解決方法...2007-03-03Echart結(jié)合圓形實(shí)現(xiàn)儀表盤(pán)的繪制詳解
EChart開(kāi)源來(lái)自百度商業(yè)前端數(shù)據(jù)可視化團(tuán)隊(duì),基于html5?Canvas,是一個(gè)純Javascript圖表庫(kù),提供直觀,生動(dòng),可交互,可個(gè)性化定制的數(shù)據(jù)可視化圖表。本文將利用EChart實(shí)現(xiàn)儀表盤(pán)的繪制,感興趣的可以學(xué)習(xí)一下2022-03-03JavaScript實(shí)現(xiàn)九宮格拖拽效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)九宮格拖拽效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06使用JS location實(shí)現(xiàn)搜索框歷史記錄功能
這篇文章主要介紹了使用JS location實(shí)現(xiàn)搜索框歷史記錄功能,本文通過(guò)實(shí)例 代碼講解的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12Aptos?SDK交互實(shí)現(xiàn)過(guò)程詳解
這篇文章主要為大家介紹了Aptos?SDK交互實(shí)現(xiàn)過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03