2022年我的面試萬字總結(jié)(代碼篇)
前言
又到了金九銀十季,最近我也是奔波于各種面試。我自己總結(jié)整理了很多方向的前端面試題。借著國慶這個假期,也把這些題目總結(jié)分享給大家,也祝正在面試的朋友們能夠拿到滿意的offer。
往期文章(1) 2022年我的面試萬字總結(jié)(瀏覽器網(wǎng)絡(luò)篇)[2]
(2) 2022年我的面試萬字總結(jié)(CSS篇)[3]
(3) 2022年我的面試萬字總結(jié)(HTML篇)[4]
(4) 2022年我的面試萬字總結(jié)(JS篇上)[5]
(5) 2022年我的面試萬字總結(jié)(JS篇下)[6]
(7) 2022年我的面試萬字總結(jié)(Vue上)[7]
(8) 2022年我的面試萬字總結(jié)(Vue下)[8]
一、手寫代碼題1. 手寫Object.create
思路:將傳入的對象作為原型
function?create(obj)?{
??function?F()?{}
??F.prototype?=?obj
??return?new?F()
}
復(fù)制代碼
2. 手寫instanceof
instanceof 運(yùn)算符用于判斷構(gòu)造函數(shù)的 prototype 屬性是否出現(xiàn)在對象的原型鏈中的任何位置。
實現(xiàn)步驟:
-
首先獲取類型的原型 -
然后獲得對象的原型 -
然后一直循環(huán)判斷對象的原型是否等于類型的原型,直到對象原型為 `null`,因為原型鏈最終為 `null`
function?myInstanceof(left,?right)?{
??let?proto?=?Object.getPrototypeOf(left),?//?獲取對象的原型
??????prototype?=?right.prototype;?//?獲取構(gòu)造函數(shù)的?prototype?對象
??//?判斷構(gòu)造函數(shù)的?prototype?對象是否在對象的原型鏈上
??while?(true)?{
????if?(!proto)?return?false;
????if?(proto?===?prototype)?return?true;
????proto?=?Object.getPrototypeOf(proto);
??}
}
復(fù)制代碼
3. 手寫 new
(1)首先創(chuàng)建了一個新的空對象
(2)設(shè)置原型,將對象的原型設(shè)置為函數(shù)的 prototype 對象。
(3)讓函數(shù)的 this 指向這個對象,執(zhí)行構(gòu)造函數(shù)的代碼(為這個新對象添加屬性)
(4)判斷函數(shù)的返回值類型,如果是值類型,返回創(chuàng)建的對象。如果是引用類型,就返回這個引用類型的對象。
?function?myNew(fn,?...args)?{
??????//?判斷參數(shù)是否是一個函數(shù)
??????if?(typeof?fn?!==?"function")?{
????????return?console.error("type?error");
??????}
??????//?創(chuàng)建一個對象,并將對象的原型綁定到構(gòu)造函數(shù)的原型上
??????const?obj?=?Object.create(fn.prototype);
??????const?value?=?fn.apply(obj,?args);?//?調(diào)用構(gòu)造函數(shù),并且this綁定到obj上
??????//?如果構(gòu)造函數(shù)有返回值,并且返回的是對象,就返回value?;否則返回obj
??????return?value?instanceof?Object???value?:?obj;
????}
復(fù)制代碼
4. 手寫promise(簡易版)
class?MyPromise?{
??constructor(fn){
????//?存儲?reslove?回調(diào)函數(shù)列表
????this.callbacks?=?[]
????const?resolve?=?(value)?=>?{
??????this.data?=?value?//?返回值給后面的?.then
??????while(this.callbacks.length)?{
????????let?cb?=?this.callbacks.shift()
????????cb(value)
??????}
????}
????fn(resolve)
??}
??then(onResolvedCallback)?{
????return?new?MyPromise((resolve)?=>?{
??????this.callbacks.push(()?=>?{
????????const?res?=?onResolvedCallback(this.data)
????????if?(res?instanceof?MyPromise)?{
??????????res.then(resolve)
????????}?else?{
??????????resolve(res)
????????}
??????})
????})
??}
}
//?這是測試案例
new?MyPromise((resolve)?=>?{
??setTimeout(()?=>?{
????resolve(1)
??},?1000)
}).then((res)?=>?{
????console.log(res)
????return?new?MyPromise((resolve)?=>?{
??????setTimeout(()?=>?{
????????resolve(2)
??????},?1000)
????})
}).then(res?=>{console.log(res)})
復(fù)制代碼
4.2 Promise.all
MyPromise.all?=?function?(promisesList)?{
??return?new?MyPromise((resolve,?reject)?=>?{
????if?(!Array.isArray(promiselList)?return?reject(new?Error('必須是數(shù)組'))
????if?(!promisesList.length)?return?resolve([])
????let?arr?=?[],?count?=?0
????//?直接循環(huán)同時執(zhí)行傳進(jìn)來的promise
????for?(let?i?=?0,?len?=?promisesList.length;?i?<?len;?i++)?{
??????//?因為有可能是?promise?有可能不是,所以用resolve()不管是不是都會自動轉(zhuǎn)成promise
??????Promise.resolve(promise).then(result?=>?{
??????????//?由到promise在初始化的時候就執(zhí)行了,.then只是拿結(jié)果而已,所以執(zhí)行完成的順序有可能和傳進(jìn)來的數(shù)組不一樣
??????????//?也就是說直接push到arr的話,順序有可能會出錯
??????????count++
??????????arr[i]?=?result
??????????//?不能用arr.length===len,是因為數(shù)組的特性
??????????//?arr=[];?arr[3]='xx';?console.log(arr.length)?這打印出來會是4?而不是1
??????????if(count?===?len)?resolve(arr)
??????}).catch(err?=>?reject(err))
????}
??})
}
復(fù)制代碼
4.3 Promise.race
傳參和上面的 all 一模一樣,傳入一個 Promise 實例集合的數(shù)組,然后全部同時執(zhí)行,誰先快先執(zhí)行完就返回誰,只返回一個結(jié)果
MyPromise.race?=?function(promisesList)?{
??return?new?MyPromise((resolve,?reject)?=>?{
????//?直接循環(huán)同時執(zhí)行傳進(jìn)來的promise
????for?(const?promise?of?promisesList)?{
??????//?直接返回出去了,所以只有一個,就看哪個快
??????promise.then(resolve,?reject)
????}
??})
}
復(fù)制代碼
5. 防抖和節(jié)流
函數(shù)防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā),則重新計時。這可以使用在一些點擊請求的事件上,避免因為用戶的多次點擊向后端發(fā)送多次請求。
函數(shù)節(jié)流是指規(guī)定一個單位時間,在這個單位時間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行,如果在同一個單位時間內(nèi)某事件被觸發(fā)多次,只有一次能生效。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽上,通過事件節(jié)流來降低事件調(diào)用的頻率。
//?//防抖
function?debounce(fn,?date)?{
??let?timer??//聲明接收定時器的變量
??return?function?(...arg)?{??//?獲取參數(shù)
????timer?&&?clearTimeout(timer)??//?清空定時器
????timer?=?setTimeout(()?=>?{??//??生成新的定時器
??????//因為箭頭函數(shù)里的this指向上層作用域的this,所以這里可以直接用this,不需要聲明其他的變量來接收
??????fn.apply(this,?arg)?//?fn()
????},?date)
??}
}
//--------------------------------
//?節(jié)流
function?debounce(fn,?data)?{
??let?timer?=?+new?Date()??//?聲明初始時間
??return?function?(...arg)?{?//?獲取參數(shù)
????let?newTimer?=?+new?Date()??//?獲取觸發(fā)事件的時間
????if?(newTimer?-?timer?>=?data)?{??//?時間判斷,是否滿足條件
??????fn.apply(this,?arg)??//?調(diào)用需要執(zhí)行的函數(shù),修改this值,并且傳入?yún)?shù)
??????timer?=?+new?Date()?//?重置初始時間
????}
??}
}
復(fù)制代碼
6. 手寫 call 函數(shù)
call 函數(shù)的實現(xiàn)步驟:
-
判斷調(diào)用對象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。 -
判斷傳入上下文對象是否存在,如果不存在,則設(shè)置為 window 。 -
處理傳入的參數(shù),截取第一個參數(shù)后的所有參數(shù)。 -
將函數(shù)作為上下文對象的一個屬性。 -
使用上下文對象來調(diào)用這個方法,并保存返回結(jié)果。 -
刪除剛才新增的屬性。 -
返回結(jié)果。
//?call函數(shù)實現(xiàn)
Function.prototype.myCall?=?function(context)?{
??//?判斷調(diào)用對象
??if?(typeof?this?!==?"function")?{
????console.error("type?error");
??}
??//?獲取參數(shù)
??let?args?=?[...arguments].slice(1),
??????result?=?null;
??//?判斷?context?是否傳入,如果未傳入則設(shè)置為?window
??context?=?context?||?window;
??//?將調(diào)用函數(shù)設(shè)為對象的方法
??context.fn?=?this;
??//?調(diào)用函數(shù)
??result?=?context.fn(...args);
??//?將屬性刪除
??delete?context.fn;
??return?result;
};
復(fù)制代碼
7. 手寫 apply 函數(shù)
apply 函數(shù)的實現(xiàn)步驟:
-
判斷調(diào)用對象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。 -
判斷傳入上下文對象是否存在,如果不存在,則設(shè)置為 window 。 -
將函數(shù)作為上下文對象的一個屬性。 -
判斷參數(shù)值是否傳入 -
使用上下文對象來調(diào)用這個方法,并保存返回結(jié)果。 -
刪除剛才新增的屬性 -
返回結(jié)果
//?apply?函數(shù)實現(xiàn)
Function.prototype.myApply?=?function(context)?{
??//?判斷調(diào)用對象是否為函數(shù)
??if?(typeof?this?!==?"function")?{
????throw?new?TypeError("Error");
??}
??let?result?=?null;
??//?判斷?context?是否存在,如果未傳入則為?window
??context?=?context?||?window;
??//?將函數(shù)設(shè)為對象的方法
??context.fn?=?this;
??//?調(diào)用方法
??if?(arguments[1])?{
????result?=?context.fn(...arguments[1]);
??}?else?{
????result?=?context.fn();
??}
??//?將屬性刪除
??delete?context.fn;
??return?result;
};
復(fù)制代碼
8. 手寫 bind 函數(shù)
bind 函數(shù)的實現(xiàn)步驟:
-
判斷調(diào)用對象是否為函數(shù),即使我們是定義在函數(shù)的原型上的,但是可能出現(xiàn)使用 call 等方式調(diào)用的情況。 -
保存當(dāng)前函數(shù)的引用,獲取其余傳入?yún)?shù)值。 -
創(chuàng)建一個函數(shù)返回 -
函數(shù)內(nèi)部使用 apply 來綁定函數(shù)調(diào)用,需要判斷函數(shù)作為構(gòu)造函數(shù)的情況,這個時候需要傳入當(dāng)前函數(shù)的 this 給 apply 調(diào)用,其余情況都傳入指定的上下文對象。
//?bind?函數(shù)實現(xiàn)
Function.prototype.myBind?=?function(context)?{
??//?判斷調(diào)用對象是否為函數(shù)
??if?(typeof?this?!==?"function")?{
????throw?new?TypeError("Error");
??}
??//?獲取參數(shù)
??var?args?=?[...arguments].slice(1),
??????fn?=?this;
??return?function?Fn()?{
????//?根據(jù)調(diào)用方式,傳入不同綁定值
????return?fn.apply(
??????this?instanceof?Fn???this?:?context,
??????args.concat(...arguments)
????);
??};
};
復(fù)制代碼
9. 函數(shù)柯里化的實現(xiàn)
函數(shù)柯里化指的是一種將使用多個參數(shù)的一個函數(shù)轉(zhuǎn)換成一系列使用一個參數(shù)的函數(shù)的技術(shù)。
function?curry(fn,?...args)?{
??return?fn.length?<=?args.length???fn(...args)?:?curry.bind(null,?fn,?...args);
}
復(fù)制代碼
10. 手寫AJAX請求
創(chuàng)建AJAX請求的步驟:
- 創(chuàng)建一個 XMLHttpRequest 對象。
- 在這個對象上使用 open 方法創(chuàng)建一個 HTTP 請求,open 方法所需要的參數(shù)是請求的方法、請求的地址、是否異步和用戶的認(rèn)證信息。
- 在發(fā)起請求前,可以為這個對象添加一些信息和監(jiān)聽函數(shù)。比如說可以通過 setRequestHeader 方法來為請求添加頭信息。還可以為這個對象添加一個狀態(tài)監(jiān)聽函數(shù)。一個 XMLHttpRequest 對象一共有 5 個狀態(tài),當(dāng)它的狀態(tài)變化時會觸發(fā)onreadystatechange 事件,可以通過設(shè)置監(jiān)聽函數(shù),來處理請求成功后的結(jié)果。當(dāng)對象的 readyState 變?yōu)?4 的時候,代表服務(wù)器返回的數(shù)據(jù)接收完成,這個時候可以通過判斷請求的狀態(tài),如果狀態(tài)是 2xx 或者 304 的話則代表返回正常。這個時候就可以通過 response 中的數(shù)據(jù)來對頁面進(jìn)行更新了。
- 當(dāng)對象的屬性和監(jiān)聽函數(shù)設(shè)置完成后,最后調(diào)用 sent 方法來向服務(wù)器發(fā)起請求,可以傳入?yún)?shù)作為發(fā)送的數(shù)據(jù)體。
const?SERVER_URL?=?"/server";
let?xhr?=?new?XMLHttpRequest();
//?創(chuàng)建?Http?請求
xhr.open("GET",?SERVER_URL,?true);
//?設(shè)置狀態(tài)監(jiān)聽函數(shù)
xhr.onreadystatechange?=?function()?{
??if?(this.readyState?!==?4)?return;
??//?當(dāng)請求成功時
??if?(this.status?===?200)?{
????handle(this.response);
??}?else?{
????console.error(this.statusText);
??}
};
//?設(shè)置請求失敗時的監(jiān)聽函數(shù)
xhr.onerror?=?function()?{
??console.error(this.statusText);
};
//?設(shè)置請求頭信息
xhr.responseType?=?"json";
xhr.setRequestHeader("Accept",?"application/json");
//?發(fā)送?Http?請求
xhr.send(null);
復(fù)制代碼
11. 使用Promise封裝AJAX請求
// promise 封裝實現(xiàn):
function?getJSON(url)?{
??//?創(chuàng)建一個?promise?對象
??let?promise?=?new?Promise(function(resolve,?reject)?{
????let?xhr?=?new?XMLHttpRequest();
????//?新建一個?http?請求
????xhr.open("GET",?url,?true);
????//?設(shè)置狀態(tài)的監(jiān)聽函數(shù)
????xhr.onreadystatechange?=?function()?{
??????if?(this.readyState?!==?4)?return;
??????//?當(dāng)請求成功或失敗時,改變?promise?的狀態(tài)
??????if?(this.status?===?200)?{
????????resolve(this.response);
??????}?else?{
????????reject(new?Error(this.statusText));
??????}
????};
????//?設(shè)置錯誤監(jiān)聽函數(shù)
????xhr.onerror?=?function()?{
??????reject(new?Error(this.statusText));
????};
????//?設(shè)置響應(yīng)的數(shù)據(jù)類型
????xhr.responseType?=?"json";
????//?設(shè)置請求頭信息
????xhr.setRequestHeader("Accept",?"application/json");
????//?發(fā)送?http?請求
????xhr.send(null);
??});
??return?promise;
}
復(fù)制代碼
12. 手寫深拷貝
?function?fn(obj)?{
??????//?判斷數(shù)據(jù)是否是復(fù)雜類型
??????if?(obj?instanceof?Object)?{
????????//判斷數(shù)據(jù)是否是數(shù)組
????????if?(Array.isArray(obj))?{
??????????//聲明一個空數(shù)組來接收拷貝后的數(shù)據(jù)
??????????let?result?=?[]
??????????obj.forEach(item?=>?{
????????????//?需要遞歸深層遍歷,否則復(fù)制的是地址
????????????result.push(fn(item))
??????????})
??????????//?返回輸出這個數(shù)組,數(shù)組拷貝完成
??????????return?result
????????}?else?{
??????????//如果是對象,就聲明一個空對象來接收拷貝后的數(shù)據(jù)
??????????let?result?=?{}
??????????for?(let?k?in?obj)?{
????????????//?使用遞歸深層遍歷
????????????result[k]?=?fn(obj[k])
??????????}
??????????//?返回輸出這個對象,對象拷貝完成
??????????return?result
????????}
??????}
??????//?簡單數(shù)據(jù)類型則直接返回輸出
??????return?obj
????}
復(fù)制代碼
13. 手寫打亂數(shù)組順序的方法
主要的實現(xiàn)思路就是:
- 取出數(shù)組的第一個元素,隨機(jī)產(chǎn)生一個索引值,將該第一個元素和這個索引對應(yīng)的元素進(jìn)行交換。
- 第二次取出數(shù)據(jù)數(shù)組第二個元素,隨機(jī)產(chǎn)生一個除了索引為1的之外的索引值,并將第二個元素與該索引值對應(yīng)的元素進(jìn)行交換
- 按照上面的規(guī)律執(zhí)行,直到遍歷完成
let arr = [1,2,3,4,5,6,7,8,9,10];
for (let i = 0; i < arr.length; i++) {
?const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
console.log(arr)
復(fù)制代碼
14. 實現(xiàn)數(shù)組扁平化
通過循環(huán)遞歸的方式,一項一項地去遍歷,如果每一項還是一個數(shù)組,那么就繼續(xù)往下遍歷,利用遞歸程序的方法,來實現(xiàn)數(shù)組的每一項的連接:
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
?let result = [];
?for(let i = 0; i < arr.length; i++) {
? ?if(Array.isArray(arr[i])) {
? ? ?result = result.concat(flatten(arr[i]));
? } else {
? ? ?result.push(arr[i]);
? }
}
?return result;
}
flatten(arr); ?// [1, 2, 3, 4,5]
復(fù)制代碼
15. 實現(xiàn)數(shù)組的flat方法
function?_flat(arr,?depth)?{
??if(!Array.isArray(arr)?||?depth?<=?0)?{
????return?arr;
??}
??return?arr.reduce((prev,?cur)?=>?{
????if?(Array.isArray(cur))?{
??????return?prev.concat(_flat(cur,?depth?-?1))
????}?else?{
??????return?prev.concat(cur);
????}
??},?[]);
}
復(fù)制代碼
16. 實現(xiàn)數(shù)組的push方法
let arr = [];
Array.prototype.push = function() {
for( let i = 0 ; i < arguments.length ; i++){
this[this.length] = arguments[i] ;
}
return this.length;
}
復(fù)制代碼
17. 實現(xiàn)數(shù)組的filter方法
Array.prototype._filter?=?function(fn)?{
????if?(typeof?fn?!==?"function")?{
????????throw?Error('參數(shù)必須是一個函數(shù)');
????}
????const?res?=?[];
????for?(let?i?=?0,?len?=?this.length;?i?<?len;?i++)?{
????????fn(this[i])?&&?res.push(this[i]);
????}
????return?res;
}
復(fù)制代碼
18. 實現(xiàn)數(shù)組的map方法
Array.prototype._map?=?function(fn)?{
???if?(typeof?fn?!==?"function")?{
????????throw?Error('參數(shù)必須是一個函數(shù)');
????}
????const?res?=?[];
????for?(let?i?=?0,?len?=?this.length;?i?<?len;?i++)?{
????????res.push(fn(this[i]));
????}
????return?res;
}
復(fù)制代碼
19. 實現(xiàn) add(1)(2)(3)(4)
可以實現(xiàn)任意數(shù)量數(shù)字相加,但是需要用+號隱式轉(zhuǎn)換
function fn() {
? ? ?let result = [];
? ? ?function add(...args) {
? ? ? ?// ...args剩余參數(shù),可以獲取到傳進(jìn)來的參數(shù)
? ? ? ?result = [...result, ...args]
? ? ? ?return add;
? ? };
? ? ?// 創(chuàng)建一個取代 valueOf 方法的函數(shù),覆蓋自定義對象的 valueOf 方法
? ? ?add.toString = () => result.reduce((sum, k) => sum + k, 0);
? ? ?return add;
? };
let add = fn()
? console.log(+add(1)(2)(3)(4)) // --->10
? ?// let add2 = fn();
? ?console.log(+add2(1, 2, 3)(4)) // --->10
復(fù)制代碼
參數(shù)固定的情況下,不需要用+號,可以根據(jù)參數(shù)長度來判斷返回值
? ?function currying(fn, length) {
? ? ?length = length || fn.length; // 第一次調(diào)用,給length賦值fn的長度,后面每次重復(fù)調(diào)用,length的長度都會減去參數(shù)的長度
? ? ?return function (...args) {
? ? ? ?return args.length >= length // 當(dāng)前傳遞進(jìn)來的參數(shù)的長度與length長度進(jìn)行比較
? ? ? ? ?? fn.apply(this, args) // 把最后一組實參傳給為賦值的形參,此時所有形參都已賦值,并調(diào)用fn函數(shù)
? ? ? ? : currying(fn.bind(this, ...args), length - args.length)
? ? ? ?// 每一次調(diào)用fn.bind,都會把當(dāng)前的args里的實參依次傳給fn的形參,length的長度減去參數(shù)的長度
? ? ? ?// 相當(dāng)于fn.bind(this, 1).bind(this, 2, 3),bind的連續(xù)調(diào)用,來填充fn的參數(shù)
? ? ? ?// 直到某一次調(diào)用,fn的形參即將全部都被賦值時,條件成立,會執(zhí)行fn.apply,把最后的參數(shù)傳遞過去,并且調(diào)用fn
? ? }
? }
? ?function fn(a, b, c, d) {
? ? ?return a + b + c + d
? }
? ?const add = currying(fn)
? ?add(4)(3)(1)(2) //10
? ?add(1, 3)(4)(2) //10
? ?add(1)(3, 4, 2) //10
復(fù)制代碼
20. 用Promise實現(xiàn)圖片的異步加載
let?imageAsync=(url)=>{
????????????return?new?Promise((resolve,reject)=>{
????????????????let?img?=?new?Image();
????????????????img.src?=?url;
????????????????img.οnlοad=()=>{
????????????????????console.log(`圖片請求成功,此處進(jìn)行通用操作`);
????????????????????resolve(image);
????????????????}
????????????????img.οnerrοr=(err)=>{
????????????????????console.log(`失敗,此處進(jìn)行失敗的通用操作`);
????????????????????reject(err);
????????????????}
????????????})
????????}
????????
imageAsync("url").then(()=>{
????console.log("加載成功");
}).catch((error)=>{
????console.log("加載失敗");
})
復(fù)制代碼
21. 手寫發(fā)布-訂閱模式
class?EventCenter{
??//?1.?定義事件容器,用來裝事件數(shù)組
????let?handlers?=?{}
??// 2. 添加事件方法,參數(shù):事件名?事件方法
??addEventListener(type,?handler)?{
????//?創(chuàng)建新數(shù)組容器
????if?(!this.handlers[type])?{
??????this.handlers[type]?=?[]
????}
????//?存入事件
????this.handlers[type].push(handler)
??}
??// 3. 觸發(fā)事件,參數(shù):事件名?事件參數(shù)
??dispatchEvent(type,?params)?{
????//?若沒有注冊該事件則拋出錯誤
????if?(!this.handlers[type])?{
??????return?new?Error('該事件未注冊')
????}
????//?觸發(fā)事件
????this.handlers[type].forEach(handler?=>?{
??????handler(...params)
????})
??}
??// 4. 事件移除,參數(shù):事件名?要刪除事件,若無第二個參數(shù)則刪除該事件的訂閱和發(fā)布
??removeEventListener(type,?handler)?{
????if?(!this.handlers[type])?{
??????return?new?Error('事件無效')
????}
????if?(!handler)?{
??????//?移除事件
??????delete?this.handlers[type]
????}?else?{
??????const?index?=?this.handlers[type].findIndex(el?=>?el?===?handler)
??????if?(index?===?-1)?{
????????return?new?Error('無該綁定事件')
??????}
??????//?移除事件
??????this.handlers[type].splice(index,?1)
??????if?(this.handlers[type].length?===?0)?{
????????delete?this.handlers[type]
??????}
????}
??}
}
復(fù)制代碼
22. Object.defineProperty(簡易版)
?//??Vue2的響應(yīng)式原理,結(jié)合了Object.defineProperty的數(shù)據(jù)劫持,以及發(fā)布訂閱者模式
?//??Vue2的數(shù)據(jù)劫持,就是通過遞歸遍歷data里的數(shù)據(jù),用Object.defineProperty給每一個屬性添加getter和setter,
?//??并且把data里的屬性掛載到vue實例中,修改vue實例上的屬性時,就會觸發(fā)對應(yīng)的setter函數(shù),向Dep訂閱器發(fā)布更新消息,
?//??對應(yīng)的Watcher訂閱者會收到通知,調(diào)用自身的回調(diào)函數(shù),讓編譯器去更新視圖。
????const?obj?=?{
??????name:?'劉逍',
??????age:?20
????}
????const?p?=?{}
????for?(let?key?in?obj)?{
??????Object.defineProperty(p,?key,?{
????????get()?{
??????????console.log(`有人讀取p里的${key}屬性`);
??????????return?obj[key]
????????},
????????set(val)?{
??????????console.log(`有人修改了p里的${key}屬性,值為${val},需要去更新視圖`);
??????????obj[key]?=?val
????????}
??????})
????}
復(fù)制代碼
23. Proxy數(shù)據(jù)劫持(簡易版)
?//?Vue3的數(shù)據(jù)劫持通過Proxy函數(shù)對代理對象的屬性進(jìn)行劫持,通過Reflect對象里的方法對代理對象的屬性進(jìn)行修改,
?//?Proxy代理對象不需要遍歷,配置項里的回調(diào)函數(shù)可以通過參數(shù)拿到修改屬性的鍵和值
?//?這里用到了Reflect對象里的三個方法,get,set和deleteProperty,方法需要的參數(shù)與配置項中回調(diào)函數(shù)的參數(shù)相同。
?// Reflect里的方法與Proxy里的方法是一一對應(yīng)的,只要是Proxy對象的方法,就能在Reflect對象上找到對應(yīng)的方法。
???const?obj?=?{
??????name:?'劉逍',
??????age:?20
????}
???const?p?=?new?Proxy(obj,?{
??????//?讀取屬性的時候會調(diào)用getter
??????get(target,?propName)?{??//第一個參數(shù)為代理的源對象,等同于上面的Obj參數(shù)。第二個參數(shù)為讀取的那個屬性值
????????console.log(`有人讀取p對象里的${propName}屬性`);
????????return?Reflect.get(target,?propName)
??????},
??????//?添加和修改屬性的時候會調(diào)用setter
??????set(target,?propName,?value)?{?//參數(shù)等同于get,第三個參數(shù)為修改后的屬性值
????????console.log(`有人修改了p對象里的${propName}屬性,值為${value},需要去修改視圖`);
????????Reflect.set(target,?propName,?value)
??????},
??????//?刪除屬性時,調(diào)用deleteProperty
??????deleteProperty(target,?propName)?{?//?參數(shù)等同于get
????????console.log(`有人刪除了p對象里的${propName}屬性,需要去修改視圖`);
????????return?Reflect.deleteProperty(target,?propName)
??????}
????})
復(fù)制代碼
24. 實現(xiàn)路由(簡易版)
//?hash路由
class?Route{
??constructor(){
????//?路由存儲對象
????this.routes?=?{}
????//?當(dāng)前hash
????this.currentHash?=?''
????//?綁定this,避免監(jiān)聽時this指向改變
????this.freshRoute?=?this.freshRoute.bind(this)
????//?監(jiān)聽
????window.addEventListener('load',?this.freshRoute,?false)
????window.addEventListener('hashchange',?this.freshRoute,?false)
??}
??//?存儲
??storeRoute?(path,?cb)?{
????this.routes[path]?=?cb?||?function?()?{}
??}
??//?更新
??freshRoute?()?{
????this.currentHash?=?location.hash.slice(1)?||?'/'
????this.routes[this.currentHash]()
??}
}
復(fù)制代碼
25. 使用 setTimeout 實現(xiàn) setInterval
實現(xiàn)思路是使用遞歸函數(shù),不斷地去執(zhí)行 setTimeout 從而達(dá)到 setInterval 的效果
function mySetInterval(fn, timeout) {
?// 控制器,控制定時器是否繼續(xù)執(zhí)行
?var timer = {
? ?flag: true
};
?// 設(shè)置遞歸函數(shù),模擬定時器執(zhí)行。
?function interval() {
? ?if (timer.flag) {
? ? ?fn();
? ? ?setTimeout(interval, timeout);
? }
}
?// 啟動定時器
?setTimeout(interval, timeout);
?// 返回控制器
?return timer;
}
復(fù)制代碼
26. 使用setInterval實現(xiàn)setTimeout
? ?function mySetInterval(fn, t) {
? ? ?const timer = setInterval(() => {
? ? ? ?clearInterval(timer)
? ? ? ?fn()
? ? }, t)
? }
? ?mySetInterval(() => {
? ? ?console.log('hoho');
? }, 1000)
復(fù)制代碼
27. 實現(xiàn) jsonp
// 動態(tài)的加載js文件
function addScript(src) {
?const script = document.createElement('script');
?script.src = src;
?script.type = "text/javascript";
?document.body.appendChild(script);
}
addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
// 設(shè)置一個全局的callback函數(shù)來接收回調(diào)結(jié)果
function handleRes(res) {
?console.log(res);
}
// 接口返回的數(shù)據(jù)格式
handleRes({a: 1, b: 2});
復(fù)制代碼
28. 提取出url 里的參數(shù)并轉(zhuǎn)成對象
function?getUrlParams(url){
??let?reg?=?/([^?&=]+)=([^?&=]+)/g
??let?obj?=?{?}
??url.replace(reg,?function(){
??????obj[arguments[1]]?=?arguments[2]
??})
??//?或者
??const?search?=?window.location.search
??search.replace(/([^&=?]+)=([^&]+)/g,?(m,?$1,?$2)=>{obj[$1]?=?decodeURIComponent($2)})
??
??return?obj
}
let?url?=?'https://www.junjin.cn?a=1&b=2'
console.log(getUrlParams(url))?//?{?a:?1,?b:?2?}
復(fù)制代碼
29. 請寫至少三種數(shù)組去重的方法?(原生js)
//利用filter
function?unique(arr)?{
??return?arr.filter(function(item,?index,?arr)?{
????//當(dāng)前元素,在原始數(shù)組中的第一個索引==當(dāng)前索引值,否則返回當(dāng)前元素
????return?arr.indexOf(item,?0)?===?index;
??});
}
????var?arr?=?[1,1,'true','true',true,true,15,15,false,false,?undefined,undefined,?null,null,?NaN,?NaN,'NaN',?0,?0,?'a',?'a',{},{}];
????????console.log(unique(arr))
復(fù)制代碼
//利用ES6?Set去重(ES6中最常用)
function?unique?(arr)?{
??return?Array.from(new?Set(arr))
}
var?arr?=?[1,1,'true','true',true,true,15,15,false,false,?undefined,undefined,?null,null,?NaN,?NaN,'NaN',?0,?0,?'a',?'a',{},{}];
console.log(unique(arr))
?//[1,?"true",?true,?15,?false,?undefined,?null,?NaN,?"NaN",?0,?"a",?{},?{}]
復(fù)制代碼
//利用for嵌套for,然后splice去重(ES5中最常用)
function unique (arr) {
? ? ? ?for(var i=0; i<arr.length; i++){
? ? ? ? ? ?for(var j=i+1; j<arr.length; j++){
? ? ? ? ? ? ? ?if(arr[i]==arr[j]){ ? ? ? ? //第一個等同于第二個,splice方法刪除第二個
? ? ? ? ? ? ? ? ? ?arr.splice(j,1);
? ? ? ? ? ? ? ? ? ?j--;
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? }
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
? ?console.log(unique(arr))
? ?//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] ? ?
//NaN和{}沒有去重,兩個null直接消失了
復(fù)制代碼
二、算法基礎(chǔ)
1. 時間&空間復(fù)雜度
- 復(fù)雜度是數(shù)量級(方便記憶、推廣),不是具體數(shù)字。
- 常見復(fù)雜度大小比較:O(n^2) > O(nlogn) > O(n) > O(logn) > O(1)
1.1 時間復(fù)雜度
常見時間復(fù)雜度對應(yīng)關(guān)系:
- O(n^2):2層循環(huán)(嵌套循環(huán))
- O(nlogn):快速排序(循環(huán) + 二分)
- O(n):1層循環(huán)
- O(logn):二分
1.2 空間復(fù)雜度
常見空間復(fù)雜度對應(yīng)關(guān)系:
- O(n):傳入一個數(shù)組,處理過程生成一個新的數(shù)組大小與傳入數(shù)組一致
2. 八大數(shù)據(jù)結(jié)構(gòu)
1. 棧
棧是一個后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)。JavaScript中沒有棧,但是可以用Array實現(xiàn)棧的所有功能。
// 數(shù)組實現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)
const stack = []
// 入棧
stack.push(0)
stack.push(1)
stack.push(2)
// 出棧
const popVal = stack.pop() // popVal 為 2
復(fù)制代碼
使用場景
- 場景一:十進(jìn)制轉(zhuǎn)二進(jìn)制
- 場景二:有效括號
- 場景三:函數(shù)調(diào)用堆棧
2. 隊列
隊列是一個先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)。JavaScript中沒有隊列,但是可以用Array實現(xiàn)隊列的所有功能。
// 數(shù)組實現(xiàn)隊列數(shù)據(jù)結(jié)構(gòu)
const queue = []
// 入隊
stack.push(0)
stack.push(1)
stack.push(2)
// 出隊
const shiftVal = stack.shift() // shiftVal 為 0
復(fù)制代碼
使用場景
- 場景一:日常測核酸排隊
- 場景二:JS異步中的任務(wù)隊列
- 場景三:計算最近請求次數(shù)
3. 鏈表
鏈表是多個元素組成的列表,元素存儲不連續(xù),用next指針連在一起。JavaScript中沒有鏈表,但是可以用Object模擬鏈表。
使用場景
- 場景一:JS中的原型鏈
- 場景二:使用鏈表指針獲取 JSON 的節(jié)點值
4. 集合
集合是一個無序且唯一的數(shù)據(jù)結(jié)構(gòu)。ES6中有集合:Set,集合常用操作:去重、判斷某元素是否在集合中、求交集。
//?去重
const?arr?=?[1,?1,?2,?2]
const?arr2?=?[...new?Set(arr)]
//?判斷元素是否在集合中
const?set?=?new?Set(arr)
const?has?=?set.has(3)?//?false
//?求交集
const?set2?=?new?Set([2,?3])
const?set3?=?new?Set([...set].filter(item?=>?set2.has(item)))
復(fù)制代碼
使用場景
- 場景一:求交集、差集
5. 字典(哈希)
字典也是一種存儲唯一值的數(shù)據(jù)結(jié)構(gòu),但它以鍵值對的形式存儲。ES6中的字典名為Map,
//?字典
const?map?=?new?Map()
//?增
map.set('key1',?'value1')
map.set('key2',?'value2')
map.set('key3',?'value3')
//?刪
map.delete('key3')
//?map.clear()
//?改
map.set('key2',?'value222')
//?查
map.get('key2')
復(fù)制代碼
使用場景
- 場景:leetcode刷題
6. 樹
樹是一種分層的數(shù)據(jù)模型。前端常見的樹包括:DOM、樹、級聯(lián)選擇、樹形控件……。JavaScript中沒有樹,但是可以通過Object和Array構(gòu)建樹。樹的常用操作:深度/廣度優(yōu)先遍歷、先中后序遍歷。
使用場景
- 場景一:DOM樹
- 場景二:級聯(lián)選擇器
7. 圖
圖是網(wǎng)絡(luò)結(jié)構(gòu)的抽象模型,是一組由邊連接的節(jié)點。圖可以表示任何二元關(guān)系,比如道路、航班。JS中沒有圖,但是可以用Object和Array構(gòu)建圖。圖的表示法:鄰接矩陣、鄰接表、關(guān)聯(lián)矩陣。
使用場景
- 場景一:道路
- 場景二:航班
8. 堆
堆是一種特殊的完全二叉樹。所有的節(jié)點都大于等于(最大堆)或小于等于(最小堆)它的子節(jié)點。由于堆的特殊結(jié)構(gòu),我們可以用數(shù)組表示堆。
使用場景
- 場景:leetcode刷題
3. 排序方法
3.1 冒泡排序
比較兩個記錄鍵值的大小,如果這兩個記錄鍵值的大小出現(xiàn)逆序,則交換這兩個記錄
每遍歷一個元素,都會把之前的所有相鄰的元素都兩兩比較一遍,即便是已經(jīng)排序好的元素
//[1,3,4,2]->[1,3,2,4]->[1,2,3,4]->[1,2,3,4]
let n = 0
function bubbleSort(arr){
? ?for(let i = 1;i < arr.length;i++){
? ? ? ?for(let j = i;j > 0;j--){
? ? ? ? ? ?n++ // 1+2+3+...+arr.length-1
? ? ? ? ? ?if(arr[j] < arr[j-1]){
? ? ? ? ? ? ? [arr[j],arr[j-1]] = [arr[j-1],arr[j]];
? ? ? ? ? }
? ? ? }
? }
? ?return arr;
}
復(fù)制代碼
3.2 插入排序
第i(i大于等于1)個記錄進(jìn)行插入操作時,R1、 R2,...,是排好序的有序數(shù)列,取出第i個元素,在序列中找到一個合適的位置并將她插入到該位置上即可。
相當(dāng)于把當(dāng)前遍歷的元素取出,在序列中找到一個合適的位置將它插入。它的第二層循環(huán)不必遍歷當(dāng)前元素之前的所有元素,因為當(dāng)前元素之前的序列是排序好的,碰到第一個小于當(dāng)前元素的值,就可以停止繼續(xù)向前查找了,然后把當(dāng)前元素插入當(dāng)前位置即可
function insertSort(arr){
? ?for(let i = 1;i < arr.length;i++){
? ? ? ?let j = i-1;
? ? ? ?if(arr[i]<arr[j]){
? ? ? ? ? ?let temp = arr[i];
? ? ? ? ? ?while(j >= 0 && temp < arr[j]){
? ? ? ? ? ? ? ?arr[j+1] = arr[j];
? ? ? ? ? ? ? ?j--;
? ? ? ? ? }
? ? ? ? ? ?arr[j+1] = temp;
? ? ? }
? }
? ?return arr;
}
//[1,3,4,2] ->[1,3,4,4]->[1,3,3,4]->[1,2,3,4]
//i=3 temp=2 j=2 arr[j]=4 arr[3]=4 [1,3,4,4];j=1 arr[2]=3 [1,3,3,4];j=0 [1,2,3,4]
復(fù)制代碼
3.3 希爾排序
算法先將要排序的一組數(shù)按某個增量d(n/2,n為要排序數(shù)的個數(shù))分成若干組,每組中記錄的下標(biāo)相差d.對每組中全部元素進(jìn)行直接插入排序,然后再用一個較小的增量(d/2)對它進(jìn)行分組,在每組中再進(jìn)行直接插入排序。當(dāng)增量減到1時,進(jìn)行直接插入排序后,排序完成。
function hillSort(arr){
? ?let len = arr.length;
? ?for(let gap = parseInt(len / 2);gap >= 1;gap = parseInt(gap / 2)){
? ? ? ?for(let i = gap;i < len;i++){
? ? ? ? ? ?if(arr[i] < arr[i-gap]){
? ? ? ? ? ? ? ?let temp = arr[i];
? ? ? ? ? ? ? ?let j = i - gap;
? ? ? ? ? ? ? ?while(j >= 0 && arr[j] > temp){
? ? ? ? ? ? ? ? ? ?arr[j+gap] = arr[j];
? ? ? ? ? ? ? ? ? ?j -= gap;
? ? ? ? ? ? ? }
? ? ? ? ? ? ? ?arr[j+gap] = temp;
? ? ? ? ? }
? ? ? }
? }
? ?return arr;
}
復(fù)制代碼
微信截圖_20221006102742.png3.4 選擇排序
在第i次選擇操作中,通過n-i次鍵值間比較,從n-i+1個記錄中選出鍵值最小的記錄,并和第i(1小于等于1小于等于n-1)個記錄交換
每一次遍歷,都把當(dāng)前元素與剩下元素里的最小值交換位置
//[4,1,3,2]->[1,4,3,2]->[1,2,4,3]->[1,2,3,4]
function selectSort(arr){
? ?for(let i = 0;i < arr.length;i++){
? ? ? ?let min = Math.min(...arr.slice(i));
? ? ? ?let index
? ? ? ?for (let j = i; j < arr.length; j++) {
? ? ? ? ?if (arr[j] === min) {
? ? ? ? ? ?index = j
? ? ? ? ? ?break
? ? ? ? }
? ? ? }
? ? ? [arr[i],arr[index]] = [arr[index],arr[i]];
? }
? ?return arr;
}
復(fù)制代碼
3.5 快排
在n個記錄中取某一個記錄的鍵值為標(biāo)準(zhǔn),通常取第一個記錄鍵值為基準(zhǔn),通過一趟排序?qū)⒋诺挠涗浄譃樾∮诨虻扔谶@個鍵值的兩個獨立的部分,這是一部分的記錄鍵值均比另一部分記錄的鍵值小,然后,對這兩部分記錄繼續(xù)分別進(jìn)行快速排序,以達(dá)到整個序列有序
取當(dāng)前排序數(shù)組的第一個值作為基準(zhǔn)值keys,通過一次遍歷把數(shù)組分為right大于基準(zhǔn)值和left小于等于基準(zhǔn)值的兩部分,然后對兩個部分重復(fù)以上步驟排序,最后return的時候按照[left,keys,right]的順序返回
function quickSort(arr){
? ?if(arr.length <= 1) return arr;
? ?let right = [],left = [],keys = arr.shift();
? ?for(let value of arr){
? ? ? ?if(value > keys){
? ? ? ? ? ?right.push(value)
? ? ? }else{
? ? ? ? ? ?left.push(value);
? ? ? }
? }
? ?return quickSort(left).concat(keys,quickSort(right));
}
//[4,1,3,2]-->quickSort([1,3,2]).concat(4,quickSort([]))
// ? ? ? ? -->quickSort([]).concant(1,quickSort([3,2])).concat(4,quickSort([]))
// ? ? ? ? -->quickSort([]).concant(1,quickSort([2]).concant(3)).concat(4,quickSort([]))
// ? ? ? ? -->[1,2,3,4]
//keys=4 R[] L[1,3,2] ?
-------quickSort(left)
//keys=1 R[3,2] L[]
//keys=3 R[] L[2]
//quickSort(left)=[1,2,3]
復(fù)制代碼
3.6各排序算法的穩(wěn)定性,時間復(fù)雜度,空間復(fù)雜度

