日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
JavaScript中各種源碼實(shí)現(xiàn)(前端面試筆試必備)

 前言

最近很多人和我一樣在積極地準(zhǔn)備前端的面試筆試,所以我也就整理了一些前端面試筆試中非常容易被問到的原生函數(shù)實(shí)現(xiàn)和各種前端原理實(shí)現(xiàn)。

創(chuàng)新互聯(lián)網(wǎng)站建設(shè)由有經(jīng)驗(yàn)的網(wǎng)站設(shè)計(jì)師、開發(fā)人員和項(xiàng)目經(jīng)理組成的專業(yè)建站團(tuán)隊(duì),負(fù)責(zé)網(wǎng)站視覺設(shè)計(jì)、用戶體驗(yàn)優(yōu)化、交互設(shè)計(jì)和前端開發(fā)等方面的工作,以確保網(wǎng)站外觀精美、網(wǎng)站設(shè)計(jì)、成都網(wǎng)站制作易于使用并且具有良好的響應(yīng)性。

能夠手寫實(shí)現(xiàn)各種JavaScript原生函數(shù),可以說是擺脫API調(diào)用師帽子的第一步,我們不光要會用,更要去探究其實(shí)現(xiàn)原理!

對JavaScript源碼的學(xué)習(xí)和實(shí)現(xiàn)能幫助我們快速和扎實(shí)地提升自己的前端編程能力。

實(shí)現(xiàn)一個(gè)new操作符

我們首先知道new做了什么:

  1. 創(chuàng)建一個(gè)空的簡單JavaScript對象(即{});
  2. 鏈接該對象(即設(shè)置該對象的構(gòu)造函數(shù))到另一個(gè)對象 ;
  3. 將步驟(1)新創(chuàng)建的對象作為this的上下文 ;
  4. 如果該函數(shù)沒有返回對象,則返回this。

知道new做了什么,接下來我們就來實(shí)現(xiàn)它

 
 
 
  1. function create(Con, ...args){ 
  2.   // 創(chuàng)建一個(gè)空的對象 
  3.   this.obj = {}; 
  4.   // 將空對象指向構(gòu)造函數(shù)的原型鏈 
  5.   Object.setPrototypeOf(this.obj, Con.prototype); 
  6.   // obj綁定到構(gòu)造函數(shù)上,便可以訪問構(gòu)造函數(shù)中的屬性,即this.obj.Con(args) 
  7.   let result = Con.apply(this.obj, args); 
  8.   // 如果返回的result是一個(gè)對象則返回 
  9.   // new方法失效,否則返回obj 
  10.   return result instanceof Object ? result : this.obj; 

實(shí)現(xiàn)一個(gè)Array.isArray

 
 
 
  1. Array.myIsArray = function(o) {  
  2.   return Object.prototype.toString.call(Object(o)) === '[object Array]';  
  3. };  

實(shí)現(xiàn)一個(gè)Object.create()方法

 
 
 
  1. function create =  function (o) { 
  2.     var F = function () {}; 
  3.     F.prototype = o; 
  4.     return new F(); 
  5. }; 

實(shí)現(xiàn)一個(gè)EventEmitter

真實(shí)經(jīng)歷,最近在字節(jié)跳動的面試中就被面試官問到了,讓我手寫實(shí)現(xiàn)一個(gè)簡單的Event類。

 
 
 
  1. class Event { 
  2.   constructor () { 
  3.     // 儲存事件的數(shù)據(jù)結(jié)構(gòu) 
  4.     // 為查找迅速, 使用對象(字典) 
  5.     this._cache = {} 
  6.   } 
  7.  
  8.   // 綁定 
  9.   on(type, callback) { 
  10.     // 為了按類查找方便和節(jié)省空間 
  11.     // 將同一類型事件放到一個(gè)數(shù)組中 
  12.     // 這里的數(shù)組是隊(duì)列, 遵循先進(jìn)先出 
  13.     // 即新綁定的事件先觸發(fā) 
  14.     let fns = (this._cache[type] = this._cache[type] || []) 
  15.     if(fns.indexOf(callback) === -1) { 
  16.       fns.push(callback) 
  17.     } 
  18.     return this 
  19.     } 
  20.  
  21.   // 解綁 
  22.   off (type, callback) { 
  23.     let fns = this._cache[type] 
  24.     if(Array.isArray(fns)) { 
  25.       if(callback) { 
  26.         let index = fns.indexOf(callback) 
  27.         if(index !== -1) { 
  28.           fns.splice(index, 1) 
  29.         } 
  30.       } else { 
  31.         // 全部清空 
  32.         fns.length = 0 
  33.       } 
  34.     } 
  35.     return this 
  36.   } 
  37.   // 觸發(fā)emit 
  38.   trigger(type, data) { 
  39.     let fns = this._cache[type] 
  40.     if(Array.isArray(fns)) { 
  41.       fns.forEach((fn) => { 
  42.         fn(data) 
  43.       }) 
  44.     } 
  45.     return this 
  46.   } 
  47.  
  48.   // 一次性綁定 
  49.   once(type, callback) { 
  50.     let wrapFun = () => { 
  51.       callback.call(this); 
  52.       this.off(type, callback); 
  53.     }; 
  54.     this.on(wrapFun, callback); 
  55.     return this; 
  56.   } 
  57.  
  58. let e = new Event() 
  59.  
  60. e.on('click',function(){ 
  61.   console.log('on') 
  62. }) 
  63. e.on('click',function(){ 
  64.   console.log('onon') 
  65. }) 
  66. // e.trigger('click', '666') 
  67. console.log(e) 

實(shí)現(xiàn)一個(gè)Array.prototype.reduce

首先觀察一下Array.prototype.reduce語法

 
 
 
  1. Array.prototype.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue]) 

