篇一 🎄 前言
本文主要总结了 2021 年前端提前批和秋招所考察的手写题,题目来源于牛客网前端面经区,统计时间自 3 月初至 10 月底,面经来源于阿里、腾讯、百度、字节、美团、京东、快手、拼多多等 15 家公司,并做了简单的频次划分。
⭐⭐⭐⭐⭐: 在 15 家公司面试中出现 10+
⭐⭐⭐⭐:在 15 家公式面试中出现 5-10
⭐⭐⭐:在 15 家公司面试中出现 3-5
无星:出现 1-2
题目解析一部分来源于小包的编写,另一部分如果我感觉题目扩展开来更好的话,我就选取部分大佬的博客链接。
🌟 promise 实现 promise 考察频率: (⭐⭐⭐⭐⭐)
参考代码[1]
实现 promise.all 考察频率: (⭐⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function PromiseAll (promises ) { return new Promise ((resolve, reject ) => { if (!Array .isArray (promises)) { throw new TypeError ("promises must be an array" ); } let result = []; let count = 0 ; promises.forEach ((promise, index ) => { promise.then ( (res ) => { result[index] = res; count++; count === promises.length && resolve (result); }, (err ) => { reject (err); } ); }); }); } 复制代码;
实现 promise.finally 考察频率: (⭐⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Promise .prototype .finally = function (cb ) { return this .then ( function (value ) { return Promise .resolve (cb ()).then (function ( ) { return value; }); }, function (err ) { return Promise .resolve (cb ()).then (function ( ) { throw err; }); } ); }; 复制代码;
实现 promise.allSettled 考察频率: (⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 function allSettled (promises ) { if (promises.length === 0 ) return Promise .resolve ([]); const _promises = promises.map ((item ) => item instanceof Promise ? item : Promise .resolve (item) ); return new Promise ((resolve, reject ) => { const result = []; let unSettledPromiseCount = _promises.length ; _promises.forEach ((promise, index ) => { promise.then ( (value ) => { result[index] = { status : "fulfilled" , value, }; unSettledPromiseCount -= 1 ; if (unSettledPromiseCount === 0 ) { resolve (result); } }, (reason ) => { result[index] = { status : "rejected" , reason, }; unSettledPromiseCount -= 1 ; if (unSettledPromiseCount === 0 ) { resolve (result); } } ); }); }); } 复制代码;
实现 promise.race 考察频率: (⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Promise .race = function (promiseArr ) { return new Promise ((resolve, reject ) => { promiseArr.forEach ((p ) => { Promise .resolve (p).then ( (val ) => { resolve (val); }, (err ) => { rejecte (err); } ); }); }); }; 复制代码;
来说一下如何串行执行多个 Promise 参考代码[2]
promise.any 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Promise .any = function (promiseArr ) { let index = 0 ; return new Promise ((resolve, reject ) => { if (promiseArr.length === 0 ) return ; promiseArr.forEach ((p, i ) => { Promise .resolve (p).then ( (val ) => { resolve (val); }, (err ) => { index++; if (index === promiseArr.length ) { reject (new AggregateError ("All promises were rejected" )); } } ); }); }); }; 复制代码;
resolve 1 2 3 4 5 6 7 8 Promise .resolve = function (value ) { if (value instanceof Promise ) { return value; } return new Promise ((resolve ) => resolve (value)); }; 复制代码;
reject 1 2 3 4 Promise .reject = function (reason ) { return new Promise ((resolve, reject ) => reject (reason)); }; 复制代码;
🐳 Array 篇 数组去重 考察频率: (⭐⭐⭐⭐⭐)
使用双重 for
和 splice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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]) { arr.splice (j, 1 ); j--; } } } return arr; } 复制代码;
使用 indexOf
或 includes
加新数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function unique (arr ) { var uniqueArr = []; for (let i = 0 ; i < arr.length ; i++) { if (uniqueArr.indexOf (arr[i]) === -1 ) { uniqueArr.push (arr[i]); } } return uniqueArr; } function unique (arr ) { var uniqueArr = []; for (let i = 0 ; i < arr.length ; i++) { if (!uniqueArr.includes (arr[i])) { uniqueArr.push (arr[i]); } } return uniqueArr; } 复制代码;
sort
排序后,使用快慢指针的思想1 2 3 4 5 6 7 8 9 10 11 12 13 14 function unique (arr ) { arr.sort ((a, b ) => a - b); var slow = 1 , fast = 1 ; while (fast < arr.length ) { if (arr[fast] != arr[fast - 1 ]) { arr[slow++] = arr[fast]; } ++fast; } arr.length = slow; return arr; } 复制代码;
sort
方法用于从小到大排序(返回一个新数组),其参数中不带以上回调函数就会在两位数及以上时出现排序错误(如果省略,元素按照转换为的字符串的各个字符的 Unicode
位点进行排序。两位数会变为长度为二的字符串来计算)。
ES6
提供的 Set
去重1 2 3 4 5 6 function unique (arr ) { const result = new Set (arr); return [...result]; } 复制代码;
Set
中的元素只会出现一次,即 Set
中的元素是唯一的。
使用哈希表存储元素是否出现(ES6
提供的 map
) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function unique (arr ) { let map = new Map (); let uniqueArr = new Array (); for (let i = 0 ; i < arr.length ; i++) { if (map.has (arr[i])) { map.set (arr[i], true ); } else { map.set (arr[i], false ); uniqueArr.push (arr[i]); } } return uniqueArr; } 复制代码;
map
对象保存键值对,与对象类似。但 map
的键可以是任意类型,对象的键只能是字符串类型。
如果数组中只有数字也可以使用普通对象作为哈希表。
filter
配合 indexOf
1 2 3 4 5 6 7 8 function unique (arr ) { return arr.filter (function (item, index, arr ) { return arr.indexOf (item) === index; }); } 复制代码;
这里有可能存在疑问,我来举个例子:
1 2 3 4 const arr = [1 , 1 , 2 , 1 , 3 ];arr.indexOf (arr[0 ]) === 0 ; arr.indexOf (arr[1 ]) !== 1 ; 复制代码;
reduce
配合 includes
1 2 3 4 5 6 7 8 9 10 function unique (arr ) { let uniqueArr = arr.reduce ((acc, cur ) => { if (!acc.includes (cur)) { acc.push (cur); } return acc; }, []); return uniqueArr; } 复制代码;
数组扁平化 考察频率: (⭐⭐⭐)
参考代码[3]
forEach 考察频率: (⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Array .prototype .myForEach = function (callbackFn ) { if (this === null || this === undefined ) { throw new TypeError ("Cannot read property 'myForEach' of null" ); } if (Object .prototype .toString .call (callbackFn) !== "[object Function]" ) { throw new TypeError (callbackFn + " is not a function" ); } var _arr = this , thisArg = arguments [1 ] || window ; for (var i = 0 ; i < _arr.length ; i++) { callbackFn.call (thisArg, _arr[i], i, _arr); } }; 复制代码;
reduce 考察频率: (⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Array .prototype .myReduce = function (callbackFn ) { var _arr = this , accumulator = arguments [1 ]; var i = 0 ; if (accumulator === undefined ) { if (_arr.length === 0 ) { throw new Error ("initVal and Array.length>0 need one" ); } accumulator = _arr[i]; i++; } for (; i < _arr.length ; i++) { accumulator = callbackFn (accumulator, _arr[i], i, _arr); } return accumulator; }; 复制代码;
map 1 2 3 4 5 6 7 8 9 10 11 12 Array .prototype .myMap = function (callbackFn ) { var _arr = this , thisArg = arguments [1 ] || window , res = []; for (var i = 0 ; i < _arr.length ; i++) { res.push (callbackFn.call (thisArg, _arr[i], i, _arr)); } return res; }; 复制代码;
filter 1 2 3 4 5 6 7 8 9 10 11 12 13 Array .prototype .myFilter = function (callbackFn ) { var _arr = this , thisArg = arguments [1 ] || window , res = []; for (var i = 0 ; i < _arr.length ; i++) { if (callbackFn.call (thisArg, _arr[i], i, _arr)) { res.push (_arr[i]); } } return res; }; 复制代码;
every 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Array .prototype .myEvery = function (callbackFn ) { var _arr = this , thisArg = arguments [1 ] || window ; var flag = true ; for (var i = 0 ; i < _arr.length ; i++) { if (!callbackFn.call (thisArg, _arr[i], i, _arr)) { return false ; } } return flag; }; 复制代码;
some 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Array .prototype .mySome = function (callbackFn ) { var _arr = this , thisArg = arguments [1 ] || window ; var flag = false ; for (var i = 0 ; i < _arr.length ; i++) { if (callbackFn.call (thisArg, _arr[i], i, _arr)) { return true ; } } return flag; }; 复制代码;
find/findIndex 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Array .prototype .myFind = function (callbackFn ) { var _arr = this , thisArg = arguments [1 ] || window ; for (var i = 0 ; i < _arr.length ; i++) { if (callbackFn.call (thisArg, _arr[i], i, _arr)) { return _arr[i]; } } return undefined ; }; 复制代码;
indexOf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function indexOf (findVal, beginIndex = 0 ) { if (this .length < 1 || beginIndex > findVal.length ) { return -1 ; } if (!findVal) { return 0 ; } beginIndex = beginIndex <= 0 ? 0 : beginIndex; for (let i = beginIndex; i < this .length ; i++) { if (this [i] == findVal) return i; } return -1 ; } 复制代码;
实现 sort 参考代码[4]
🌊 防抖节流 实现防抖函数 debounce 考察频率: (⭐⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function debounce (func, wait, immediate ) { var timeout, result; var debounced = function ( ) { var context = this ; var args = arguments ; if (timeout) clearTimeout (timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout (function ( ) { timeout = null ; }, wait); if (callNow) result = func.apply (context, args); } else { timeout = setTimeout (function ( ) { result = func.apply (context, args); }, wait); } return result; }; debounced.cancel = function ( ) { clearTimeout (timeout); timeout = null ; }; return debounced; } 复制代码;
实现节流函数 throttle 考察频率: (⭐⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function throttle (func, wait, options ) { var timeout, context, args, result; var previous = 0 ; if (!options) options = {}; var later = function ( ) { previous = options.leading === false ? 0 : new Date ().getTime (); timeout = null ; func.apply (context, args); if (!timeout) context = args = null ; }; var throttled = function ( ) { var now = new Date ().getTime (); if (!previous && options.leading === false ) previous = now; var remaining = wait - (now - previous); context = this ; args = arguments ; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout (timeout); timeout = null ; } previous = now; func.apply (context, args); if (!timeout) context = args = null ; } else if (!timeout && options.trailing !== false ) { timeout = setTimeout (later, remaining); } }; return throttled; } 复制代码;
⛲ Object 篇 能不能写一个完整的深拷贝 考察频率: (⭐⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 const getType = (obj ) => Object .prototype .toString .call (obj);const isObject = (target ) => (typeof target === "object" || typeof target === "function" ) && target !== null ; const canTraverse = { "[object Map]" : true , "[object Set]" : true , "[object Array]" : true , "[object Object]" : true , "[object Arguments]" : true , }; const mapTag = "[object Map]" ;const setTag = "[object Set]" ;const boolTag = "[object Boolean]" ;const numberTag = "[object Number]" ;const stringTag = "[object String]" ;const symbolTag = "[object Symbol]" ;const dateTag = "[object Date]" ;const errorTag = "[object Error]" ;const regexpTag = "[object RegExp]" ;const funcTag = "[object Function]" ;const handleRegExp = (target ) => { const { source, flags } = target; return new target.constructor (source, flags ); }; const handleFunc = (func ) => { if (!func.prototype ) return func; const bodyReg = /(?<={)(.|\n)+(?=})/m ; const paramReg = /(?<=\().+(?=\)\s+{)/ ; const funcString = func.toString (); const param = paramReg.exec (funcString); const body = bodyReg.exec (funcString); if (!body) return null ; if (param) { const paramArr = param[0 ].split ("," ); return new Function (...paramArr, body[0 ]); } else { return new Function (body[0 ]); } }; const handleNotTraverse = (target, tag ) => { const Ctor = target.constructor ; switch (tag) { case boolTag : return new Object (Boolean .prototype .valueOf .call (target)); case numberTag : return new Object (Number .prototype .valueOf .call (target)); case stringTag : return new Object (String .prototype .valueOf .call (target)); case symbolTag : return new Object (Symbol .prototype .valueOf .call (target)); case errorTag : case dateTag : return new Ctor (target); case regexpTag : return handleRegExp (target); case funcTag : return handleFunc (target); default : return new Ctor (target); } }; const deepClone = (target, map = new WeakMap () ) => { if (!isObject (target)) return target; let type = getType (target); let cloneTarget; if (!canTraverse[type]) { return handleNotTraverse (target, type); } else { let ctor = target.constructor ; cloneTarget = new ctor (); } if (map.get (target)) return target; map.set (target, true ); if (type === mapTag) { target.forEach ((item, key ) => { cloneTarget.set (deepClone (key, map), deepClone (item, map)); }); } if (type === setTag) { target.forEach ((item ) => { cloneTarget.add (deepClone (item, map)); }); } for (let prop in target) { if (target.hasOwnProperty (prop)) { cloneTarget[prop] = deepClone (target[prop], map); } } return cloneTarget; }; 复制代码;
参考博客[5]
实现 new 考察频率: (⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function createObject (Con ) { var obj = Object .create (null ); Object .setPrototypeOf (obj, Con .prototype ); const ret = Con .apply (obj, [].slice .call (arguments , 1 )); return typeof ret === "object" ? ret : obj; } 复制代码;
继承 考察频率: (⭐⭐⭐⭐)
原型链继承 借用构造函数(经典继承) 组合继承 原型式继承 寄生式继承 寄生组合式继承 Class 实现继承(补充一下) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Animal { constructor (name ) { this .name = name; } getName ( ) { return this .name ; } } class Dog extends Animal { constructor (name, age ) { super (name); this .age = age; } } 复制代码;
参考代码[6]
实现 object.create 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function newCreate (proto, propertiesObject ) { if (typeof proto !== "object" && typeof proto !== "function" ) { throw TypeError ("Object prototype may only be an Object: " + proto); } function F ( ) {} F.prototype = proto; const o = new F (); if (propertiesObject !== undefined ) { Object .keys (propertiesObject).forEach ((prop ) => { let desc = propertiesObject[prop]; if (typeof desc !== "object" || desc === null ) { throw TypeError ("Object prorotype may only be an Object: " + desc); } else { Object .defineProperty (o, prop, desc); } }); } return o; } 复制代码;
🚂 Function 篇 call 考察频率: (⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 Function .prototype .myCall = function (thisArg ) { thisArg = thisArg || window ; thisArg.func = this ; const args = []; for (let i = 1 ; i < arguments .length ; i++) { args.push ("arguments[" + i + "]" ); } const result = eval ("thisArg.func(" + args + ")" ); delete thisArg.func ; return result; }; 复制代码;
bind 考察频率: (⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Function .prototype .sx_bind = function (obj, ...args ) { obj = obj || window ; const fn = Symbol (); obj[fn] = this ; const _this = this ; const res = function (...innerArgs ) { console .log (this , _this); if (this instanceof _this) { this [fn] = _this; this [fn](...[...args, ...innerArgs]); delete this [fn]; } else { obj[fn](...[...args, ...innerArgs]); delete obj[fn]; } }; res.prototype = Object .create (this .prototype ); return res; }; 复制代码;
apply 考察频率: (⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 Function .prototype .myApply = function (thisArg, arr ) { thisArg = thisArg || window ; thisArg.func = this ; const args = []; for (let i = 0 ; i < arr.length ; i++) { args.push ("arr[" + i + "]" ); } const result = eval ("thisArg.func(" + args + ")" ); delete thisArg.func ; return result; }; 复制代码;
实现柯里化 考察频率: (⭐⭐⭐)
参考代码[7]
实现链式调用 参考代码[8]
偏函数 参考代码[9]
🌍 ajax 与 jsonp 考察频率: (⭐⭐⭐)
实现 ajax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function ajax ({ url = null , method = "GET" , dataType = "JSON" , async = true } ) { return new Promise ((resolve, reject ) => { let xhr = new XMLHttpRequest (); xhr.open (method, url, async ); xhr.responseType = dataType; xhr.onreadystatechange = () => { if (!/^[23]\d{2}$/ .test (xhr.status )) return ; if (xhr.readyState === 4 ) { let result = xhr.responseText ; resolve (result); } }; xhr.onerror = (err ) => { reject (err); }; xhr.send (); }); } 复制代码;
实现 jsonp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const jsonp = ({ url, params, callbackName } ) => { const generateUrl = ( ) => { let dataSrc = "" ; for (let key in params) { if (params.hasOwnProperty (key)) { dataSrc += `${key} =${params[key]} &` ; } } dataSrc += `callback=${callbackName} ` ; return `${url} ?${dataSrc} ` ; }; return new Promise ((resolve, reject ) => { const scriptEle = document .createElement ("script" ); scriptEle.src = generateUrl (); document .body .appendChild (scriptEle); window [callbackName] = (data ) => { resolve (data); document .removeChild (scriptEle); }; }); }; 复制代码;
🛫 ES6 篇 实现 set 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Set { constructor ( ) { this .items = {}; this .size = 0 ; } has (element ) { return element in this .items ; } add (element ) { if (!this .has (element)) { this .items [element] = element; this .size ++; } return this ; } delete (element ) { if (this .has (element)) { delete this .items [element]; this .size --; } return this ; } clear ( ) { this .items = {}; this .size = 0 ; } values ( ) { let values = []; for (let key in this .items ) { if (this .items .hasOwnProperty (key)) { values.push (key); } } return values; } } 复制代码;
实现 map 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 function defaultToString (key ) { if (key === null ) { return "NULL" ; } else if (key === undefined ) { return "UNDEFINED" ; } else if ( Object .prototype .toString .call (key) === "[object Object]" || Object .prototype .toString .call (key) === "[object Array]" ) { return JSON .stringify (key); } return key.toString (); } class Map { constructor ( ) { this .items = {}; this .size = 0 ; } set (key, value ) { if (!this .has (key)) { this .items [defaultToString (key)] = value; this .size ++; } return this ; } get (key ) { return this .items [defaultToString (key)]; } has (key ) { return this .items [defaultToString (key)] !== undefined ; } delete (key ) { if (this .has (key)) { delete this .items [key]; this .size --; } return this ; } clear ( ) { this .items = {}; this .size = 0 ; } keys ( ) { let keys = []; for (let key in this .items ) { if (this .has (key)) { keys.push (key); } } return keys; } values ( ) { let values = []; for (let key in this .items ) { if (this .has (key)) { values.push (this .items [key]); } } return values; } } 复制代码;
实现 es6 的 class 参考代码[10]
🦉 其他 instanceof 考察频率: (⭐⭐⭐⭐)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function instance_of (Case, Constructor ) { if ((typeof Case != "object" && typeof Case != "function" ) || Case == "null" ) return false ; let CaseProto = Object .getPrototypeOf (Case ); while (true ) { if (CaseProto == null ) return false ; if (CaseProto === Constructor .prototype ) return true ; CaseProto = Object .getPrototypeOf (CaseProto ); } } 复制代码;
实现千分位分隔符 考察频率: (⭐⭐⭐)
1 2 3 4 var str = "100000000000" , reg = /(?=(\B\d{3})+$)/g ; str.replace (reg, "," ); 复制代码;
把一个 JSON 对象的 key 从下划线形式(Pascal)转换到小驼峰形式(Camel) 考察频率: (⭐⭐⭐)
参考代码[11]
实现数据类型判断函数 1 2 3 4 function myTypeof (obj ) { return Object .prototype .toString .call (obj).slice (8 , -1 ).toLowerCase (); } 复制代码;
实现数组转树 参考代码[12]
实现 sleep 函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const sleep = (time ) => { return new Promise ((resolve ) => setTimeout (resolve, time)); }; sleep (1000 ).then (() => { console .log (1 ); }); function sleep (callback, time ) { if (typeof callback === "function" ) setTimeout (callback, time); } function output ( ) { console .log (1 ); } sleep (output, 1000 );复制代码;
实现发布订阅模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class EventEmitter { constructor ( ) { this .cache = {}; } on (name, fn ) { if (this .cache [name]) { this .cache [name].push (fn); } else { this .cache [name] = [fn]; } } off (name, fn ) { let tasks = this .cache [name]; if (tasks) { const index = tasks.findIndex ((f ) => f === fn || f.callback === fn); if (index >= 0 ) { tasks.splice (index, 1 ); } } } emit (name, once = false , ...args ) { if (this .cache [name]) { let tasks = this .cache [name].slice (); for (let fn of tasks) { fn (...args); } if (once) { delete this .cache [name]; } } } }
篇二 1.call 的实现
第一个参数为 null 或者 undefined 时,this 指向全局对象 window,值为原始值的指向该原始值的自动包装对象,如 String、Number、Boolean
为了避免函数名与上下文(context)的属性发生冲突,使用 Symbol 类型作为唯一值
将函数作为传入的上下文(context)属性执行
函数执行完成后删除该属性
返回执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 Function .prototype .myCall = function (context, ...args ) { let cxt = context || window ; let func = Symbol (); cxt[func] = this ; args = args ? args : []; const res = args.length > 0 ? cxt[func](...args) : cxt[func](); delete cxt[func]; return res; };
2.apply 的实现
前部分与 call 一样
第二个参数可以不传,但类型必须为数组或者类数组
1 2 3 4 5 6 7 8 9 10 11 Function .prototype .myApply = function (context, args = [] ) { let cxt = context || window ; let func = Symbol (); cxt[func] = this ; const res = args.length > 0 ? cxt[func](...args) : cxt[func](); delete cxt[func]; return res; };
3.bind 的实现 需要考虑:
bind() 除了 this 外,还可传入多个参数;
bind 创建的新函数可能传入多个参数;
新函数可能被当做构造函数调用;
函数可能有返回值;
实现方法:
bind 方法不会立即执行,需要返回一个待执行的函数;(闭包)
实现作用域绑定(apply)
参数传递(apply 的数组传参)
当作为构造函数的时候,进行原型继承
1 2 3 4 5 6 7 8 9 10 11 12 13 Function .prototype .myBind = function (context, ...args ) { const fn = this ; args = args ? args : []; return function newFn (...newFnArgs ) { if (this instanceof newFn) { return new fn (...args, ...newFnArgs); } return fn.apply (context, [...args, ...newFnArgs]); }; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let name = "小王" , age = 17 ; let obj = { name : "小张" , age : this .age , myFun : function (from , to ) { console .log (this .name + " 年龄 " + this .age + "来自 " + from + "去往" + to); }, }; let db = { name : "德玛" , age : 99 , }; obj.myFun .myCall (db, "成都" , "上海" ); obj.myFun .myApply (db, ["成都" , "上海" ]); obj.myFun .myBind (db, "成都" , "上海" )(); obj.myFun .myBind (db, ["成都" , "上海" ])();
4.寄生式组合继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function Person (obj ) { this .name = obj.name ; this .age = obj.age ; } Person .prototype .add = function (value ) { console .log (value); }; var p1 = new Person ({ name : "番茄" , age : 18 });function Person1 (obj ) { Person .call (this , obj); this .sex = obj.sex ; } Person1 .prototype = Object .create (Person .prototype );Person1 .prototype .constructor = Person1 ;Person1 .prototype .play = function (value ) { console .log (value); }; var p2 = new Person1 ({ name : "鸡蛋" , age : 118 , sex : "男" });
5.ES6 继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class People { constructor (name = "wang" , age = "27" ) { this .name = name; this .age = age; } eat ( ) { console .log (`${this .name} ${this .age} eat food` ); } } class Woman extends People { constructor (name = "ren" , age = "27" ) { super (name, age); } eat ( ) { super .eat (); } } let wonmanObj = new Woman ("xiaoxiami" );wonmanObj.eat ();
6.new 的实现
一个继承自 Foo.prototype 的新对象被创建。
使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤 1 创建的对象。
一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function Ctor ( ){ .... } function myNew (ctor,...args ){ if (typeof ctor !== 'function' ){ throw 'myNew function the first param must be a function' ; } var newObj = Object .create (ctor.prototype ); var ctorReturnResult = ctor.apply (newObj, args); var isObject = typeof ctorReturnResult === 'object' && ctorReturnResult !== null ; var isFunction = typeof ctorReturnResult === 'function' ; if (isObject || isFunction){ return ctorReturnResult; } return newObj; } let c = myNew (Ctor );
7.instanceof 的实现
instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。
instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。
不能检测基本数据类型,在原型链上的结果未必准确,不能检测 null,undefined
实现:遍历左边变量的原型链,直到找到右边变量的 prototype,如果没有找到,返回 false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function myInstanceOf (a, b ) { let left = a.__proto__ ; let right = b.prototype ; while (true ) { if (left == null ) { return false ; } if (left == right) { return true ; } left = left.__proto__ ; } } function myInstanceof (left, right ) { let proto = Object .getPrototypeOf (left), prototype = right.prototype ; while (true ) { if (!proto) return false ; if (proto === prototype) return true ; proto = Object .getPrototypeOf (proto); } }
8.Object.create()的实现
MDN 文档
Object.create()会将参数对象作为一个新创建的空对象的原型, 并返回这个空对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 function myCreate (obj ) { function C ( ) {} C.prototype = obj; return new C (); } if (typeof Object .create !== "function" ) { Object .create = function (proto, propertiesObject ) { if (typeof proto !== "object" && typeof proto !== "function" ) { throw new TypeError ("Object prototype may only be an Object: " + proto); } else if (proto === null ) { throw new Error ( "This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument." ); } if (typeof propertiesObject !== "undefined" ) throw new Error ( "This browser's implementation of Object.create is a shim and doesn't support a second argument." ); function F ( ) {} F.prototype = proto; return new F (); }; }
9.实现 Object.assign 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Object .assign2 = function (target, ...source ) { if (target == null ) { throw new TypeError ("Cannot convert undefined or null to object" ); } let ret = Object (target); source.forEach (function (obj ) { if (obj != null ) { for (let key in obj) { if (obj.hasOwnProperty (key)) { ret[key] = obj[key]; } } } }); return ret; };
10.Promise 的实现 实现 Promise 需要完全读懂 Promise A+ 规范,不过从总体的实现上看,有如下几个点需要考虑到:
Promise 本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从 Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆
then 需要支持链式调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 class Promise { callbacks = []; state = "pending" ; value = null ; constructor (fn ) { fn (this ._resolve .bind (this ), this ._reject .bind (this )); } then (onFulfilled, onRejected ) { return new Promise ((resolve, reject ) => { this ._handle ({ onFulfilled : onFulfilled || null , onRejected : onRejected || null , resolve : resolve, reject : reject, }); }); } _handle (callback ) { if (this .state === "pending" ) { this .callbacks .push (callback); return ; } let cb = this .state === "fulfilled" ? callback.onFulfilled : callback.onRejected ; if (!cb) { cb = this .state === "fulfilled" ? callback.resolve : callback.reject ; cb (this .value ); return ; } let ret = cb (this .value ); cb = this .state === "fulfilled" ? callback.resolve : callback.reject ; cb (ret); } _resolve (value ) { if (value && (typeof value === "object" || typeof value === "function" )) { var then = value.then ; if (typeof then === "function" ) { then.call (value, this ._resolve .bind (this ), this ._reject .bind (this )); return ; } } this .state = "fulfilled" ; this .value = value; this .callbacks .forEach ((callback ) => this ._handle (callback)); } _reject (error ) { this .state = "rejected" ; this .value = error; this .callbacks .forEach ((callback ) => this ._handle (callback)); } }
Promise.resolve
Promsie.resolve(value) 可以将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值本身是 Promise 则会原样返回它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Promise .resolve (value ) { if (value && value instanceof Promise ) { return value; } else if (value && typeof value === 'object' && typeof value.then === 'function' ) { let then = value.then ; return new Promise (resolve => { then (resolve); }); } else if (value) { return new Promise (resolve => resolve (value)); } else { return new Promise (resolve => resolve ()); } }
Promise.reject
和 Promise.resolve() 类似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。
1 2 3 Promise .reject = function (reason ) { return new Promise ((resolve, reject ) => reject (reason)); };
Promise.all
传入的所有 Promsie 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;
只要有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值;
只要有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Promise .all = function (promiseArr ) { let index = 0 , result = []; return new Promise ((resolve, reject ) => { promiseArr.forEach ((p, i ) => { Promise .resolve (p).then ( (val ) => { index++; result[i] = val; if (index === promiseArr.length ) { resolve (result); } }, (err ) => { reject (err); } ); }); }); };
Promise.race
Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Promise .race = function (promiseArr ) { return new Promise ((resolve, reject ) => { promiseArr.forEach ((p ) => { Promise .resolve (p).then ( (val ) => { resolve (val); }, (err ) => { rejecte (err); } ); }); }); };
11.Ajax 的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function ajax (url, method, body, headers ) { return new Promise ((resolve, reject ) => { let req = new XMLHttpRequest (); req.open (methods, url); for (let key in headers) { req.setRequestHeader (key, headers[key]); } req.onreadystatechange (() => { if (req.readystate == 4 ) { if (req.status >= "200" && req.status <= 300 ) { resolve (req.responeText ); } else { reject (req); } } }); req.send (body); }); }
12.实现防抖函数(debounce)
1 2 3 4 5 6 7 8 9 10 let debounce = (fn, time = 1000 ) => { let timeLock = null ; return function (...args ) { clearTimeout (timeLock); timeLock = setTimeout (() => { fn (...args); }, time); }; };
13.实现节流函数(throttle)
1 2 3 4 5 6 7 8 9 10 11 12 13 let throttle = (fn, time = 1000 ) => { let flag = true ; return function (...args ) { if (flag) { flag = false ; setTimeout (() => { flag = true ; fn (...args); }, time); } }; };
14.深拷贝(deepclone)
判断类型,正则和日期直接返回新对象
空或者非对象类型,直接返回原值
考虑循环引用,判断如果 hash 中含有直接返回 hash 中的值
新建一个相应的 new obj.constructor 加入 hash
遍历对象递归(普通 key 和 key 是 symbol 情况)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function deepClone (obj, hash = new WeakMap () ) { if (obj instanceof RegExp ) return new RegExp (obj); if (obj instanceof Date ) return new Date (obj); if (obj === null || typeof obj !== "object" ) return obj; if (hash.has (obj)) { return hash.get (obj); } let constr = new obj.constructor ( ); hash.set (obj, constr); for (let key in obj) { if (obj.hasOwnProperty (key)) { constr[key] = deepClone (obj[key], hash); } } let symbolObj = Object .getOwnPropertySymbols (obj); for (let i = 0 ; i < symbolObj.length ; i++) { if (obj.hasOwnProperty (symbolObj[i])) { constr[symbolObj[i]] = deepClone (obj[symbolObj[i]], hash); } } return constr; }
15.数组扁平化的实现(flat) 1 2 let arr = [1 , 2 , [3 , 4 , [5 , [6 ]]]];console .log (arr.flat (Infinity ));
1 2 3 4 5 6 function fn (arr ) { return arr.reduce ((prev, cur ) => { return prev.concat (Array .isArray (cur) ? fn (cur) : cur); }, []); }
16.函数柯里化 1 2 3 4 5 6 function sumFn (a, b, c ) { return a + b + c; } let sum = curry (sumFn);sum (2 )(3 )(5 ); sum (2 , 3 )(5 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function curry (fn, ...args ) { let fnLen = fn.length , argsLen = args.length ; if (fnLen > argsLen) { return function (...arg2s ) { return curry (fn, ...args, ...arg2s); }; } else { return fn (...args); } }
17.使用闭包实现每隔一秒打印 1,2,3,4 1 2 3 4 5 for (var i = 1 ; i <= 5 ; i++) { (function (i ) { setTimeout (() => console .log (i), 1000 * i); })(i); }
18.手写一个 jsonp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 const jsonp = function (url, data ) { return new Promise ((resolve, reject ) => { let dataString = url.indexOf ("?" ) === -1 ? "?" : "" ; let callbackName = `jsonpCB_${Date .now()} ` ; url += `${dataString} callback=${callbackName} ` ; if (data) { for (let k in data) { url += `${k} =${data[k]} ` ; } } let jsNode = document .createElement ("script" ); jsNode.src = url; window [callbackName] = (result ) => { delete window [callbackName]; document .body .removeChild (jsNode); if (result) { resolve (result); } else { reject ("没有返回数据" ); } }; jsNode.addEventListener ( "error" , () => { delete window [callbackName]; document .body .removeChild (jsNode); reject ("JavaScript资源加载失败" ); }, false ); document .body .appendChild (jsNode); }); }; jsonp ("http://192.168.0.103:8081/jsonp" , { a : 1 , b : "heiheihei" , }) .then ((result ) => { console .log (result); }) .catch ((err ) => { console .error (err); });
19.手写一个观察者模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class Subject { constructor (name ) { this .name = name; this .observers = []; this .state = "XXXX" ; } attach (observer ) { this .observers .push (observer); } setState (newState ) { this .state = newState; this .observers .forEach ((o ) => { o.update (newState); }); } } class Observer { constructor (name ) { this .name = name; } update (newState ) { console .log (`${this .name} say:${newState} ` ); } } let sub = new Subject ("灯" );let mm = new Observer ("小明" );let jj = new Observer ("小健" );sub.attach (mm); sub.attach (jj); sub.setState ("灯亮了来电了" );
20.EventEmitter 实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class EventEmitter { constructor ( ) { this .events = {}; } on (event, callback ) { let callbacks = this .events [event] || []; callbacks.push (callback); this .events [event] = callbacks; return this ; } off (event, callback ) { let callbacks = this .events [event]; this .events [event] = callbacks && callbacks.filter ((fn ) => fn !== callback); return this ; } emit (event, ...args ) { let callbacks = this .events [event]; callbacks.forEach ((fn ) => { fn (...args); }); return this ; } once (event, callback ) { let wrapFun = function (...args ) { callback (...args); this .off (event, wrapFun); }; this .on (event, wrapFun); return this ; } }
21.生成随机数的各种方法? 1 2 3 function getRandom (min, max ) { return Math .floor (Math .random () * (max - min)) + min; }
22.如何实现数组的随机排序? 1 2 3 4 5 let arr = [2 , 3 , 454 , 34 , 324 , 32 ];arr.sort (randomSort); function randomSort (a, b ) { return Math .random () > 0.5 ? -1 : 1 ; }
23.写一个通用的事件侦听器函数。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const EventUtils = { addEvent : function (element, type, handler ) { if (element.addEventListener ) { element.addEventListener (type, handler, false ); } else if (element.attachEvent ) { element.attachEvent ("on" + type, handler); } else { element["on" + type] = handler; } }, removeEvent : function (element, type, handler ) { if (element.removeEventListener ) { element.removeEventListener (type, handler, false ); } else if (element.detachEvent ) { element.detachEvent ("on" + type, handler); } else { element["on" + type] = null ; } }, getTarget : function (event ) { return event.target || event.srcElement ; }, getEvent : function (event ) { return event || window .event ; }, stopPropagation : function (event ) { if (event.stopPropagation ) { event.stopPropagation (); } else { event.cancelBubble = true ; } }, preventDefault : function (event ) { if (event.preventDefault ) { event.preventDefault (); } else { event.returnValue = false ; } }, };
24.使用迭代的方式实现 flatten 函数。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var arr = [1 , 2 , 3 , [4 , 5 ], [6 , [7 , [8 ]]]];function wrap ( ) { var ret = []; return function flat (a ) { for (var item of a) { if (item.constructor === Array ) { ret.concat (flat (item)); } else { ret.push (item); } } return ret; }; } console .log (wrap ()(arr));
25.怎么实现一个 sleep
sleep 函数作用是让线程休眠,等到指定时间在重新唤起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function sleep (delay ) { var start = new Date ().getTime (); while (new Date ().getTime () - start < delay) { continue ; } } function test ( ) { console .log ("111" ); sleep (2000 ); console .log ("222" ); } test ();
26.实现正则切分千分位(10000 => 10,000) 1 2 3 4 5 6 let num1 = "1321434322222" ;num1.replace (/(\d)(?=(\d{3})+$)/g , "$1," ); let num2 = "342243242322.3432423" ;num2.replace (/(\d)(?=(\d{3})+\.)/g , "$1," );
27.对象数组去重 1 2 3 4 5 6 7 8 9 输入: [ { a : 1 , b : 2 , c : 3 }, { b : 2 , c : 3 , a : 1 }, { d : 2 , c : 2 }, ]; 输出: [ { a : 1 , b : 2 , c : 3 }, { d : 2 , c : 2 }, ];
首先写一个函数把对象中的 key 排序,然后再转成字符串
遍历数组利用 Set 将转为字符串后的对象去重
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function objSort (obj ) { let newObj = {}; Object .keys (obj) .sort () .map ((key ) => { newObj[key] = obj[key]; }); return JSON .stringify (newObj); } function unique (arr ) { let set = new Set (); for (let i = 0 ; i < arr.length ; i++) { let str = objSort (arr[i]); set.add (str); } arr = [...set].map ((item ) => { return JSON .parse (item); }); return arr; }
28.解析 URL Params 为对象 1 2 3 4 5 6 7 8 9 10 let url = "http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled" ; parseParam (url);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function parseParam (url ) { const paramsStr = /.+\?(.+)$/ .exec (url)[1 ]; const paramsArr = paramsStr.split ("&" ); let paramsObj = {}; paramsArr.forEach ((param ) => { if (/=/ .test (param)) { let [key, val] = param.split ("=" ); val = decodeURIComponent (val); val = /^\d+$/ .test (val) ? parseFloat (val) : val; if (paramsObj.hasOwnProperty (key)) { paramsObj[key] = [].concat (paramsObj[key], val); } else { paramsObj[key] = val; } } else { paramsObj[param] = true ; } }); return paramsObj; }
29.模板引擎实现 1 2 3 4 5 6 let template = "我是{{name}},年龄{{age}},性别{{sex}}" ;let data = { name : "姓名" , age : 18 , }; render (template, data);
1 2 3 4 5 6 7 8 9 10 function render (template, data ) { const reg = /\{\{(\w+)\}\}/ ; if (reg.test (template)) { const name = reg.exec (template)[1 ]; template = template.replace (reg, data[name]); return render (template, data); } return template; }
30.转化为驼峰命名 1 2 var s1 = "get-element-by-id" ;
1 2 3 4 5 var f = function (s ) { return s.replace (/-\w/g , function (x ) { return x.slice (1 ).toUpperCase (); }); };
31.查找字符串中出现最多的字符和个数
例: abbcccddddd -> 字符最多的是 d,出现了 5 次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let str = "abcabcabcbbccccc" ;let num = 0 ;let char = "" ;str = str.split ("" ).sort ().join ("" ); let re = /(\w)\1+/g ;str.replace (re, ($0, $1 ) => { if (num < $0.length ) { num = $0.length ; char = $1; } }); console .log (`字符最多的是${char} ,出现了${num} 次` );
32.图片懒加载 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 let imgList = [...document .querySelectorAll ("img" )];let length = imgList.length ;const imgLazyLoad = function ( ) { let count = 0 ; return (function ( ) { let deleteIndexList = []; imgList.forEach ((img, index ) => { let rect = img.getBoundingClientRect (); if (rect.top < window .innerHeight ) { img.src = img.dataset .src ; deleteIndexList.push (index); count++; if (count === length) { document .removeEventListener ("scroll" , imgLazyLoad); } } }); imgList = imgList.filter ((img, index ) => !deleteIndexList.includes (index)); })(); }; document .addEventListener ("scroll" , imgLazyLoad);
有些重复的,可做参考 ⭐ 。