欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript常見(jiàn)手寫(xiě)題超全匯總

 更新時(shí)間:2022年12月30日 11:12:09   作者:前端要努力QAQ  
作為前端開(kāi)發(fā),JS是重中之重,下面這篇文章主要給大家介紹了關(guān)于JavaScript常見(jiàn)手寫(xiě)題的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

之前有陸續(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ù)。 pipecompose 都是函數(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ù) foobar,他們兩個(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)
  • 定義 resolvereject 回調(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)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論