前端面试题题库

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 模块,它是一个类,它的实例具有以下几个方法:onemitoff

  • 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
    }
}