然后就可以動手實(shí)現(xiàn)了:

 
 
 
  1. Array.prototype.myReduce = function(callback, initialValue) { 
  2.   let accumulator = initialValue ? initialValue : this[0]; 
  3.   for (let i = initialValue ? 0 : 1; i < this.length; i++) { 
  4.     let _this = this; 
  5.     accumulator = callback(accumulator, this[i], i, _this); 
  6.   } 
  7.   return accumulator; 
  8. }; 
  9.  
  10. // 使用 
  11. let arr = [1, 2, 3, 4]; 
  12. let sum = arr.myReduce((acc, val) => { 
  13.   acc += val; 
  14.   return acc; 
  15. }, 5); 
  16.  
  17. console.log(sum); // 15 

實(shí)現(xiàn)一個(gè)call或apply

先來看一個(gè)call實(shí)例,看看call到底做了什么:

 
 
 
  1. let foo = { 
  2.   value: 1 
  3. }; 
  4. function bar() { 
  5.   console.log(this.value); 
  6. bar.call(foo); // 1 

從代碼的執(zhí)行結(jié)果,我們可以看到,call首先改變了this的指向,使函數(shù)的this指向了foo,然后使bar函數(shù)執(zhí)行了。

總結(jié)一下:

  1. call改變函數(shù)this指向
  2. 調(diào)用函數(shù)

思考一下:我們?nèi)绾螌?shí)現(xiàn)上面的效果呢?代碼改造如下:

 
 
 
  1. Function.prototype.myCall = function(context) { 
  2.   context = context || window; 
  3.   //將函數(shù)掛載到對象的fn屬性上 
  4.   context.fn = this; 
  5.   //處理傳入的參數(shù) 
  6.   const args = [...arguments].slice(1); 
  7.   //通過對象的屬性調(diào)用該方法 
  8.   const result = context.fn(...args); 
  9.   //刪除該屬性 
  10.   delete context.fn; 
  11.   return result 
  12. }; 

我們看一下上面的代碼:

  1. 首先我們對參數(shù)context做了兼容處理,不傳值,context默認(rèn)值為window;
  2. 然后我們將函數(shù)掛載到context上面,context.fn = this;
  3. 處理參數(shù),將傳入myCall的參數(shù)截取,去除第一位,然后轉(zhuǎn)為數(shù)組;
  4. 調(diào)用context.fn,此時(shí)fn的this指向context;
  5. 刪除對象上的屬性 delete context.fn;
  6. 將結(jié)果返回。

