前端面试题题库
http://www.qdfuns.com/notes/24610/01b6dcaafaf78da4a2b79ebcbca9e4b6.html
https://www.cnblogs.com/En-summerGarden/p/6973522.html
https://www.nowcoder.com/discuss/85670
css基础面试题: https://juejin.im/entry/5ad2d3bff265da237a4d75dd
JavaScript 练习题库:https://scriptoj.com/
1、完成 extname
函数,它会接受一个文件名作为参数,你需要返回它的扩展名。
例如,输入 emoji.png
,返回 .png
。
const extname = (filename) => { /* TODO 要考虑输入是'hello' 和 '.hello'的情况时输出都是'' */ let arr = filename.split('.'); return (arr.length > 1 && arr[0] !== '') ? '.' + arr[arr.length-1] : ''; }
2、
观察者模式在前端开发中非常常用,我们经常用的事件就是观察者模式的一种体现。它对我们解耦模块、开发基于消息的业务起着非常重要的作用。Node.js 原生自带 EventEmitter 模块,可见它的重要性。
完成 EventEmitter
模块,它是一个类,它的实例具有以下几个方法:on
、emit
、off
:
-
on(eventName, func)
:监听eventName
事件,事件触发的时候调用func
函数。 -
emit(eventName, arg1, arg2, arg3...)
:触发eventName
事件,并且把参数arg1, arg2, arg3...
传给事件处理函数。 -
off(eventName, func)
:停止监听某个事件。
使用例子:
var EventEmitter = require('events').EventEmitter; const emitter = new EventEmitter() const sayHi = (name) => console.log(`Hello ${name}`) const sayHi2 = (name) => console.log(`Good night, ${name}`) emitter.on('hi', sayHi) emitter.on('hi', sayHi2) emitter.emit('hi', 'ScriptOJ') // => Hello ScriptOJ // => Good night, ScriptOJ emitter.off('hi', sayHi) emitter.emit('hi', 'ScriptOJ') // => Good night, ScriptOJ const emitter2 = new EventEmitter() emitter2.on('hi', (name, age) => { console.log(`I am ${name}, and I am ${age} years old`) }) emitter2.emit('hi', 'Jerry', 12) // => I am Jerry, and I am 12 years old
JavaScript
/* *浏览器端模拟EventEmitter的实现,拓展了部分功能,添加了定制实践促发的次数的功 *能, 使用方式和其他的EventEmiiter类似。 */ (function (window, undefined) { 'use strict'; /*构造函数*/ var EventEmitter = function() { this.events = {};//保存事务,存储结构为{'eventName1':[{listener:function触发的函数, time:触发的次数}], 'eventName2':[],} }; EventEmitter.prototype.once = function(evt, listener) { return this.addListener(evt, listener, 0); }; /*获取所有的事务*/ EventEmitter.prototype.getEvents = function() { return this.events || (this.events = {}); } /*获取某个实践的所有触发函数*/ EventEmitter.prototype.getListeners = function(evt) { var events = this.getEvents(); return events[evt] || (events[evt] = []); }; /** 注册实践触发函数 evet:事件名称 listener:事件监听函数 time:可选,选择可以触发的次数,-1表示无数次,默认为-1 **/ EventEmitter.prototype.on = function(evt, listener, time) { time = typeof(time) == 'number' ? time : -1; time = time >= -1 ? time : -1; var listeners = this.getListeners(evt); var listenerWrapper = { listener:listener, time:time, }; listeners.push(listenerWrapper); return this; }; /*addListener 和on 同义 */ EventEmitter.prototype.addListener = EventEmitter.prototype.on; /*移除事件的所有监听函数*/ EventEmitter.prototype.off = function(evt) { var events = this.getEvents(); events[evt] = []; }; EventEmitter.prototype.removeEvent = EventEmitter.prototype.off; /** 会删除同一事件中的所有listener **/ EventEmitter.prototype.removeListener = function(evt, listener) { var listeners = this.getListeners(evt); for(var i=0; i<listeners.length; i++) { if(listeners[i].listener == listener) { delete listeners[i]; } } }; /** 触发事件 **/ EventEmitter.prototype.trigger = function(evt, args) { var listeners = this.getListeners(evt); for(var i=0; i<listeners.length; i++){ var listener = listeners[i]; if(listener.time != -1) { listener.time--; } if (listener.time == 0) { this.removeListener(evt, listener.listener);//可以同步或异步执行 } listener.listener.apply(this, args || []); } }; EventEmitter.prototype.fire = EventEmitter.prototype.trigger; /** 触发事件 **/ EventEmitter.prototype.emit = function(evt) { var args = Array.prototype.slice.call(arguments, 1); return this.trigger(evt, args); }; EventEmitter.inherit = function(target) { if(typeof(target.prototype) == 'undefined') { throw 'target:' + target + 'must have prototype'; } var souPto = EventEmitter.prototype; var tarPto = target.prototype; for(var key in souPto) { tarPto[key] = souPto[key]; } return target; }; window.EventEmitter = EventEmitter; })(window);
找出最大子数组(js实现)
有长度为n 的数组,其元素都是int型整数(有正有负)。在连续的子数组中找到其和为最大值的数组。
如 [1, -2, 3, 10, -4, 7, 2, -5]的最大子数组为[3, 10, -4, 7, 2]
方法1: 暴力解决
直接使用循环,时间复杂度O(n^3)
function maxSubArray(arr) { const len = arr.length; if (len<=1) { return arr } let sum = arr[0]; let x_pos=0; let y_pos=0; for(let i=0;i<len-1;i++){ for(let j=i+1;j<len;j++){ let tmp = 0; for(let k=i;k<=j;k++){ tmp+=arr[k] } if (tmp>sum) { sum=tmp; x_pos=i; y_pos=j; } } } // console.log("sum is",sum) return arr.slice(x_pos,y_pos+1) }
方法2:逻辑分析
因为有正有负,所以子数组的和的最大值肯定为正值(哪怕该子数组只有一个正数)。(思考下:如果数组中全是负数时呢?)
其次可以得出:最大子数组的第一个元素、最后一个元素肯定为正值。
开始遍历数组,记录两个变量current_sum+=arr[i]
,max_sum=0
,一旦current_sum<0
是不是current_sum
中记录的连续元素就绝对不可能成为最大子数组?所以此时重置current_sum=0
开始从下一个元素记录其连续片段的和。继续与max_sum
比较即可。
在循环过程中只要current_sum>max_sum
,就设置max_sum =current_sum
这样只需要一个遍历,即可完成对最大子数组和的计算。时间复杂度O(n).
function maxSubArray(arr) { const len=arr.length; if (len<=1) { return arr } //left_pos,right_pos 记录最大子数组的位置信息 let left_pos=0; let right_pos=0; let maxSum=0; let curSum=0; for(let i=0;i<len;i++){ curSum+=arr[i] if (curSum>maxSum) { maxSum=curSum right_pos=i } if (curSum<0) { curSum=0 left_pos=i+1 } } let maxArr=arr.slice(left_pos,right_pos+1) console.log("sub arr is ",maxArr) return maxSum }
如果考虑到数组中全是负数的情况呢?
上面是根据current_sum
是否小于0来决定current_sum是
是否开始记录新的子数组的和。在数组向后扫描时,针对a[i+1]有两种选择:
- 加入
current_sum
- 作为新子数组中的第一项。
那么判断条件是什么呢?是current_sum +a[i]
与a[i]
的值的比较。如果a[i]加上前面的和却更小,那就将其作为新数组的第一项,同时更新记录数组位置的值。
for(let i=0;i<len;i++){ if (curSum+arr[i]<arr[i]) { curSum=arr[i] left_pos=i }else{ curSum=curSum+arr[i] } if(curSum>maxSum){ maxSum=curSum right_pos=i } }