日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

前端为什么有的接口明明是成功回调却执行了.catch失败回调_前端进阶高薪必看-手写源码篇(高频技术点)...

發布時間:2024/7/5 HTML 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 前端为什么有的接口明明是成功回调却执行了.catch失败回调_前端进阶高薪必看-手写源码篇(高频技术点)... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

此系列作為筆者之前發過的前端高頻面試整理的補充 會比較偏向中高前端面試問題 當然大家都是從新手一路走過來的 感興趣的朋友們都可以看哈

初衷

我相信不少同學面試的時候最怕的一個環節就是手寫代碼 大家一定聽過這句話talk is cheap, show me the code 沒事 此文章不僅包含了前端經典的手寫源碼面試題 還包含了大量的分析和引導 希望能幫助大家更好的食用(歡迎評論 不定時更新補充題庫)

注意

本文所有的手寫源碼實現都是基于 es6 的 不想用原生去實現原因如下:一方面是網上太多原生實現的方案了 另一方面是我們要面向未來編程 多使用 es6 的特性更加貼合實際工作

1 promise

先思考?

  • promise 是什么?
  • 異步回調解決方案

  • promise 如何保證異步執行完了再去執行后面的代碼?
  • 使用 then 關鍵字 then 接受兩個參數 第一個參數(函數)會在 promise resolve 之后執行 第二個參數(函數)會在 promise reject 之后執行

  • 為什么能在異步事件執行完成的回調之后再去觸發 then 中的函數?
  • 引入事件注冊機制(將 then 中的代碼注冊事件 當異步執行完了之后再去觸發事件)

  • 怎么保證 promise 鏈式調用 形如 promise.then().then()
  • 每個 then 返回的也是一個 promise 對象

  • 怎么知道異步事件執行完畢或者執行失敗?
  • 需要狀態表示


    具體實現如下

    //這里使用es6 class實現class Mypromise { constructor(fn) { // 表示狀態 this.state = "pending"; // 表示then注冊的成功函數 this.successFun = []; // 表示then注冊的失敗函數 this.failFun = []; let resolve = val => { // 保持狀態改變不可變(resolve和reject只準觸發一種) if (this.state !== "pending") return; // 成功觸發時機 改變狀態 同時執行在then注冊的回調事件 this.state = "success"; // 為了保證then事件先注冊(主要是考慮在promise里面寫同步代碼) promise規范 這里為模擬異步 setTimeout(() => { // 執行當前事件里面所有的注冊函數 this.successFun.forEach(item => item.call(this, val)); }); }; let reject = err => { if (this.state !== "pending") return; // 失敗觸發時機 改變狀態 同時執行在then注冊的回調事件 this.state = "fail"; // 為了保證then事件先注冊(主要是考慮在promise里面寫同步代碼) promise規范 這里模擬異步 setTimeout(() => { this.failFun.forEach(item => item.call(this, err)); }); }; // 調用函數 try { fn(resolve, reject); } catch (error) { reject(error); } } // 實例方法 then then(resolveCallback, rejectCallback) { // 判斷回調是否是函數 resolveCallback = typeof resolveCallback !== "function" ? v => v : resolveCallback; rejectCallback = typeof rejectCallback !== "function" ? err => { throw err; } : rejectCallback; // 為了保持鏈式調用 繼續返回promise return new Mypromise((resolve, reject) => { // 將回調注冊到successFun事件集合里面去 this.successFun.push(val => { try { // 執行回調函數 let x = resolveCallback(val); //(最難的一點) // 如果回調函數結果是普通值 那么就resolve出去給下一個then鏈式調用 如果是一個promise對象(代表又是一個異步) 那么調用x的then方法 將resolve和reject傳進去 等到x內部的異步 執行完畢的時候(狀態完成)就會自動執行傳入的resolve 這樣就控制了鏈式調用的順序 x instanceof Mypromise ? x.then(resolve, reject) : resolve(x); } catch (error) { reject(error); } }); this.failFun.push(val => { try { // 執行回調函數 let x = rejectCallback(val); x instanceof Mypromise ? x.then(resolve, reject) : reject(x); } catch (error) { reject(error); } }); }); } //靜態方法 static all(promiseArr) { let result = []; //聲明一個計數器 每一個promise返回就加一 let count = 0 return new Mypromise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { promiseArr[i].then( res => { //這里不能直接push數組 因為要控制順序一一對應(感謝評論區指正) result[i] = res count++ //只有全部的promise執行成功之后才resolve出去 if (count === promiseArr.length) { resolve(result); } }, err => { reject(err); } ); } }); } //靜態方法 static race(promiseArr) { return new Mypromise((resolve, reject) => { for (let i = 0; i < promiseArr.length; i++) { promiseArr[i].then( res => { //promise數組只要有任何一個promise 狀態變更 就可以返回 resolve(res); }, err => { reject(err); } ); } }); }}// 使用let promise1 = new Mypromise((resolve, reject) => { setTimeout(() => { resolve(123); }, 2000);});let promise2 = new Mypromise((resolve, reject) => { setTimeout(() => { resolve(1234); }, 1000);});// Mypromise.all([promise1,promise2]).then(res=>{// console.log(res);// })// Mypromise.race([promise1, promise2]).then(res => {// console.log(res);// });promise1 .then( res => { console.log(res); //過兩秒輸出123 return new Mypromise((resolve, reject) => { setTimeout(() => { resolve("success"); }, 1000); }); }, err => { console.log(err); } ) .then( res => { console.log(res); //再過一秒輸出success }, err => { console.log(err); } );復制代碼

    擴展:如何取消 promise

    先思考?

    怎么才能取消已經發起的異步呢?

    Promise.race()方法可以用來競爭 Promise 誰的狀態先變更就返回誰那么可以借助這個 自己包裝一個 假的 promise 與要發起的 promise 來實現


    具體實現如下

    function wrap(pro) { let obj = {}; // 構造一個新的promise用來競爭 let p1 = new Promise((resolve, reject) => { obj.resolve = resolve; obj.reject = reject; }); obj.promise = Promise.race([p1, pro]); return obj;}let testPro = new Promise((resolve, reject) => { setTimeout(() => { resolve(123); }, 1000);});let wrapPro = wrap(testPro);wrapPro.promise.then(res => { console.log(res);});wrapPro.resolve("被攔截了");復制代碼

    2 防抖節流

    先思考

  • 防抖和節流區別
  • 防抖是 N 秒內函數只會被執行一次,如果 N 秒內再次被觸發,則重新計算延遲時間(舉個極端的例子 如果 window 滾動事件添加了防抖 2s 執行一次 如果你不停地滾動 永遠不停下 那這個回調函數就永遠無法執行)

    節流是規定一個單位時間,在這個單位時間內最多只能觸發一次函數執行(還是滾動事件 如果你一直不停地滾動 那么 2 秒就會執行一次回調)

  • 防抖怎么保證
  • 事件延遲執行 并且在規定時間內再次觸發需要清除 這個很容易就想到了 setTimeout

  • 節流怎么保證
  • 在單位時間內觸發了一次就不再生效了 可以用一個 flag 標志來控制


    具體實現如下

    // 防抖function debounce(fn, delay=300) { //默認300毫秒 let timer; return function() { var args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.apply(this, args); // 改變this指向為調用debounce所指的對象 }, delay); };}window.addEventListener( "scroll", debance(() => { console.log(111); }, 1000));// 節流//方法一:設置一個標志function throttle(fn, delay) { let flag = true; return () => { if (!flag) return; flag = false; timer = setTimeout(() => { fn(); flag = true; }, delay); };}//方法二:使用時間戳function throttle(fn, delay) { let startTime = new Date(); return () => { let endTime = new Date(); if (endTime - startTime >= delay) { fn(); startTime = endTime; } else { return; } };}window.addEventListener( "scroll", throttle(() => { console.log(111); }, 1000));復制代碼

    防抖節流屬于性能優化的一點 更多性能優化擴展請點擊 性能優化

    3 EventEmitter(發布訂閱模式--簡單版)

    先思考?

  • 什么是發布訂閱模式
  • 發布-訂閱模式其實是一種對象間一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都將得到狀態改變的通知

  • 怎么實現一對多
  • 既然一對多 肯定有一個事件調度中心用來調度事件 訂閱者可以注冊事件(on)到事件中心 發布者可以發布事件(emit)到調度中心 訂閱者也可以取消訂閱(off)或者只訂閱一次(once)


    具體實現如下

    // 手寫發布訂閱模式 EventEmitterclass EventEmitter { constructor() { this.events = {}; } // 實現訂閱 on(type, callBack) { if (!this.events) this.events = Object.create(null); if (!this.events[type]) { this.events[type] = [callBack]; } else { this.events[type].push(callBack); } } // 刪除訂閱 off(type, callBack) { if (!this.events[type]) return; this.events[type] = this.events[type].filter(item => { return item !== callBack; }); } // 只執行一次訂閱事件 once(type, callBack) { function fn() { callBack(); this.off(type, fn); } this.on(type, fn); } // 觸發事件 emit(type, ...rest) { this.events[type] && this.events[type].forEach(fn => fn.apply(this, rest)); }}// 使用如下const event = new EventEmitter();const handle = (...rest) => { console.log(rest);};event.on("click", handle);event.emit("click", 1, 2, 3, 4);event.off("click", handle);event.emit("click", 1, 2);event.once("dbClick", () => { console.log(123456);});event.emit("dbClick");event.emit("dbClick");復制代碼

    4 call、apply、bind

    先思考?

  • call 用法
  • 第一個參數 可以改變調用函數的 this 指向 第二個以及之后的參數為傳入的函數的參數

    let obj = { a: 1};function fn(name, age) { console.log(this.a); //1 console.log(name); console.log(age);}fn.call(obj, "我是 lihua", "18");復制代碼
  • 怎么改變 this 指向呢
  • 根據 this 特性 對象的方法調用 那么方法內部的 this 就指向這個對象

    let obj = { a: 1, fn(name, age) { console.log(this.a); //1 console.log(name); console.log(age); }};obj.fn("我是lihua", "18");復制代碼
  • 怎么獲取傳入的不定參數呢
  • 利用 es6 ...args 剩余參數獲取方法(rest)


    具體實現如下

    Function.prototype.myCall = function(context, ...args) { if (!context || context === null) { context = window; } // 創造唯一的key值 作為我們構造的context內部方法名 let fn = Symbol(); context[fn] = this; //this指向調用call的函數 // 執行函數并返回結果 相當于把自身作為傳入的context的方法進行調用了 return context[fn](...args);};// apply原理一致 只是第二個參數是傳入的數組Function.prototype.myApply = function(context, args) { if (!context || context === null) { context = window; } // 創造唯一的key值 作為我們構造的context內部方法名 let fn = Symbol(); context[fn] = this; // 執行函數并返回結果 return context[fn](...args);};//測試一下 call 和 applylet obj = { a: 1};function fn(name, age) { console.log(this.a); console.log(name); console.log(age);}fn.myCall(obj, "我是lihua", "18");fn.myApply(obj, ["我是lihua", "18"]);let newFn = fn.myBind(obj, "我是lihua", "18");newFn();//bind實現要復雜一點 因為他考慮的情況比較多 還要涉及到參數合并(類似函數柯里化)Function.prototype.myBind = function (context, ...args) { if (!context || context === null) { context = window; } // 創造唯一的key值 作為我們構造的context內部方法名 let fn = Symbol(); context[fn] = this; let _this = this // bind情況要復雜一點 const result = function (...innerArgs) { // 第一種情況 :若是將 bind 綁定之后的函數當作構造函數,通過 new 操作符使用,則不綁定傳入的 this,而是將 this 指向實例化出來的對象 // 此時由于new操作符作用 this指向result實例對象 而result又繼承自傳入的_this 根據原型鏈知識可得出以下結論 // this.__proto__ === result.prototype //this instanceof result =>true // this.__proto__.__proto__ === result.prototype.__proto__ === _this.prototype; //this instanceof _this =>true if (this instanceof _this === true) { // 此時this指向指向result的實例 這時候不需要改變this指向 this[fn] = _this this[fn](...[...args, ...innerArgs]) //這里使用es6的方法讓bind支持參數合并 delete this[fn] } else { // 如果只是作為普通函數調用 那就很簡單了 直接改變this指向為傳入的context context[fn](...[...args, ...innerArgs]); delete context[fn] } }; // 如果綁定的是構造函數 那么需要繼承構造函數原型屬性和方法 // 實現繼承的方式一: 構造一個中間函數來實現繼承 // let noFun = function () { } // noFun.prototype = this.prototype // result.prototype = new noFun() // 實現繼承的方式二: 使用Object.create result.prototype = Object.create(this.prototype) return result};//測試一下function Person(name, age) { console.log(name); //'我是參數傳進來的name' console.log(age); //'我是參數傳進來的age' console.log(this); //構造函數this指向實例對象}// 構造函數原型的方法Person.prototype.say = function() { console.log(123);}let obj = { objName: '我是obj傳進來的name', objAge: '我是obj傳進來的age'}// 普通函數function normalFun(name, age) { console.log(name); //'我是參數傳進來的name' console.log(age); //'我是參數傳進來的age' console.log(this); //普通函數this指向綁定bind的第一個參數 也就是例子中的obj console.log(this.objName); //'我是obj傳進來的name' console.log(this.objAge); //'我是obj傳進來的age'}// 先測試作為構造函數調用// let bindFun = Person.myBind(obj, '我是參數傳進來的name')// let a = new bindFun('我是參數傳進來的age')// a.say() //123// 再測試作為普通函數調用let bindFun = normalFun.myBind(obj, '我是參數傳進來的name') bindFun('我是參數傳進來的age')復制代碼

    bind 實現 運用原型鏈相關知識 如果對 js 原型鏈和繼承不是很熟悉 請點傳送門

    5 new 操作符

    先思考?

  • new 用法是什么?
  • 從構造函數創造一個實例對象 構造函數的 this 指向為創造的實例函數 并且可以使用構造函數原型屬性和方法

    function Person(name, age) { this.name = name; this.age = age;}Person.prototype.say = function() { console.log(this.age);};let p1 = new Person("lihua", 18);console.log(p1.name);p1.say();復制代碼
  • 怎么實現 this 指向改變?
  • call apply

  • 怎么實現構造函數原型屬性和方法的使用
  • 原型鏈 原型繼承


    具體實現如下

    function myNew(fn, ...args) { // 1.創造一個實例對象 let obj = {}; // 2.生成的實例對象繼承構造函數原型 // 方法一 粗暴的改變指向 完成繼承 obj.__proto__ = fn.prototype; // 方法二 利用Object.create實現 // obj=Object.create(fn.prototype) // 3.改變構造函數this指向為實例對象 let result = fn.call(obj, ...args); // 4. 如果構造函數執行的結果返回的是一個對象或者函數,那么返回這個對象或函數 if ((result && typeof result === "object") || typeof result === "function") { return result; } //不然直接返回boj return obj;}// 測試一下function Person(name, age) { this.name = name; this.age = age;}Person.prototype.say = function() { console.log(this.age);};let p1 = myNew(Person, "lihua", 18);console.log(p1.name);console.log(p1);p1.say();復制代碼

    對原型鏈深入理解學習 建議看看 傳送門

    6 instanceof

    先思考?

  • instanceof 原理?
  • 右側對象的原型對象(prototype )是否在左側對象的原型鏈上面

  • 怎么遍歷左側對象的原型鏈是關鍵點?
  • while(true) 一直遍歷 直到原型鏈的盡頭 null 都沒有相等就說明不存在 返回 false


    具體實現如下

    function myInstanceof(left, right) { let leftProp = left.__proto__; let rightProp = right.prototype; // 一直會執行循環 直到函數return while (true) { // 遍歷到了原型鏈最頂層 if (leftProp === null) { return false; } if (leftProp === rightProp) { return true; } else { // 遍歷賦值__proto__做對比 leftProp = leftProp.__proto__; } }}// 測試一下let a = [];console.log(myInstanceof(a, Array));復制代碼

    7 深拷貝

    先思考?

  • 什么是深拷貝?
  • js 對引用類型的數據進行復制的時候,深拷貝不會拷貝引用類型的引用,而是將引用類型的值全部拷貝一份,形成一個新的引用類型,這樣就不會發生引用錯亂的問題,使得我們可以多次使用同樣的數據,而不用擔心數據之間會起沖突

  • 怎么樣才能全部拷貝?
  • 遞歸遍歷 直到數據類型不是引用類型才進行賦值操作


    具體實現如下

    // 定義一個深拷貝函數 接收目標target參數function deepClone(target) { // 定義一個變量 let result; // 如果當前需要深拷貝的是一個對象的話 if (typeof target === 'object') { // 如果是一個數組的話 if (Array.isArray(target)) { result = []; // 將result賦值為一個數組,并且執行遍歷 for (let i in target) { // 遞歸克隆數組中的每一項 result.push(deepClone(target[i])) } // 判斷如果當前的值是null的話;直接賦值為null } else if(target===null) { result = null; // 判斷如果當前的值是一個RegExp對象的話,直接賦值 } else if(target.constructor===RegExp){ result = target; }else { // 否則是普通對象,直接for in循環,遞歸賦值對象的所有值 result = {}; for (let i in target) { result[i] = deepClone(target[i]); } } // 如果不是對象的話,就是基本數據類型,那么直接賦值 } else { result = target; } // 返回最終結果 return result;}復制代碼

    擴展:利用JSON的方法實現簡單的深拷貝

    let targetObj = JSON.parse(JSON.stringify(sourceObj))復制代碼

    但是它有局限性

    • 不可以拷貝 undefined , function, RegExp 等等類型的
    • 會拋棄對象的 constructor,所有的構造函數會指向 Object
    • 對象有循環引用,會報錯

    源自:https://juejin.im/post/5eb8f5cdf265da7bd44254b4

    聲明:文章著作權歸作者所有,如有侵權,請聯系小編刪除。

    總結

    以上是生活随笔為你收集整理的前端为什么有的接口明明是成功回调却执行了.catch失败回调_前端进阶高薪必看-手写源码篇(高频技术点)...的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。