自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

JavaScript中各種源碼實現(xiàn)(前端面試筆試必備)

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

 前言

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

[[315089]]

能夠手寫實現(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)可以幫助你應(yī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. <ul id="color-list"
  2.     <li>red</li> 
  3.     <li>yellow</li> 
  4.     <li>blue</li> 
  5.     <li>green</li> 
  6.     <li>black</li> 
  7.     <li>white</li> 
  8.   </ul> 
  9.   <script> 
  10.     (function () { 
  11.       var color_list = document.getElementById('color-list'); 
  12.       color_list.addEventListener('click', showColor, true); 
  13.       function showColor(e) { 
  14.         var x = e.target; 
  15.         if (x.nodeName.toLowerCase() === 'li') { 
  16.           alert(x.innerHTML); 
  17.         } 
  18.       } 
  19.     })(); 
  20.   </script> 

 

 

 

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

 

 

責任編輯:華軒 來源: segmentfault
相關(guān)推薦

2023-06-26 08:24:23

JavaScriptAJAX

2023-06-13 07:54:17

DOM 封裝作用域

2017-08-16 10:03:57

前端面試題算法

2020-09-16 14:17:42

flat方法

2022-07-27 08:27:34

Call前端

2022-01-18 08:16:52

Web 前端JavaScript

2021-02-02 06:12:39

JavaScript 前端面試題

2023-06-02 08:49:25

優(yōu)雅降級CSS3

2012-05-08 16:11:14

WEB前端開發(fā)面試

2022-07-08 08:21:26

JSbind 方法

2010-05-10 10:18:20

2009-07-14 10:05:02

HCDA認證考試筆試題

2025-03-17 08:15:27

SQLJOIN連接

2021-05-18 07:52:31

PromiseAsyncAwait

2016-02-23 11:22:20

前端面試小記

2023-06-29 07:48:35

異步加載JavaScript

2020-06-29 15:20:31

前端React Hooks面試題

2018-05-10 16:52:03

阿里巴巴前端面試題

2019-02-21 14:12:26

前端面試題Vue

2023-05-19 08:21:40

MarginCSS
點贊
收藏

51CTO技術(shù)棧公眾號