这是前端社招面试总结的第二篇编程篇,没看过第一篇算法篇的可以看一下。
前言
前端社招面试,考工程编程题的比例要比纯算法要高。因为编程题中很多问题是实际工作中经常会遇到的,可以实际考察对javascript知识的理解和掌握程度。
面试过程中遇到的编程题
科里化
- 自定义加法,a, b 两数相加,a、b 有可能是浮点数
function add(a, b) {
let count = 1;
// 当a和b都不是整数时,继续乘10
while(a !== Math.floor(a) && b !== Math.floor(b)) {
a *= 10;
b *= 10;
count *= 10;
}
return (a + b) / count;
}
小tip:面试官可能会问的问题,0.1+0.2===0.3吗,为什么
- 优化上面的函数,实现add(0.1)(0.2)(0.3)() === 0.6
function superAdd(a, b) {
let count = 1;
// 当a和b都不是整数时,继续乘10
while(a !== Math.floor(a) && b !== Math.floor(b)) {
a *= 10;
b *= 10;
count *= 10;
}
return (a + b) / count;
}
function add(param1) {
let res = param1 || 0;
return function innerAdd(param2) {
res = superAdd(res, param2 || 0);
// 无参数,返回res
if(param2 === undefined) {
return res;
}
// 有参数,返回函数
return innerAdd;
}
}
- 继续优化以上函数,实现add(0.1)(0.2)(0.3) === 0.6
function superAdd(a, b) {
let count = 1;
// 当a和b都不是整数时,继续乘10
while(a !== Math.floor(a) && b !== Math.floor(b)) {
a *= 10;
b *= 10;
count *= 10;
}
return (a + b) / count;
}
function add(param1) {
let res = param1 || 0;
const innerAdd = function(param2) {
res = superAdd(res, param2 || 0);
// 无参数,返回res
// 有参数,返回函数
return innerAdd;
}
innerAdd.toString = function() {
return res;
}
return innerAdd;
}
- 继续优化以上函数,实现不限定参数个数,比如add(0.1, 0.2, 0.3)(0.4) = 1
function superAdd(a, b) {
let count = 1;
// 当a和b都不是整数时,继续乘10
while(a !== Math.floor(a) && b !== Math.floor(b)) {
a *= 10;
b *= 10;
count *= 10;
}
return (a + b) / count;
}
function add() {
let res = 0;
if(arguments.length) {
res = [...arguments].reduce((pre, cur) => superAdd(pre, cur));
}
const innerAdd = function() {
if(arguments.length) {
res = [...arguments].reduce((pre, cur) => superAdd(pre, cur), res);
}
// 无参数,返回res
// 有参数,返回函数
return innerAdd;
}
innerAdd.toString = function() {
return res;
}
return innerAdd;
}
实现LazyMan
实现一个LazyMan,可以按照以下方式调用:
LazyMan(“Hank”)输出:
Hi! This is Hank!
LazyMan(“Hank”).sleep(10).eat(“dinner”)输出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)输出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
以此类推。
- function的方式
function LazyMan(name) {
var task = () => { console.log(name); next() };
var tasks = [task];
var next = () => {
if (tasks.length) {
var tt = tasks.shift();
tt && tt();
}
}
var res = {
eat: function (type) {
tasks.push(() => {
console.log(type);
next();
});
return res;
},
sleep: function (time) {
var task = () => {
setTimeout(() => {
console.log(`sleep ${time}`);
next();
}, time)
}
tasks.push(task);
return res;
},
sleepFirst: function (time) {
var task = () => {
setTimeout(() => {
console.log(`sleep ${time}`);
next();
}, time)
}
tasks.unshift(task);
return res;
}
}
setTimeout(() => { next() });
return res;
}
LazyMan('Tony').eat('lunch').sleep(10).eat('dinner').sleepFirst(5000);
}
- class的方式
class LazyManClass {
// 构造函数
constructor(name) {
this.name = name
// 定义一个数组存放执行队列
this.queue = [()=>{
console.log(`Hi I am ${name}`)
this.next()
}]
setTimeout(() => {
this.next()
}, 0)
}
// 定义原型方法
eat(food) {
var fn = () => {
console.log(`I am eating ${food}`)
this.next()
}
this.queue.push(fn)
return this
}
sleep(time) {
var fn = () => {
// 等待了10秒...
setTimeout(() => {
console.log(`等待了${time}秒`)
this.next()
}, 1000 * time)
}
this.queue.push(fn)
return this
}
sleepFirst(time) {
var fn = () => {
// 等待了5秒...
setTimeout(() => {
console.log(`等待了${time}秒`)
this.next()
}, 1000 * time)
}
this.queue.unshift(fn)
return this
}
next() {
var fn = this.queue.shift()
fn && fn()
}
}
function LazyMan(name) {
return new LazyManClass(name)
}
LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food')
手写系列
- 实现一个 EventEmitter 类,作用是进行事件的监听和触发
class EventEmitter {
/**
* 实现一个 EventEmitter 类,作用是进行事件的监听和触发
* @example
* const ee = new EventEmitter();
* ee.on('foo', () => {
* console.log('Foo event');
* });
* ee.on('bar', data => {
* console.log(`Bar event with ${data.value}!`)
* });
* ee.emit('foo'); // 打印 Foo event 和 Foo event again
* ee.emit('bar', { value: 1 }); // 打印 Bar Event with 1
* ee.on('foo', () => {
* console.log('Foo event again');
* });
*/
}
- 实现一个 iterate 方法,作用是按照字典序遍历对象
- @param {object} obj 传入的对象
- @returns {object}
- @example
- const obj = {
- c: 1,
- b: 2,
- a: 3
- }
- const iter = iterate(obj);
- console.log(iter.next()); // 打印 a
- console.log(iter.next()); // 打印 b
- console.log(iter.next()); // 打印 c
- console.log(iter.next()); // 打印 undefined
- 实现深拷贝
- 手写继承
- 手写Promise、Promise.all、Promise.race、Promise.finally
- 实现Array.isArray
- 实现Array.reduce()
- 模拟new操作
- 其他手写操作可参考(juejin.cn/post/687515…)
网络类
- 实现ajax函数
// promise版
function myAjax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
// 知识点:xhr.open(method, url, async, username, password);第三个参数请求方式是否为异步,默认为true
xhr.open('GET', url)
xhr.onreadystatechange = function () {
// 知识点:readyState 0尚未调用xhr.open 1已调用xhr.open 2已调用xhr.send 3下载中 4下载已完成
if (this.readyState === 4) {
if (this.status === 200 || this.status === 304) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
}
// 知识点:send(body),body-post请求的数据体
xhr.send()
})
}
- 实现带超时功能的ajax,超过time时间请求未完成时,取消请求
function myAjax(url, timeout) {
const ajaxPromise = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200 || this.status === 304) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
}
xhr.send()
})
return Promise.race(ajaxPromise, new Promise((resolve) => {
setTimeout(() => {
resolve('请求超时')
}, time)
}))
}
- 实现一个异步请求管理方法。有 200 个异步请求要发送出去,但是同时最多只能发送 5 个请求。如何能够最快请求完全部请求。
- 手写jsonp
dom类
- 获取页面中出现次数最多的节点