每個語言的排序內(nèi)部實現(xiàn)都是不同的。
對于 JS 來說,數(shù)組長度大于 10 會采用快排,否則使用插入排序。選擇插入排序是因為雖然時間復(fù)雜度很差,但是在數(shù)據(jù) 量很小的情況下和 O(N * logN) 相差無幾,然而插入排序需要的常數(shù)時間很小,所以相對別的排序來說更快。
4. JS尾遞歸優(yōu)化斐波拉契數(shù)列
正常的斐波拉契數(shù)列js實現(xiàn)方式
const Fibonacci = (n) => {
? ?if (n <= 1) return 1;
? ?return ?Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(40) // 165580141 計算緩慢有延遲了
Fibonacci(100) // 棧溢出,無法得到結(jié)果復(fù)制代碼
復(fù)制代碼
使用尾遞歸優(yōu)化該方法
const Fibonacci = (n, sum1 = 1, sum2 = 1) => {
? ? if (n <= 1) return sum2;
? ? return Fibonacci(n - 1, sum2, sum1 + sum2)
}
Fibonacci(10) // 89
Fibonacci(100) // 573147844013817200000 速度依舊很快
Fibonacci(1000) // 7.0330367711422765e+208 還是沒有壓力復(fù)制代碼
復(fù)制代碼
尾遞歸優(yōu)化可以在數(shù)量較大的計算中,可以起到很好的作用。作者:逍丶
https://juejin.cn/post/7151221875224346637
祝 您:2022 年暴富!萬事如意!
點贊和在看就是最大的支持,
比心??
