【前端】一網(wǎng)打盡──前端進(jìn)階和面試必會(huì)的8個(gè)手寫(xiě)代碼
寫(xiě)在前面
我們知道在前端進(jìn)階和面試的時(shí)候,會(huì)考察到很多手寫(xiě)源碼的問(wèn)題,這些是通過(guò)學(xué)習(xí)和練習(xí)是可以掌握的,下面列舉了八個(gè)手寫(xiě)的代碼系列,希望能夠?qū)δ阌兴鶐椭?/p>
1 手寫(xiě)Promise系列
在Promise的學(xué)習(xí)中,之前也寫(xiě)過(guò)相關(guān)的分享文章,敬請(qǐng)參見(jiàn)《從小白視角上手Promise、Async/Await和手撕代碼》。
1.1 Promise.all
- //手寫(xiě)promise.all
- Promise.prototype._all = promiseList => {
- // 當(dāng)輸入的是一個(gè)promise列表
- const len = promiseList.length;
- const result = [];
- let count = 0;
- //
- return new Promise((resolve,reject)=>{
- // 循環(huán)遍歷promise列表中的promise事件
- for(let i = 0; i < len; i++){
- // 遍歷到第i個(gè)promise事件,判斷其事件是成功還是失敗
- promiseList[i].then(data=>{
- result[i] = data;
- count++;
- // 當(dāng)遍歷到最后一個(gè)promise時(shí),結(jié)果的數(shù)組長(zhǎng)度和promise列表長(zhǎng)度一致,說(shuō)明成功
- count === len && resolve(result);
- },error=>{
- return reject(error);
- })
- }
- })
- }
1.2 Promise.race
- // 手寫(xiě)promise.race
- Promise.prototype._race = promiseList => {
- const len = promiseList.length;
- return new Promise((resolve,reject)=>{
- // 循環(huán)遍歷promise列表中的promise事件
- for(let i = 0; i < len; i++){
- promiseList[i]().then(data=>{
- return resolve(data);
- },error=>{
- return reject(error);
- })
- }
- })
- }
1.3 Promise.finally
- Promise.prototype._finally = function(promiseFunc){
- return this.then(data=>Promise.resolve(promiseFunc()).then(data=>data)
- ,error=>Promise.reject(promiseFunc()).then(error=>{throw error}))
- }
2 手寫(xiě)Aysnc/Await
- function asyncGenertor(genFunc){
- return new Promise((resolve,reject)=>{
- // 生成一個(gè)迭代器
- const gen = genFunc();
- const step = (type,args)=>{
- let next;
- try{
- next = gen[type](args);
- }catch(e){
- return reject(e);
- }
- // 從next中獲取done和value的值
- const {done,value} = next;
- // 如果迭代器的狀態(tài)是true
- if(done) return resolve(value);
- Promise.resolve(value).then(
- val=>step("next",val),
- err=>step("throw",err)
- )
- }
- step("next");
- })
- }
3 深拷貝
深拷貝:拷貝所有的屬性值,以及屬性地址指向的值的內(nèi)存空間。
3.1 丟失引用的深拷貝
當(dāng)遇到對(duì)象時(shí),就再新開(kāi)一個(gè)對(duì)象,然后將第二層源對(duì)象的屬性值,完整地拷貝到這個(gè)新開(kāi)的對(duì)象中。
- // 丟失引用的深拷貝
- function deepClone(obj){
- // 判斷obj的類型是否為object類型
- if(!obj && typeof obj !== "object") return;
- // 判斷對(duì)象是數(shù)組類型還是對(duì)象類型
- let newObj = Array.isArray(obj) ? [] : {};
- // 遍歷obj的鍵值對(duì)
- for(const [key,value] of Object.entries(obj)){
- newObj[key] = typeof value === "string" ? deepClone(value) : value;
- };
- return newObj;
- }
3.2 終極方案的深拷貝(棧和深度優(yōu)先的思想)
其思路是:引入一個(gè)數(shù)組 uniqueList 用來(lái)存儲(chǔ)已經(jīng)拷貝的數(shù)組,每次循環(huán)遍歷時(shí),先判斷對(duì)象是否在 uniqueList 中了,如果在的話就不執(zhí)行拷貝邏輯了。
- function deepCopy(obj){
- // 用于去重
- const uniqueList = [];
- // 設(shè)置根節(jié)點(diǎn)
- let root = {};
- // 遍歷數(shù)組
- const loopList = [{
- parent: root,
- key: undefined,
- data: obj
- }];
- // 遍歷循環(huán)
- while(loopList.length){
- // 深度優(yōu)先-將數(shù)組最后的元素取出
- const {parent,key,data} = loopList.pop();
- // 初始化賦值目標(biāo),key--undefined時(shí)拷貝到父元素,否則拷貝到子元素
- let result = parent;
- if(typeof key !== "undefined") result = parent[key] = {};
- // 數(shù)據(jù)已存在時(shí)
- let uniqueData = uniqueList.find(item=>item.source === data);
- if(uniqueData){
- parent[key] = uniqueData.target;
- // 中斷本次循環(huán)
- continue;
- }
- // 數(shù)據(jù)不存在時(shí)
- // 保存源數(shù)據(jù),在拷貝數(shù)據(jù)中對(duì)應(yīng)的引用
- uniqueList.push({
- source:data,
- target:result
- });
- // 遍歷數(shù)據(jù)
- for(let k in data){
- if(data.hasOwnProperty(k)){
- typeof data[k] === "object"
- ?
- // 下一次循環(huán)
- loopList.push({
- parent:result,
- key:k,
- data:data[k]
- })
- :
- result[k] = data[k];
- }
- }
- }
- return root;
- }
4 手寫(xiě)一個(gè)單例模式
單例模式:保證一個(gè)類僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。實(shí)現(xiàn)方法一般是先判斷實(shí)例是否存在,如果存在直接返回,如果不存在就先創(chuàng)建再返回。
- // 創(chuàng)建單例對(duì)象,使用閉包
- const getSingle = function(func){
- let result;
- return function(){
- return result || (result = func.apply(this,arguments));
- }
- }
- // 使用Proxy攔截
- const proxy = function(func){
- let reuslt;
- const handler = {
- construct:function(){
- if(!result) result = Reflect.construct(func,arguments);
- return result;
- }
- }
- return new Proxy(func,hendler);
- }
5 手寫(xiě)封裝一個(gè)ajax函數(shù)
- /*
- 封裝自己的ajax函數(shù)
- 參數(shù)1:{string} method 請(qǐng)求方法
- 參數(shù)2:{string} url 請(qǐng)求地址
- 參數(shù)2:{Object} params 請(qǐng)求參數(shù)
- 參數(shù)3:{function} done 請(qǐng)求完成后執(zhí)行的回調(diào)函數(shù)
- */
- function ajax(method,url,params,done){
- // 1.創(chuàng)建xhr對(duì)象,兼容寫(xiě)法
- let xhr = window.XMLHttpRequest
- ? new XMLHttpRequest()
- : new ActiveXObject("Microsoft.XMLHTTP");
- // 將method轉(zhuǎn)換成大寫(xiě)
- method = method.toUpperCase();
- // 參數(shù)拼接
- let newParams = [];
- for(let key in params){
- newParams.push(`${key}=${params[k]}`);
- }
- let str = newParams.join("&");
- // 判斷請(qǐng)求方法
- if(method === "GET") url += `?${str}`;
- // 打開(kāi)請(qǐng)求方式
- xhr.open(method,url);
- let data = null;
- if(method === "POST"){
- // 設(shè)置請(qǐng)求頭
- xhr.setRequestHeader(("Content-Type","application/x-www-form-urlencoded"));
- data = str;
- }
- xhr.send(data);
- // 指定xhr狀態(tài)變化事件處理函數(shù)
- // 執(zhí)行回調(diào)函數(shù)
- xhr.onreadystatechange = function(){
- if(this.readyState === 4) done(JSON.parse(xhr.responseText));
- }
- }
6 手寫(xiě)“防抖”和“節(jié)流”
在Promise的學(xué)習(xí)中,之前也寫(xiě)過(guò)相關(guān)的分享文章,敬請(qǐng)參見(jiàn)《一網(wǎng)打盡──他們都在用這些”防抖“和”節(jié)流“方法》。
6.1 防抖
- /*
- func:要進(jìn)行防抖處理的函數(shù)
- delay:要進(jìn)行延時(shí)的時(shí)間
- immediate:是否使用立即執(zhí)行 true立即執(zhí)行 false非立即執(zhí)行
- */
- function debounce(func,delay,immediate){
- let timeout; //定時(shí)器
- return function(arguments){
- // 判斷定時(shí)器是否存在,存在的話進(jìn)行清除,重新進(jìn)行定時(shí)器計(jì)數(shù)
- if(timeout) clearTimeout(timeout);
- // 判斷是立即執(zhí)行的防抖還是非立即執(zhí)行的防抖
- if(immediate){//立即執(zhí)行
- const flag = !timeout;//此處是取反操作
- timeout = setTimeout(()=>{
- timeout = null;
- },delay);
- // 觸發(fā)事件后函數(shù)會(huì)立即執(zhí)行,然后 n 秒內(nèi)不觸發(fā)事件才能繼續(xù)執(zhí)行函數(shù)的效果。
- if(flag) func.call(this,arguments);
- }else{//非立即執(zhí)行
- timeout = setTimeout(()=>{
- func.call(this,arguments);
- },delay)
- }
- }
- }
6.2 節(jié)流
- // 節(jié)流--定時(shí)器版
- function throttle(func,delay){
- let timeout;//定義一個(gè)定時(shí)器標(biāo)記
- return function(arguments){
- // 判斷是否存在定時(shí)器
- if(!timeout){
- // 創(chuàng)建一個(gè)定時(shí)器
- timeout = setTimeout(()=>{
- // delay時(shí)間間隔清空定時(shí)器
- clearTimeout(timeout);
- func.call(this,arguments);
- },delay)
- }
- }
- }
7 手寫(xiě)apply、bind、call
7.1 apply
傳遞給函數(shù)的參數(shù)處理,不太一樣,其他部分跟call一樣。
apply接受第二個(gè)參數(shù)為類數(shù)組對(duì)象, 這里用了《JavaScript權(quán)威指南》中判斷是否為類數(shù)組對(duì)象的方法。
- Function.prototype._apply = function (context) {
- if (context === null || context === undefined) {
- context = window // 指定為 null 和 undefined 的 this 值會(huì)自動(dòng)指向全局對(duì)象(瀏覽器中為window)
- } else {
- context = Object(context) // 值為原始值(數(shù)字,字符串,布爾值)的 this 會(huì)指向該原始值的實(shí)例對(duì)象
- }
- // JavaScript權(quán)威指南判斷是否為類數(shù)組對(duì)象
- function isArrayLike(o) {
- if (o && // o不是null、undefined等
- typeof o === 'object' && // o是對(duì)象
- isFinite(o.length) && // o.length是有限數(shù)值
- o.length >= 0 && // o.length為非負(fù)值
- o.length === Math.floor(o.length) && // o.length是整數(shù)
- o.length < 4294967296) // o.length < 2^32
- return true
- else
- return false
- }
- const specialPrototype = Symbol('特殊屬性Symbol') // 用于臨時(shí)儲(chǔ)存函數(shù)
- context[specialPrototype] = this; // 隱式綁定this指向到context上
- let args = arguments[1]; // 獲取參數(shù)數(shù)組
- let result
- // 處理傳進(jìn)來(lái)的第二個(gè)參數(shù)
- if (args) {
- // 是否傳遞第二個(gè)參數(shù)
- if (!Array.isArray(args) && !isArrayLike(args)) {
- throw new TypeError('myApply 第二個(gè)參數(shù)不為數(shù)組并且不為類數(shù)組對(duì)象拋出錯(cuò)誤');
- } else {
- args = Array.from(args) // 轉(zhuǎn)為數(shù)組
- result = context[specialPrototype](...args); // 執(zhí)行函數(shù)并展開(kāi)數(shù)組,傳遞函數(shù)參數(shù)
- }
- } else {
- result = context[specialPrototype](); // 執(zhí)行函數(shù)
- }
- delete context[specialPrototype]; // 刪除上下文對(duì)象的屬性
- return result; // 返回函數(shù)執(zhí)行結(jié)果
- };
7.2 bind
拷貝源函數(shù):
- 通過(guò)變量?jī)?chǔ)存源函數(shù)
- 使用Object.create復(fù)制源函數(shù)的prototype給fToBind
返回拷貝的函數(shù)
調(diào)用拷貝的函數(shù):
- new調(diào)用判斷:通過(guò)instanceof判斷函數(shù)是否通過(guò)new調(diào)用,來(lái)決定綁定的context
- 綁定this+傳遞參數(shù)
- 返回源函數(shù)的執(zhí)行結(jié)果
- Function.prototype._bind = function (objThis, ...params) {
- const thisFn = this; // 存儲(chǔ)源函數(shù)以及上方的params(函數(shù)參數(shù))
- // 對(duì)返回的函數(shù) secondParams 二次傳參
- let fToBind = function (...secondParams) {
- const isNew = this instanceof fToBind // this是否是fToBind的實(shí)例 也就是返回的fToBind是否通過(guò)new調(diào)用
- const context = isNew ? this : Object(objThis) // new調(diào)用就綁定到this上,否則就綁定到傳入的objThis上
- return thisFn.call(context, ...params, ...secondParams); // 用call調(diào)用源函數(shù)綁定this的指向并傳遞參數(shù),返回執(zhí)行結(jié)果
- };
- if (thisFn.prototype) {
- // 復(fù)制源函數(shù)的prototype給fToBind 一些情況下函數(shù)沒(méi)有prototype,比如箭頭函數(shù)
- fToBind.prototype = Object.create(thisFn.prototype);
- }
- return fToBind; // 返回拷貝的函數(shù)
- };
7.3 call
根據(jù)call的規(guī)則設(shè)置上下文對(duì)象,也就是this的指向。
通過(guò)設(shè)置context的屬性,將函數(shù)的this指向隱式綁定到context上
通過(guò)隱式綁定執(zhí)行函數(shù)并傳遞參數(shù)。
刪除臨時(shí)屬性,返回函數(shù)執(zhí)行結(jié)果
- Function.prototype._call = function (context, ...arr) {
- if (context === null || context === undefined) {
- // 指定為 null 和 undefined 的 this 值會(huì)自動(dòng)指向全局對(duì)象(瀏覽器中為window)
- context = window
- } else {
- context = Object(context) // 值為原始值(數(shù)字,字符串,布爾值)的 this 會(huì)指向該原始值的實(shí)例對(duì)象
- }
- const specialPrototype = Symbol('特殊屬性Symbol') // 用于臨時(shí)儲(chǔ)存函數(shù)
- context[specialPrototype] = this; // 函數(shù)的this指向隱式綁定到context上
- let result = context[specialPrototype](...arr); // 通過(guò)隱式綁定執(zhí)行函數(shù)并傳遞參數(shù)
- delete context[specialPrototype]; // 刪除上下文對(duì)象的屬性
- return result; // 返回函數(shù)執(zhí)行結(jié)果
- };
8 手寫(xiě)繼承
8.1 構(gòu)造函數(shù)式繼承
構(gòu)造函數(shù)式繼承并沒(méi)有繼承父類原型上的方法。
- function fatherUser(username, password) {
- let _password = password
- this.username = username
- fatherUser.prototype.login = function () {
- console.log(this.username + '要登錄父親賬號(hào),密碼是' + _password)
- }
- }
- function sonUser(username, password) {
- fatherUser.call(this, username, password)
- this.articles = 3 // 文章數(shù)量
- }
- const yichuanUser = new sonUser('yichuan', 'xxx')
- console.log(yichuanUser.username) // yichuan
- console.log(yichuanUser.username) // xxx
- console.log(yichuanUser.login()) // TypeError: yichuanUser.login is not a function
8.2 組合式繼承
- function fatherUser(username, password) {
- let _password = password
- this.username = username
- fatherUser.prototype.login = function () {
- console.log(this.username + '要登錄fatherUser,密碼是' + _password)
- }
- }
- function sonUser(username, password) {
- fatherUser.call(this, username, password) // 第二次執(zhí)行 fatherUser 的構(gòu)造函數(shù)
- this.articles = 3 // 文章數(shù)量
- }
- sonUser.prototype = new fatherUser(); // 第二次執(zhí)行 fatherUser 的構(gòu)造函數(shù)
- const yichuanUser = new sonUser('yichuan', 'xxx')
8.3 寄生組合繼承
上面的繼承方式有所缺陷,所以寫(xiě)這種方式即可。
- function Parent() {
- this.name = 'parent';
- }
- function Child() {
- Parent.call(this);
- this.type = 'children';
- }
- Child.prototype = Object.create(Parent.prototype);
- Child.prototype.constructor = Child;
參考文章
- 《前端進(jìn)階之必會(huì)的JavaScript技巧總結(jié)》
- 《js基礎(chǔ)-面試官想知道你有多理解call,apply,bind?[不看后悔系列]》






