web前端面试之笔试题

写在最前面

本篇介绍出现频率较高的 JavaScript 手写相关的基础题目,JavaScript 基础非常重要。

1、变量提升和 ES6

  • 说说箭头函数和普通函数的区别,能讲讲其中 this 的使用吗?

  • fn() 输出什么?,如果我们使用 let 会发生什么,为什么?

    function fn() {
      console.log('a', a);
      var a = 1; // let a = 1;
      function a () {
          console.log('I am a function');
      }
    }
    fn() 
    // ƒ a () {console.log('I am a function');}

    keywords: 变量提升函数提升 && let const 和 var 的区别 && 箭头函数和普通函数的区别

    2、闭包问题

  • 什么是闭包?使用场景

  • 闭包就是:能够读取其他函数内部变量的函数(参考阮一峰老师的博客)

  • 闭包的作用:可以读取函数内部的变量,和****

    for(var i=0;i<10;i++){
    
      setTimeout(function(){
    
          console.log(i)//10个10
    
      },1000) 
    
    }
    // 输出的什么?Q&A 10 个 10
    
如果想输出 0-9 可以怎么做?

for(var i=0;i<10;i++){ ((j)=>{

    setTimeout(function(){

        console.log(j)//10个10

    },1000) 
})(i)

}

#### 3、使用 JavaScript 写一个模糊搜索

- 我们在考虑一个搜索框需要注意哪些部分?

function filterMap(arr: any[],value:string){ return arr.filter(item => String(item).includes(value)) }

#### 4、什么是防抖?,什么是节流?
原理是利用闭包,外部使用内部的值。

- 防抖就是限制一定的时间才触发的函数,比如搜索框input,用户输入的时候不能一输入就触发函数,等一定的时间(比如 1s)才触发相关的函数。

- 节流就是在规定时间内只执行一次,比如页面 scroll
// 设置一个 timer = null, 传入 time 为
// setTimeout的函数的时间,返回一个函数,当 timer 存在的时候clearTimeout

// 防抖 function debounce(fn,time){ let timer = null; return function(){ if(timer){ clearTimeout(timer) } timer = setTimeout(()=>{ fn.apply(this,arguments) },time) } }

// 设置一个 canRun = true,传入 fn 和 time // 一个闭包的函数,先设置 canRun = false,直接执行setTimeout在其中把 canRun 置为 true,现在又可以执行了

function throttle(fn,time){ let canRun = true; return function(){ if(!canRun){ return } canRun = false; setTimeout(() => { fn.apply(this,arguments); canRun = true; },time) } }


#### 5callbind,apply

- 我们一般使用什么判断 JS 对象类型(typeof类型检测,instanceof 原型检测,constructor 构造函数检测, Object.prototype.toString.call([]))?
提到了callcallbind,apply讲一下啊能简单的说一下他们的使用和区别吗?

- call、apply、bind的作用是改变函数运行时this的指向。
区别:
> bind返回对应函数, 便于稍后调用; 
apply, call则是立即调用;
call 接收参数对象,apply 接收参数数组。


- 手写一个 call,apply,和bind?

//call 重写 Function.prototype.MyCall = function(context, ...args){ context = context || window; // 因为传递过来的context有可能是null context.fn = this; // 让fn的上下文为context const result = context.fn(...args); delete context.fn; return result; // 因为有可能this函数会有返回值return }

//apply 重写 Function.prototype.MyApply = function(context, arr: any[]){ context = context || window; context.fn = this; const result = context.fn(...args); delete context.fn; return result; }

// bind 重写 Function.prototype.MyBind = function(context, ...args){ var fn = this; return function () { fn.call(context, ...args, ...arguments); } }


- 理解上面的代码(MyCall)

>1:把传入的第一个参数作为 call 函数内部的一个临时对象 context2:给 context 对象一个新属性 fn , 我称呼其为实际执行函数 context.fn ;让 this 关键字(仅仅是关键字,而不是this对象)指向这个属性 ,即 context.fn = this ; 注意 : 在这里的 this 对象指向的是调用call()函数的函数对象。
3:传入 args 然后执行 context.fn,返回的结果保存在 result 中。
4:执行完成后再把 context.fn 删除。返回执行 result。



#### 6、JavaScript 设计模式考察
先讲讲平时设计开发中常用的设计模式?

>设计原理:单一责任原则(SRP),最少知识原则(LKP),开放-封闭原则(OCP)
设计模式: 他的原则是“找出 程序中变化的地方,并将变化封装起来”,它的关键是意图,而不是结构。
平时设计模式非常重要,这里只记录了两种考察得比较多设计模式,其他的还需要大家继续复习。

- 单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点

function SetManager(name) { this.manager = name; }

SetManager.prototype.getName = function() { console.log(this.manager); };

var SingletonSetManager = (function() { var manager = null;

return function(name) {
    if (!manager) {
        manager = new SetManager(name);
    }

    return manager;
} 

})();

