记录广州申迪的前端面试

前言

记录广州申迪的前端面试。

找了一个月工作了,投了简历很多都是没回复的,一个月只面试了 4 家。

感觉要回家送外卖了。

正文

这个公司需要先笔试,所以在那里填了差不多 1 个小时题目。

基本数据类型和复杂数据类型

这个没什么好说的,看给的多少个空,一定要填的为 numberstringbooleanundefinednull ,空多的话再协商 symbolbigint

复杂数据类型就是 object 对象。

盒子 margin-top 坍塌

给定两个块级盒子,上方盒子设置 margin-bottom: 10px 下方盒子设置 margin-top: 5px ,问两者的差距是多少。

这题主要考相邻盒子的 margin 坍塌(也称为外边距折叠)以及 BFC ,即 Block Formatting Context ,中文名为区块格式化上下文。

相邻盒子在默认情况下会发生 margin 坍塌,即会取 margin 大的一方作为两者的间距。所以该题答案为 10px

为了解决这种情况,需要让其中的一个盒子创建 BFC ,这样即可解决该问题,最简单地解决方法就是加上 overflow: hidden 使其创建 BFC 。

失焦的事件名

聚焦是 focus ,失焦是 blur

原型链

输出以下代码的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
function o(name) {
this.name = name;
}

o.protoType.print = function() {
console.log(this.name);
}

const i = new o("dd");
i.name = "ee";
i.__proto__.name = "dd"
i.__proto__.print();
i.print();

问输出结果,这里需要对原型链有一些认识,o.protoTypei.__proto__ 为相同的对象,每个实例化的对象的 __proto__ 都指向它构造器的 protoType

接着我们需要知道,非箭头函数的调用的 this 取决于谁调用的它。

  • 对于 i.__proto__.print() 此时 thisi.__proto__
  • 对于 i.print() 此时 thisi

这里需要注意 i.printi.__proto__.print 为同一个函数, i.printi 上没有找到 print 方法后一直往原型链上面查找。

所以这道题的答案为 ddee

数组中插入元素的方法

这里给了三个空,应该是填 pushunshiftsplice

PS:这里弄混了 shiftunshift ,写成 shift 了, shift 为弹出头元素,给自己菜麻了😅。

  • push 在尾部插入一个元素。
  • unshift 在头部插入一个元素。
  • splice 可以指定某个位置删除并插入元素,只需把第二个参数设置为 0 ,那么就不会删除元素了,再指定第三个以后的参数就可以往该位置插值了。

地址栏输入地址到显示页面的过程

纯八股文,这种直接百度。

深拷贝与浅拷贝

浅拷贝可以用很多内置的方法实现,比如字面对象的展开,数组的展开:

1
2
const o = { a: 1, b: 2 };
const copy = { ...o };
1
2
const o = [1, 2, 3];
const copy = [...o];

或者使用 Object.assign 来进行浅拷贝:

1
2
const o = { a: 1, b: 2 };
const copy = Object({}, o);

深拷贝其实就是递归浅拷贝,当然也有一些方便的方法来进行快速的深拷贝。

如果对象不包含无法序列化的类型(比如 Function , Symbol , DOM 对象等),且没有循环引用,则可以使用 JSON.parse(JSON.stringify(obj))

1
2
const o = { a: 1, b: 2 };
const copy = JSON.parse(JSON.stringify(o));

如果你的执行环境够现代,你还可以使用 structuredClone 原生接口,它很好地处理了循环引用的情况,而且支持所有支持结构化克隆的类型,当然对于其他类型也是无法克隆的。

除此之外,还可以使用三方库,比如 lodash 的 clone ,不过它也是实现了结构化克隆的类型,如果还要支持其他不支持的类型,那就得手写克隆函数来处理对应的情况了。

手写一个节流(throttle)函数

可以基于时间戳或者 setTimeout 来实现。

setTimeout 的版本我们可以参考 radash 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const throttle = ({ interval }, func) => {
let ready = true;
let timer = void 0;
const throttled = (...args) => {
if (!ready)
return;
func(...args);
ready = false;
timer = setTimeout(() => {
ready = true;
timer = void 0;
}, interval);
};
throttled.isThrottled = () => {
return timer !== void 0;
};
return throttled;
};

基于 Date.now 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function throttle(func, delay) {
let lastCallTime = 0;

const throttled = (...args) => {
const now = Date.now();
if (now - lastCallTime < delay) {
return;
}
func(...args);
lastCallTime = now;
};

return throttled;
}

手写一个解析地址栏参数的函数

写一个函数 ,要求根据当前 href 来解析 query 参数,即,如果访问 http://example.com?a=1&b=2 ,那么函数 fn("a") 返回 1fn("b") 返回 2

这道题直接使用 URL 对象来搞定,手写实现是不可能,只能调调 api 这个样子:

1
2
3
4
function fn(key) {
const url = new URL(location.href);
return url.searchParams.get(key);
}

当然如果要手写实现的话,其实就是那几个步骤:

  • 通过 location.search 拿到查询字符串。
  • 去掉 ? ,然后通过 & 分割。
  • 接着每一项通过 = 分割,索引 0 为键,索引 1 为值。
  • 返回对应值。
1
2
3
4
5
6
7
8
9
10
11
12
13
function fn(searchKey) {
const queryString = location.search;
const queryList = queryString.substring(1).split("&");

for(let i = 0; i < queryList.length; i++) {
const [key, value] = queryList[i].split("=");
if (key === searchKey) {
return decodeURIComponent(value);
}
}

return null;
}