日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

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

新聞中心

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

 前言

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

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

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

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

實現(xiàn)一個new操作符

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

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

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

 
 
 
  1. function create(Con, ...args){ 
  2.   // 創(chuàng)建一個空的對象 
  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是一個對象則返回 
  9.   // new方法失效,否則返回obj 
  10.   return result instanceof Object ? result : this.obj; 

實現(xiàn)一個Array.isArray

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

實現(xiàn)一個Object.create()方法

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

實現(xiàn)一個EventEmitter

真實經(jīng)歷,最近在字節(jié)跳動的面試中就被面試官問到了,讓我手寫實現(xiàn)一個簡單的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.     // 將同一類型事件放到一個數(shù)組中 
  12.     // 這里的數(shù)組是隊列, 遵循先進先出 
  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) 

實現(xiàn)一個Array.prototype.reduce

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

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

然后就可以動手實現(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 

實現(xiàn)一個call或apply

先來看一個call實例,看看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)绾螌崿F(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默認值為window;
  2. 然后我們將函數(shù)掛載到context上面,context.fn = this;
  3. 處理參數(shù),將傳入myCall的參數(shù)截取,去除第一位,然后轉(zhuǎn)為數(shù)組;
  4. 調(diào)用context.fn,此時fn的this指向context;
  5. 刪除對象上的屬性 delete context.fn;
  6. 將結(jié)果返回。

以此類推,我們順便實現(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的第二個參數(shù)為[arg1,arg2,arg3] 
  7.   // 這里我們用擴展運算符來處理一下參數(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的模擬實現(xiàn),唯一不同的是對參數(shù)的處理方式。

實現(xiàn)一個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對象,給其添加三個屬性; 
  10. changePerson(); 
  11. //  1、輸出obj 
  12. console.log(obj); 
  13. //  用改變了this指向的構(gòu)造函數(shù),new一個實例出來 
  14. let p = new changePerson(); 
  15. // 2、輸出obj 
  16. console.log(p); 

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

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

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

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

 
 
 
  1. // bind實現(xiàn) 
  2. Function.prototype.mybind = function(){ 
  3.   // 1、保存函數(shù) 
  4.   let _this = this; 
  5.   // 2、保存目標對象 
  6.   let context = arguments[0]||window; 
  7.   // 3、保存目標對象之外的參數(shù),將其轉(zhuǎn)化為數(shù)組; 
  8.   let rest = Array.prototype.slice.call(arguments,1); 
  9.   // 4、返回一個待執(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ù),并用擴展運算符傳遞參數(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. }; 

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

Currying的概念其實并不復雜,用通俗易懂的話說:只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,讓它返回一個函數(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ù)個數(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é)束時觸發(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 時,每隔一段時間觸發(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; 

手寫一個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)可以幫助你應付一些面試官考察你的遞歸解決問題的能力。但是顯然,這個深拷貝函數(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.   } 

實現(xiàn)一個instanceOf

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

 
 
 
  1. // L 表示左表達式,R 表示右表達式 
  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.         // 這里重點:當 O 嚴格等于 L 時,返回 true 
  10.         if (O === L) { 
  11.             return true; 
  12.         } 
  13.         L = L.__proto__; 
  14.   } 

實現(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; 

實現(xiàn)一個async/await

原理

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

實現(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.       // 獲取迭代器實例 
  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.   }; 

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

最近字節(jié)跳動的前端面試中也被面試官問到,要求手寫實現(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. }; 

實現(xiàn)一個事件代理

這個問題一般還會讓你講一講事件冒泡和事件捕獲機制

 
 
 
  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.    

實現(xiàn)一個雙向綁定

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中各種源碼實現(xiàn)(前端面試筆試必備)
標題鏈接:http://www.dlmjj.cn/article/cojgdho.html