本文最后更新于:2025年1月29日 凌晨
16. 实现一个Event Emitter Node.js中有Event Emitter ,Facebook 也曾经有自己的实现 不过已经archive了。
请实现你自己的 Event Emitter
1 const emitter = new Emitter ()
它需要支持事件订阅
1 2 3 4 5 const sub1 = emitter.subscribe ('event1' , callback1)const sub2 = emitter.subscribe ('event2' , callback2)const sub3 = emitter.subscribe ('event1' , callback1)
emit(eventName, ...args)
可以用来触发callback
1 2 emitter.emit ('event1' , 1 , 2 );
subscribe()
返回一个含有release()
的对象,可以用来取消订阅。
1 2 3 4 sub1.release () sub3.release ()
ans:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class EventEmitter { eventCache = {}; cbId = 0 ; subscribe (eventName, callback ) { const eventKey = eventName, funcId = this .cbId ++; this .eventCache [eventKey] = this .eventCache [eventKey] || {}; this .eventCache [eventKey][funcId] = callback; return { release : () => { delete this .eventCache [eventKey][funcId]; } } } emit (eventName, ...args ) { return this .eventCache [eventName] && Object .values (this .eventCache [eventName]).forEach (cb => cb.apply (null , args)) } }
17. 实现一个DOM element store JavaScript中有Map
,我们可以用任何data做key,即便是DOM元素。
1 2 const map = new Map () map.set (domNode, somedata)
如果运行的JavaScript不支持Map,我们如何能让上述代码能够工作?
请在不利用Map的条件下实现一个Node Store,支持DOM element作为key。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class NodeStore { set (node, value ) { } get (node ) { } has (node ) { } }
你可以实现一个通用的Map polyfill。或者利用以下DOM元素的特性来做文章?
请注意时间空间复杂度。
ans:
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 class NodeStore { constructor ( ) { this .store = {}; this .nodeKey = Symbol (); } set (node, value ) { node[this .nodeKey ] = Symbol (); this .store [node[this .nodeKey ]] = value; } get (node ) { return this .store [node[this .nodeKey ]]; } has (node ) { return !!this .store [node[this .nodeKey ]]; } }
18. 优化一个function 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let items = [ {color : 'red' , type : 'tv' , age : 18 }, {color : 'silver' , type : 'phone' , age : 20 }, {color : 'blue' , type : 'book' , age : 17 } ] const excludes = [ {k : 'color' , v : 'silver' }, {k : 'type' , v : 'tv' }, ... ] function excludeItems (items, excludes ) { excludes.forEach ( pair => { items = items.filter (item => item[pair.k ] === item[pair.v ]) }) return items }
上述excludeItems
方法是什么用途?
上述方法是否和设想的一样在运作?
上述方法的时间复杂度是?
你能否优化以下?
注意
BFE.dev仅仅根据结果进行judge,不会考虑时间成本。请提交你觉得最好的解答。
ans:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function excludeItems (items, excludes ) { items = items.filter (item => { for (let pair of excludes) { if (item[pair.k ] === pair.v ) { return false ; } } return true ; }) return items; }
19. 相同结构的DOM tree上面寻找对应的节点 给定两个完全一样的DOM Tree A 和B ,以及A 中的元素a ,请找到B 中对应的元素b 。
补充说明
这个问题可以出在一般的树结构上,DOM Tree只是一个特例。
你能否既通过递归也能通过迭代来解决该问题。
既然是DOM Tree,能否提供一个利用到DOM tree特性的解法?
你的解法的时空复杂度是多少?
ans:
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 const findCorrespondingNode = (rootA, rootB, target ) => { if (rootA === target) { return rootB; } for (let i = 0 ; i < rootA.children .length ; i++) { let found = findCorrespondingNode (rootA.children [i], rootB.children [i], target); if (found) return found; } }const findCorrespondingNode = (rootA, rootB, target ) => { if (rootA === target) { return rootB; } const queueA = [rootA]; const queueB = [rootB]; while (queueA.length ) { const currentElementA = queueA.shift (); const currentElementB = queueB.shift (); if (currentElementA === target) { return currentElementB; } queueA.push (...currentElementA.children ); queueB.push (...currentElementB.children ); } return null ; }const findCorrespondingNode = (rootA, rootB, target ) => { const stack = [[rootA, rootB]]; while (stack.length > 0 ) { const [leftNode, rightNode] = stack.pop (); if (leftNode === target) return rightNode; for (let i = 0 ; i < leftNode.children .length ; i++) { stack.push ([leftNode.children [i], rightNode.children [i]]); } } }
20. 检测 data type 这是个简单的问题。
对于JavaScript中的所有基础数据类型 ,请实现一个方法进行检测。
除了基础数据类型之外,你的方法需要额外支持常见的类型包括Array
、ArrayBuffer
、Map
、 Set
、Date
和 Function
。
该题目的目标并不是想要你列举出所有数据类型,而是想要你证明你能解决该类型的问题。
类型名请返回小写。
1 2 3 4 5 6 detectType (1 ) detectType (new Map ()) detectType ([]) detectType (null )
ans:
1 2 3 4 5 6 7 8 9 10 11 12 function detectType (data ) { if (data instanceof FileReader ) return 'object' ; return Object .prototype .toString .call (data).slice (1 , -1 ).split (' ' )[1 ].toLowerCase (); }
21. 手写JSON.stringify() 相信你必定用过JSON.stringify()
,你知道它是如何工作的吗?
请脑补以下其内部逻辑,然后参考 MDN的说明 ,其实并不简单。
回到本题目,请实现你自己的JSON.stringify()
。
在真正面试的时候,面试官并不期待你能完全按照spec来实现,请预先和面试官决定需要支持的范围。为了达到练习的目的,该题目将会测试多种数据类型,请尽量考虑周全。
并请注意循环引用。
注意
JSON.stringify()
有额外两个参数,这里并不需要支持。
不要直接用JSON.stringify()
糊弄BFE.dev,这样做并不能帮助你的面试。
ans:
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 function stringify (data ) { const type = getType (data); switch (type) { case 'string' : return `"${data} "` ; case 'number' : case 'boolean' : return `${data} ` ; case 'null' : case 'undefined' : case 'symbol' : return 'null' ; case 'function' : return undefined ; case 'date' : return `"${data.toISOString()} "` ; case 'bigint' : throw new Error ("can not stringify 'bigint' type" ); case 'array' : const arr = data.map (v => stringify (v)); return `[${arr.join(',' )} ]` ; case 'object' : case 'map' : const items = Object .entries (data).reduce ((acc, cur ) => { let [key, value] = cur; if (value === undefined ) { return acc; } acc.push (`${stringify(key)} :${stringify(value)} ` ); return acc; }, []) return `{${items.join(',' )} }` } }function getType (data ) { if (Number .isNaN (data)) { return 'null' ; } else if (data === Infinity ) { return 'null' ; } else { return Object .prototype .toString .call (data).slice (1 , -1 ).split (' ' )[1 ].toLowerCase (); } }
22. 手写JSON.parse() 该问题是 21. 手写JSON.stringify() 的后续。
相信你已经很熟悉JSON.parse()
了,你能自己实现一个吗?
假若你还不熟悉spec的话,可以参考MDN 的说明 ,也许会有帮助。
JSON.parse()
支持第二个参数reviver
,你可以在本题目中忽略。
ans:
1 2 3 4 5 6 7 8 9 10 11 12 function parse (str ) { let result = (new Function (`return ${str.replace(/\"/g, " '")}`))(); if(str !== JSON.stringify(result)) { throw new Error(' Has A Error '); } return result; }
23. 实现一个sum()方法 实现一个 sum()
,使得如下判断成立。
1 2 3 4 5 const sum1 = sum (1 )sum1 (2 ) == 3 sum1 (3 ) == 4 sum (1 )(2 )(3 ) == 6 sum (5 )(-1 )(2 ) == 6
ans:
1 2 3 4 5 6 7 8 9 10 function sum (num ) { const fn = b => sum (num + b); fn[Symbol .toPrimitive ] = () => num; return fn; }
24. 用JavaScript手写一个Priority Queue 优先队列(Priority Queue) 是在算法题目中经常用到的数据结构。特别是Top-k 系列问题非常有效,因为它可以避免整体的排序。
JavaScript中没有原生的优先队列。在真实的面试中,你可以告诉面试官说“假设我们已经又一个优先队列的实现我可以直接使用”,因为没有时间让我们去手写一个优先队列。
但是这不妨碍优先队列成为一个很好的联手题目,所以请手写一个优先队列!
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 class PriorityQueue { constructor (compare ) { } add (element ) { } poll ( ) { } peek ( ) { } size ( ) { } }
以下的例子可能更好理解
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 const pq = new PriorityQueue ((a, b ) => a - b) pq.add (5 ) pq.add (2 ) pq.add (1 ) pq.peek () pq.poll () pq.peek () pq.poll ()
ans:
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 class PriorityQueue { constructor (compare ) { this .compare = compare; this .data = []; } size ( ) { return this .data .length ; } peek ( ) { return this .data [0 ]; } add (element ) { this .data .push (element); if (this .compare ) { this .data .sort (this .compare ); } } poll ( ) { return this .data .shift (); } }
25. 更新数组的顺序 假设我们又一个数组A ,以及另外一个整数数组 B .
1 2 const A = ['A' , 'B' , 'C' , 'D' , 'E' , 'F' ]const B = [1 , 5 , 4 , 3 , 2 , 0 ]
你需要对A进行重新排序,A[i]的新位置将在B[i],也就是说B是A的各个元素的新索引。
上述例子进行重排过后,应该得到如下结果
1 ['F' , 'A' , 'E' , 'D' , 'C' , 'B' ]
传入的数据保证是有效的。
继续问问
使用额外的O(n)
空间很简单就能完成该题目,你能不实用额外空间完成该题目吗?
ans:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function sort (items, newOrder ) { for (let key=0 ; key<items.length ; key++){ let value =newOrder[key]; [items[value], items[key]] = [items[key], items[value]]; [newOrder[key], newOrder[value]] = [newOrder[value], newOrder[key]]; } }
26. 实现Object.assign() Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 (source: MDN )
这个方法很常用,实际上展开语法 的内部逻辑和Object.assign()
是一样的(source )。以下两行代码完全等价。
1 2 let aClone = { ...a };let aClone = Object .assign ({}, a);
这是个简单的题目,请自行实现Object.assign()
。
注意
不要直接使用Object.assign() 这不会对你的能力提高有帮助。
ans:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function objectAssign (target, ...sources ) { if ([null , undefined ].includes (target)) { throw new Error ("Expected function to throw an exception." ); } const obj = new Object (target); for (let item of sources) { if (['object' , 'string' , 'function' ].includes (typeof item) && item !== null ) { Object .defineProperties (obj, Object .getOwnPropertyDescriptors (item)); } } return obj; }
27. 实现completeAssign() 本题是 26. 实现Object.assign() 的延续。
Object.assign()
处理的是可枚举属性,所以getters不会被复制,不可枚举属性被忽略。
假设我们有如下的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 const source = Object .create ( { a : 3 }, { b : { value : 4 , enumerable : true }, c : { value : 5 , }, d : { get : function ( ) { return this ._d ; }, set : function (value ) { this ._d = value } }, e : { get : function ( ) { return this ._e ; }, set : function (value ) { this ._e = value }, enumerable : true } } )
如果我们调用 Object.assign()
的话,我们得到的是
1 2 3 4 Object .assign ({}, source)
这也许不是我们想要的结果,你能否实现一个completeAssign()
,使得data descriptors和 accessor descriptors都能被拷贝?
如果你还不熟悉descriptors,请参照MDN的说明 。
该问题单纯地想要考考你对descriptors的理解。
祝你好运!
ans:
1 2 3 4 5 6 7 8 9 10 11 12 function completeAssign (target, ...sources ) { if ([null , undefined ].includes (target)) { throw new Error ("Expected function to throw an exception." ); } target = new Object (target); sources.forEach (source => { if (['object' , 'string' , 'function' ].includes (typeof source) && source !== null ) { Object .defineProperties (target, Object .getOwnPropertyDescriptors (source)); } }); return target; }
28. 实现clearAllTimeout() window.setTimeout()
可以用来设定未来将要执行的任务。
你能否实现一个clearAllTimeout()
来取消掉所有未执行的timer?比如当页面跳转的时候我们或许想要清除掉所有的timer。
1 2 3 4 5 6 7 8 setTimeout (func1, 10000 )setTimeout (func2, 10000 )setTimeout (func3, 10000 )clearAllTimeout ()
注意
你需要保证window.setTimeout
和 window.clearTimeout
还是原来的interface,虽然你可以替换其中的逻辑。
ans:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function clearAllTimeout ( ) { timers.forEach (v => clearTimeout (v)); }let timers = [];const originTimer = setTimeout ;setTimeout = (...args ) => { let timer = originTimer (...args); timers.push (timer); return timer; }