han's bolg - 年糕記

2021前端社招面试总结之编程篇

这是前端社招面试总结的第二篇编程篇,没看过第一篇算法篇的可以看一下。

前言

前端社招面试,考工程编程题的比例要比纯算法要高。因为编程题中很多问题是实际工作中经常会遇到的,可以实际考察对javascript知识的理解和掌握程度。

面试过程中遇到的编程题

科里化

  1. 自定义加法,a, b 两数相加,a、b 有可能是浮点数
1
2
3
4
5
6
7
8
9
10
11
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吗,为什么

  1. 优化上面的函数,实现add(0.1)(0.2)(0.3)() === 0.6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
}
}
  1. 继续优化以上函数,实现add(0.1)(0.2)(0.3) === 0.6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
}
  1. 继续优化以上函数,实现不限定参数个数,比如add(0.1, 0.2, 0.3)(0.4) = 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
实现一个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
以此类推。
  1. function的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    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);
    }
  2. class的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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')

手写系列

  1. 实现一个 EventEmitter 类,作用是进行事件的监听和触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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');
* });
*/
}
  1. 实现一个 iterate 方法,作用是按照字典序遍历对象
1
2
3
4
5
6
7
8
9
10
11
12
13
- @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
  1. 实现深拷贝
  2. 手写继承
  3. 手写Promise、Promise.all、Promise.race、Promise.finally
  4. 实现Array.isArray
  5. 实现Array.reduce()
  6. 模拟new操作
  7. 其他手写操作可参考(https://juejin.cn/post/6875152247714480136)

网络类

  1. 实现ajax函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 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()
    })
    }
  2. 实现带超时功能的ajax,超过time时间请求未完成时,取消请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)
}))
}
  1. 实现一个异步请求管理方法。有 200 个异步请求要发送出去,但是同时最多只能发送 5 个请求。如何能够最快请求完全部请求。
  2. 手写jsonp

dom类

  1. 获取页面中出现次数最多的节点