以此類推,我們順便實(shí)現(xiàn)一下apply,唯一不同的是參數(shù)的處理,代碼如下:

 
 
 
  1. Function.prototype.myApply = function(context) { 
  2.   context = context || window 
  3.   context.fn = this 
  4.   let result 
  5.   // myApply的參數(shù)形式為(obj,[arg1,arg2,arg3]); 
  6.   // 所以myApply的第二個(gè)參數(shù)為[arg1,arg2,arg3] 
  7.   // 這里我們用擴(kuò)展運(yùn)算符來處理一下參數(shù)的傳入方式 
  8.   if (arguments[1]) { 
  9.     result = context.fn(…arguments[1]) 
  10.   } else { 
  11.     result = context.fn() 
  12.   } 
  13.   delete context.fn; 
  14.   return result 
  15. }; 

以上便是call和apply的模擬實(shí)現(xiàn),唯一不同的是對參數(shù)的處理方式。

實(shí)現(xiàn)一個(gè)Function.prototype.bind

 
 
 
  1. function Person(){ 
  2.   this.name="zs"; 
  3.   this.age=18; 
  4.   this.gender="男" 
  5. let obj={ 
  6.   hobby:"看書" 
  7. //  將構(gòu)造函數(shù)的this綁定為obj 
  8. let changePerson = Person.bind(obj); 
  9. //  直接調(diào)用構(gòu)造函數(shù),函數(shù)會操作obj對象,給其添加三個(gè)屬性; 
  10. changePerson(); 
  11. //  1、輸出obj 
  12. console.log(obj); 
  13. //  用改變了this指向的構(gòu)造函數(shù),new一個(gè)實(shí)例出來 
  14. let p = new changePerson(); 
  15. // 2、輸出obj 
  16. console.log(p); 

仔細(xì)觀察上面的代碼,再看輸出結(jié)果。

我們對Person類使用了bind將其this指向obj,得到了changeperson函數(shù),此處如果我們直接調(diào)用changeperson會改變obj,若用new調(diào)用changeperson會得到實(shí)例 p,并且其__proto__指向Person,我們發(fā)現(xiàn)bind失效了。

我們得到結(jié)論:用bind改變了this指向的函數(shù),如果用new操作符來調(diào)用,bind將會失效。

這個(gè)對象就是這個(gè)構(gòu)造函數(shù)的實(shí)例,那么只要在函數(shù)內(nèi)部執(zhí)行 this instanceof 構(gòu)造函數(shù) 來判斷其結(jié)果是否為true,就能判斷函數(shù)是否是通過new操作符來調(diào)用了,若結(jié)果為true則是用new操作符調(diào)用的,代碼修正如下:

 
 
 
  1. // bind實(shí)現(xiàn) 
  2. Function.prototype.mybind = function(){ 
  3.   // 1、保存函數(shù) 
  4.   let _this = this; 
  5.   // 2、保存目標(biāo)對象 
  6.   let context = arguments[0]||window; 
  7.   // 3、保存目標(biāo)對象之外的參數(shù),將其轉(zhuǎn)化為數(shù)組; 
  8.   let rest = Array.prototype.slice.call(arguments,1); 
  9.   // 4、返回一個(gè)待執(zhí)行的函數(shù) 
  10.   return function F(){ 
  11.     // 5、將二次傳遞的參數(shù)轉(zhuǎn)化為數(shù)組; 
  12.     let rest2 = Array.prototype.slice.call(arguments) 
  13.     if(this instanceof F){ 
  14.       // 6、若是用new操作符調(diào)用,則直接用new 調(diào)用原函數(shù),并用擴(kuò)展運(yùn)算符傳遞參數(shù) 
  15.       return new _this(...rest2) 
  16.     }else{ 
  17.       //7、用apply調(diào)用第一步保存的函數(shù),并綁定this,傳遞合并的參數(shù)數(shù)組,即context._this(rest.concat(rest2)) 
  18.       _this.apply(context,rest.concat(rest2)); 
  19.     } 
  20.   } 
  21. }; 

實(shí)現(xiàn)一個(gè)JS函數(shù)柯里化

Currying的概念其實(shí)并不復(fù)雜,用通俗易懂的話說:只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個(gè)函數(shù)去處理剩下的參數(shù)。

 
 
 
  1. function progressCurrying(fn, args) { 
  2.  
  3.     let _this = this 
  4.     let len = fn.length; 
  5.     let args = args || []; 
  6.  
  7.     return function() { 
  8.         let _args = Array.prototype.slice.call(arguments); 
  9.         Array.prototype.push.apply(args, _args); 
  10.  
  11.         // 如果參數(shù)個(gè)數(shù)小于最初的fn.length,則遞歸調(diào)用,繼續(xù)收集參數(shù) 
  12.         if (_args.length < len) { 
  13.             return progressCurrying.call(_this, fn, _args); 
  14.         } 
  15.  
  16.         // 參數(shù)收集完畢,則執(zhí)行fn 
  17.         return fn.apply(this, _args); 
  18.     } 

手寫防抖(Debouncing)和節(jié)流(Throttling)

節(jié)流

防抖函數(shù) onscroll 結(jié)束時(shí)觸發(fā)一次,延遲執(zhí)行

 
 
 
  1. function debounce(func, wait) { 
  2.   let timeout; 
  3.   return function() { 
  4.     let context = this; // 指向全局 
  5.     let args = arguments; 
  6.     if (timeout) { 
  7.       clearTimeout(timeout); 
  8.     } 
  9.     timeout = setTimeout(() => { 
  10.       func.apply(context, args); // context.func(args) 
  11.     }, wait); 
  12.   }; 
  13. // 使用 
  14. window.onscroll = debounce(function() { 
  15.   console.log('debounce'); 
  16. }, 1000); 

節(jié)流

節(jié)流函數(shù) onscroll 時(shí),每隔一段時(shí)間觸發(fā)一次,像水滴一樣

 
 
 
  1. function throttle(fn, delay) { 
  2.   let prevTime = Date.now(); 
  3.   return function() { 
  4.     let curTime = Date.now(); 
  5.     if (curTime - prevTime > delay) { 
  6.       fn.apply(this, arguments); 
  7.       prevTime = curTime; 
  8.     } 
  9.   }; 
  10. // 使用 
  11. var throtteScroll = throttle(function() { 
  12.   console.log('throtte'); 
  13. }, 1000); 
  14. window.onscroll = throtteScroll; 

手寫一個(gè)JS深拷貝

乞丐版

 
 
 
  1. JSON.parse(JSON.stringfy)); 

非常簡單,但缺陷也很明顯,比如拷貝其他引用類型、拷貝函數(shù)、循環(huán)引用等情況。

基礎(chǔ)版

 
 
 
  1. function clone(target){ 
  2.   if(typeof target === 'object'){ 
  3.     let cloneTarget = {}; 
  4.     for(const key in target){ 
  5.       cloneTarget[key] = clone(target[key]) 
  6.     } 
  7.     return cloneTarget; 
  8.   } else { 
  9.     return target 
  10.   } 

寫到這里已經(jīng)可以幫助你應(yīng)付一些面試官考察你的遞歸解決問題的能力。但是顯然,這個(gè)深拷貝函數(shù)還是有一些問題。

一個(gè)比較完整的深拷貝函數(shù),需要同時(shí)考慮對象和數(shù)組,考慮循環(huán)引用:

 
 
 
  1. function clone(target, map = new WeakMap()) { 
  2.   if(typeof target === 'object'){ 
  3.     let cloneTarget = Array.isArray(target) ? [] : {}; 
  4.     if(map.get(target)) { 
  5.       return target; 
  6.     } 
  7.     map.set(target, cloneTarget); 
  8.     for(const key in target) { 
  9.       cloneTarget[key] = clone(target[key], map) 
  10.     } 
  11.     return cloneTarget; 
  12.   } else { 
  13.     return target; 
  14.   } 

實(shí)現(xiàn)一個(gè)instanceOf

原理: L 的 proto 是不是等于 R.prototype,不等于再找 L.__proto__.__proto__ 直到 proto 為 null

 
 
 
  1. // L 表示左表達(dá)式,R 表示右表達(dá)式 
  2. function instance_of(L, R) { 
  3.     var O = R.prototype; 
  4.   L = L.__proto__; 
  5.   while (true) { 
  6.         if (L === null){ 
  7.             return false; 
  8.         } 
  9.         // 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時(shí),返回 true 
  10.         if (O === L) { 
  11.             return true; 
  12.         } 
  13.         L = L.__proto__; 
  14.   } 

實(shí)現(xiàn)原型鏈繼承

 
 
 
  1. function myExtend(C, P) { 
  2.     var F = function(){}; 
  3.     F.prototype = P.prototype; 
  4.     C.prototype = new F(); 
  5.     C.prototype.constructor = C; 
  6.     C.super = P.prototype; 

實(shí)現(xiàn)一個(gè)async/await

原理

就是利用 generator(生成器)分割代碼片段。然后我們使用一個(gè)函數(shù)讓其自迭代,每一個(gè)yield 用 promise 包裹起來。執(zhí)行下一步的時(shí)機(jī)由 promise 來控制

實(shí)現(xiàn)

 
 
 
  1. function _asyncToGenerator(fn) { 
  2.   return function() { 
  3.     var self = this, 
  4.       args = arguments; 
  5.     // 將返回值promise化 
  6.     return new Promise(function(resolve, reject) { 
  7.       // 獲取迭代器實(shí)例 
  8.       var gen = fn.apply(self, args); 
  9.       // 執(zhí)行下一步 
  10.       function _next(value) { 
  11.         asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value); 
  12.       } 
  13.       // 拋出異常 
  14.       function _throw(err) { 
  15.         asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err); 
  16.       } 
  17.       // 第一次觸發(fā) 
  18.       _next(undefined); 
  19.     }); 
  20.   }; 

實(shí)現(xiàn)一個(gè)Array.prototype.flat()函數(shù)

最近字節(jié)跳動的前端面試中也被面試官問到,要求手寫實(shí)現(xiàn)。

 
 
 
  1. Array.prototype.myFlat = function(num = 1) { 
  2.   if (Array.isArray(this)) { 
  3.     let arr = []; 
  4.     if (!Number(num) || Number(num) < 0) { 
  5.       return this; 
  6.     } 
  7.     this.forEach(item => { 
  8.       if(Array.isArray(item)){ 
  9.         let count = num 
  10.         arr = arr.concat(item.myFlat(--count)) 
  11.       } else { 
  12.         arr.push(item) 
  13.       }   
  14.     }); 
  15.     return arr; 
  16.   } else { 
  17.     throw tihs + ".flat is not a function"; 
  18.   } 
  19. }; 

實(shí)現(xiàn)一個(gè)事件代理

這個(gè)問題一般還會讓你講一講事件冒泡和事件捕獲機(jī)制

 
 
 
  1.  
  2.     
  3. red
  4.  
  5.     
  6. yellow
  7.  
  8.     
  9. blue
  10.  
  11.     
  12. green
  13.  
  14.     
  15. black
  16.  
  17.     
  18. white
  19.  
  20.    
  21.    

實(shí)現(xiàn)一個(gè)雙向綁定

Vue 2.x的Object.defineProperty版本

 
 
 
  1. // 數(shù)據(jù) 
  2. const data = { 
  3.   text: 'default' 
  4. }; 
  5. const input = document.getElementById('input'); 
  6. const span = document.getElementById('span'); 
  7. // 數(shù)據(jù)劫持 
  8. Object.defineProperty(data, 'text', { 
  9.   // 數(shù)據(jù)變化 —> 修改視圖 
  10.   set(newVal) { 
  11.     input.value = newVal; 
  12.     span.innerHTML = newVal; 
  13.   } 
  14. }); 
  15. // 視圖更改 --> 數(shù)據(jù)變化 
  16. input.addEventListener('keyup', function(e) { 
  17.   data.text = e.target.value; 
  18. }); 

Vue 3.x的proxy 版本

 
 
 
  1. // 數(shù)據(jù) 
  2. const data = { 
  3.   text: 'default' 
  4. }; 
  5. const input = document.getElementById('input'); 
  6. const span = document.getElementById('span'); 
  7. // 數(shù)據(jù)劫持 
  8. const handler = { 
  9.   set(target, key, value) { 
  10.     target[key] = value; 
  11.     // 數(shù)據(jù)變化 —> 修改視圖 
  12.     input.value = value; 
  13.     span.innerHTML = value; 
  14.     return value; 
  15.   } 
  16. }; 
  17. const proxy = new Proxy(data, handler); 
  18.  
  19. // 視圖更改 --> 數(shù)據(jù)變化 
  20. input.addEventListener('keyup', function(e) { 
  21.   proxy.text = e.target.value; 
  22. }); 

網(wǎng)頁名稱:JavaScript中各種源碼實(shí)現(xiàn)(前端面試筆試必備)
文章起源:http://www.5511xx.com/article/cojgdho.html