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) } }
#### 5、call,bind,apply
- 我们一般使用什么判断 JS 对象类型(typeof类型检测,instanceof 原型检测,constructor 构造函数检测, Object.prototype.toString.call([]))?
提到了call,call,bind,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 函数内部的一个临时对象 context;
2:给 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); } ```