SingletonSetManager('a').getName(); // a SingletonSetManager('b').getName(); // a SingletonSetManager('c').getName(); // a

- 发布-订阅模式(观察者模式)

定义了对象间的一种一对多的依赖关系,当一个对象的状态发 生改变时,所有依赖于它的对象都将得到通知

// 观察者 var observer = { // 订阅集合 subscribes: [],

// 订阅
subscribe: function(type, fn) {
    if (!this.subscribes[type]) {
        this.subscribes[type] = [];
    }

    // 收集订阅者的处理
    typeof fn === 'function' && this.subscribes[type].push(fn);
},

// 发布  可能会携带一些信息发布出去
publish: function() {
    var type = [].shift.call(arguments),
        fns = this.subscribes[type];

    // 不存在的订阅类型,以及订阅时未传入处理回调的
    if (!fns || !fns.length) {
        return;
    }

    // 挨个处理调用
    for (var i = 0; i < fns.length; ++i) {
        fns[i].apply(this, arguments);
    }
},

// 删除订阅
remove: function(type, fn) {
    // 删除全部
    if (typeof type === 'undefined') {
        this.subscribes = [];
        return;
    }

    var fns = this.subscribes[type];

    // 不存在的订阅类型,以及订阅时未传入处理回调的
    if (!fns || !fns.length) {
        return;
    }

    if (typeof fn === 'undefined') {
        fns.length = 0;
        return;
    }

    // 挨个处理删除
    for (var i = 0; i < fns.length; ++i) {
        if (fns[i] === fn) {
            fns.splice(i, 1);
        }
    }
}

};

使用 emit,on 来现实这个类

class Event { constructor() { this.eventTypeObj = {} this.cacheObj = {} } on(eventType, fn) { if (!this.eventTypeObj[eventType]) { // 按照不同的订阅事件类型,存储不同的订阅回调 this.eventTypeObj[eventType] = [] } this.eventTypeObj[eventType].push(fn)

    // 如果是先发布,则在订阅者订阅后,则根据发布后缓存的事件类型和参数,执行订阅者的回调
    if (this.cacheObj[eventType]) {
        var cacheList = this.cacheObj[eventType]
        for (var i = 0; i < cacheList.length; i++) {
            cacheList[i]()
        }
    }
}
emit() {
    // 可以理解为arguments借用shift方法
    var eventType = Array.prototype.shift.call(arguments)
    var args = arguments
    var that = this

    function cache() {
        if (that.eventTypeObj[eventType]) {
            var eventList = that.eventTypeObj[eventType]
            for (var i = 0; i < eventList.length; i++) {
                eventList[i].apply(eventList[i], args)
            }
        }
    }
    if (!this.cacheObj[eventType]) {
        this.cacheObj[eventType] = []
    }

    // 如果先订阅,则直接订阅后发布
    cache(args)

    // 如果先发布后订阅,则把发布的事件类型与参数保存起来,等到有订阅后执行订阅
    this.cacheObj[eventType].push(cache)
}

}

#### 7、JavaScript 链式调用和异步处理

实现一个 lazyMan

LazyMan("Hank").eat("dinner").sleepFirst(5).sleep(2).eat("supper");

打印如下

// Hank // ...(延迟5s) // dinner // ... (延迟2s) // supper


实现如上函数

重点在于 next,我们把执行的函数放进 queue 中,在运行的时候 next(), 取出数组中的函数执行。

function _LazyMan(name){ this.nama = name; this.queue = []; this.queue.push(() => { console.log("Hi! This is " + name + "!"); this.next(); }) setTimeout(()=>{ this.next() },0) }

_LazyMan.prototype.eat = function(name){ this.queue.push(() =>{ console.log("Eat " + name + "~"); this.next() }) return this; }

_LazyMan.prototype.next = function(){ var fn = this.queue.shift(); fn && fn(); }

_LazyMan.prototype.sleep = function(time){ this.queue.push(() =>{ setTimeout(() => { console.log("Wake up after " + time + "s!");

    this.next()
},time * 1000)

}) return this; }

_LazyMan.prototype.sleepFirst = function(time){ this.queue.unshift(() =>{ setTimeout(() => { console.log("Wake up after " + time + "s!");

    this.next()
},time * 1000)

}) return this; }

function LazyMan(name){ return new _LazyMan(name) }

补充: 如果我们使用 promise 怎么实现如上的 lazyMan?


#### 8、实现一个有缓存的斐波拉契数列

var cache = { }; var count = 0; function fib(n){ count++; if(n === 1 || n === 2){ return 1; } if(cache[n]){ return cache[n]; }else{ var ret = fib(n - 1) + fib(n - 2); cache[n] = ret; return ret; } }

#### 9、不同路径(一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角.
递归的思想

function robotPath(m,n) { if(m == 1 && n == 1) return 1; if(m == 1) return robotPath(m, n - 1); if(n == 1) return robotPath(m - 1, n); return robotPath(m-1, n) + robotPath(m, n - 1); } ```