前言
这期分享的是 Shopee
的面经,中间较为曲折。先走了一轮卖家平台的面试,HR
面面完之后说不合适。后面供应链部门 HR
又找到我,说可以再面一次供应链部门,所以就有了两次面经,每次都有技术一二面以及 HR
面
自己是三月份面的 Shopee
,以下作为自己的面经记录(当时算是一年半多经验),有一些问题会总结归纳知识点,希望对大家有所帮助【仅供参考,欢迎讨论】
前面整理了一下 Bigo
的,感兴趣可以看下:【面试说】一年半前端 Bigo 一二三 面[1]
谈谈对面经的看法:关于面经,其实个人的理解是作为一个查漏补缺的作用,深入的话,还是得靠其它途径的学习和实践
卖家平台
一面
如何判断数据类型
事件循环,以及事件循环题目【忘了题目】
关于事件循环,我写了一篇 【前端进阶】深入浅出浏览器事件循环【内附练习题】[2],我自认为是比较深入浅出。能够做出文末的题目,你就成功了
事件委托
深复制和浅复制
深复制有哪些方法
let、var、const 的区别
说说在 JS 中变量的存储方式
类似如下题目:
代码语言:javascript复制// 基本数据类型-栈内存
let a1 = 0;
// 基本数据类型-栈内存
let a2 = 'this is string';
// 基本数据类型-栈内存
let a3 = null;
// 对象的指针存放在栈内存中,指针指向的对象存放在堆内存中
let b = { m: 20 };
// 数组的指针存放在栈内存中,指针指向的数组存放在堆内存中
let c = [1, 2, 3];
对于原始类型,数据本身是存在栈内,对于对象类型,在栈中存的只是一个堆内地址的引用
上面的如下图所示:
内存中栈区的数据,在函数调用结束后,就会自动的出栈,不需要程序进行操作,操作系统会自动执行,换句话说:栈中的变量在函数调用结束后,就会消失
那么在栈中存储不了的数据(比如一个对象),就会被存储在堆中,栈中就仅仅保留一个对该数据的引用(也就是该块数据的首地址)
参考:「前端进阶」JS中的栈内存堆内存[3]
this 的指向,箭头函数中 this 的指向【忘了题目】
可以使用 new 一个箭头函数么?
箭头函数、没有 prototype
、没有自己的 this
指向、不可以使用 arguments
、自然不可以new
箭头函数和普通函数有什么区别
- 语法更加简洁、清晰
- 箭头函数不会创建自己的
this
- 箭头函数继承而来的
this
指向永远不变 - .call()/.apply()/.bind() 无法改变箭头函数中
this
的指向 - 箭头函数没有原型
prototype
- 箭头函数不能作为构造函数使用,不能用
new
- 箭头函数没有自己的
arguments
参考:ES6 - 箭头函数、箭头函数与普通函数的区别[4]
new 的实现
代码语言:javascript复制function create (ctr) {
// 创建一个空对象
let obj = new Object()
// 获取构造函数
let Con = [].shift.call(arguments)
// 将对象(实例)的 __proto__ 和构造函数的 prototype 绑定
obj.__proto__ = Con.prototype
// 绑定this,以及参数
let result = Con.apply(obj, arguments);
// 确保返回的是对象
return typeof result === 'object'? result : obj;
}
说说 instanceof 的原理
instanceof
主要的作用就是判断一个实例是否属于某种类型,其原理的实现类似如下:
function new_instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__
}
}
其实 instanceof
主要的实现原理就是只要右边变量的 prototype
在左边变量的原型链上即可。因此,instanceof
在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype
,如果查找失败,则会返回 false
,告诉我们左边变量并非是右边变量的实例
参考:浅谈 instanceof 和 typeof 的实现原理[5]
cookie、localStorage 和 sessionStorage 的区别
说下 HTTP 缓存
HTTPS 的握手、TLS 握手、对称加密和非对称加密
CSRF
跨站点请求伪造,其原理是攻击者构造网站后台某个功能接口的请求地址,诱导用户去点击或者用特殊方法让该请求地址自动加载。用户在登录状态下这个请求被服务端接收后会被误以为是用户合法的操作。对于 GET
形式的接口地址可轻易被攻击,对于 POST
形式的接口地址也不是百分百安全,攻击者可诱导用户进入带 Form
表单可用 POST
方式提交参数的页面
参考:「每日一题」CSRF 是什么?[6]
XMLHTTPRequest 设置哪个值自动带上 cookie
代码语言:javascript复制xhr.withCredentials = true;
同源策略以及跨域解决方案
强缓存下的返回的状态码是?协商缓存呢?
强缓存:200 OK (from memory/disk cache) 协商缓存:200 和 304
具体流程见下图:
参考:区分http请求状态码来理解缓存(协商缓存和强制缓存)[7]
输入 URL 之后发生了什么
script 标题中的 defer 和 async
cookie HTTP-only 、secure
如果一个 cookie
被设置了 Secure=true
,那么这个 cookie
只能用 https
协议发送给服务器,用 http
协议是不发送的
设置了 http-only
不能通过 JS
访问 cookie
,减少 XSS
攻击
e.target 和 e.currentTarget 的区别
e.target
指向触发事件监听的对象e.currentTarget
指向添加监听事件的对象
参考:e.target与e.currentTarget的区别[8]
Watch 和 computed 的区别
computed
不支持异步操作,当computed
内有异步操作时无效,无法监听数据的变化- 计算结果会被缓存,
computed
的值在getter
执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed
的值时才会重新调用对应的getter
来计算
参考:Vue的computed和 watched的区别[9]
Vue 的生命周期,以及哪个生命周期可以拿到 DOM
Vue 的 Mixin,created 和 data 中的值合并策略
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
- 比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先
- 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用
详见官方文档[10]
Vue
props
当前组件接收到的 props
对象。Vue 实例代理了对其 props
对象属性的访问。
attr 包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="
详细的,之前写过一篇文章:【Vue进阶】——如何实现组件属性透传?[11]
Vue 的双向数据绑定是怎样实现的?
Vue 的 key 值有什么用?
可以看我的另外一篇文章:深入浅出 Vue 中的 key 值[12]
Vue 数组的方法你知道是怎么实现的么?
Vue 的通信方式
CSS 居中
display:none; 和 visibility: hidden 的区别
有哪些可以常见的性能优化的点
HTTP2 相比于 HTTP1 多了哪些东西
- 多路复用
- 二进制传输。HTTP/2 中所有加强性能的核心点在于此。在之前的 HTTP 版本中,我们是通过文本的方式传输数据。在 HTTP/2 中引入了新的编码机制,所有传输的数据都会被分割,并采用二进制格式编码。
- 服务端 Push
- Header 压缩。在 HTTP/1 中,我们使用文本的形式传输 header,在 header 携带 cookie 的情况下,可能每次都需要重复传输几百到几千的字节。在 HTTP /2 中,**使用了 HPACK 压缩格式对传输的 header 进行编码,减少了 header 的大小。**并在两端维护了索引表,用于记录出现过的 header ,后面在传输过程中就可以传输已经记录过的 header 的键名,对端收到数据后就可以通过键名找到对应的值。
复杂请求和简单请求
只要同时满足以下两大条件,就属于简单请求
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
参考:跨域资源共享 CORS 详解[13]
- 编程题
- 拍平数组
- 深复制
/**
* 深复制实现
*/
function clone(data) {
let result = {};
function isObject(data) {
if (
(typeof data === "object" || typeof data === "function") &&
data !== null
) {
return true
} else {
return false
}
}
if (Array.isArray(data)) {
result = [...data];
}
for (let key in data) {
if (data.hasOwnProperty(key)) {
if (isObject(data[key])) {
result[key] = clone(data[key]);
} else {
result[key] = data[key];
}
}
}
return result;
}
- 有什么想问我的么?
二面
Vue-router 原理
怎么去捕获 await、async 中的错误
常见的异步方案,以及 Promise 的一道题
代码语言:javascript复制new Promise(function(resolve,reject){
resolve(Promise.reject())
}).then(function () {
console.log(1)
}).catch(function() {
console.log(2)
})
答案是 2
参考:Promise.resolve()[14]
HTTP 请求的方面进行优化
cookie 有哪些属性?
之前总结过 Cookie
的一篇文章: 前端须知的 Cookie 知识小结[15]
HTTP 的options 请求方法【
HTTP
的OPTIONS
方法 用于获取目的资源所支持的通信选项。客户端可以对特定的 URL 使用OPTIONS
方法,也可以对整站(通过将 URL 设置为“*”)使用该方法。- 检测服务器所支持的请求方法。可以使用
OPTIONS
方法对服务器发起请求,以检测服务器支持哪些HTTP
方法:curl -X OPTIONS http://example.org -i
- CORS 中的预检请求。在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求,以检测实际请求是否可以被服务器所接受。预检请求报文中的 `Access-Control-Request-Method`[16] 首部字段告知服务器实际请求所使用的 HTTP 方法;`Access-Control-Request-Headers`[17] 首部字段告知服务器实际请求所携带的自定义首部字段。服务器基于从预检请求获得的信息来判断,是否接受接下来的实际请求
- 检测服务器所支持的请求方法。可以使用
参考:OPTIONS[18]
50 个 SVG 图片请求你怎么进行优化
webpack 的性能优化
HTTP 请求怎么缓存
- 使用
server Worker
追问:如果不用 server Worker,你会采用什么方案?因为 SW 本身就是有兼容性问题的?
- 使用
localStorage
追问:如果不用 localStorage(因为容量有所限制),那你会用什么去实现呢?
- 回答了
indexDB
实现以下的 cache 方案(编程题)
实际上就是利用闭包和高阶函数实现函数的缓存:
以下是我的实现
代码语言:javascript复制
f1('abc', 123, {b:3}); // 10, 1000s
f1('abc', 123, {b:3}); // 10, 1000s
function cache(f) {
let objCache = {}
return function () {
let curArgs = ''
// 这里使用深复制会好点
for (let i = 0; i < arguments.length; i ) {
if (Array.isArray(arguments[i])) {
curArgs = arguments[i].join(',')
} else if (typeof arguments[i] === 'object') {
curArgs = JSON.stringify(arguments[i])
} else {
curArgs = arguments[i]
}
}
// curArgs
if (curArgs) {
return objCache[curArgs]
} else {
objCache[curArgs] = f(curArgs)
return objCache[curArgs]
}
}
}
f2 = cache(f1);
f2('abc', 123, {b:3}); // 10, 1000s
f2('abc', 123, {b:3}); // 10, 0s
其他实现:参考:JS如何实现函数缓存[19]
代码语言:javascript复制 const memorize = function(fn) {
const cache = {} // 存储缓存数据的对象
return function(...args) { // 这里用到数组的扩展运算符
const _args = JSON.stringify(args) // 将参数作为cache的key
return cache[_args] || (cache[_args] = fn.apply(fn, args)) // 如果已经缓存过,直接取值。否则重新计算并且缓存
}
}
const add = function(a, b) {
console.log('开始缓存')
return a b
}
const adder = memorize(add)
有用过 Vuex 么
你为什么会想到离职呢?
描述一下你自己的优缺点,一两个词
你有什么需要问我的么?
供应链
一面
ES6 你用了哪些
箭头函数和普通函数的区别
说下事件循环,微任务总是在宏任务之前执行么?
说下浏览器的渲染机制
对 React 了解,Vue 和 React 的最大区别, Vue 的双向数据绑定的实现
对现在哪些技术有了解【比如 server Worker】
cookie 的 samesite 最新的默认值是 Lau
CDN 了解过么【内容分发网络】
- 参考:webpack使用HtmlWebpackPlugin进行cdn配置[20]
做题(有很多题目忘了)
- 以下执行顺序
new Promise((resolve,reject) => {
console.log(1)
resolve()
console.log(2)
}).then(() => {
console.log(3)
})
- 下面的输出
(function () {
const a = b =1;
// 这里我理解相当于
// b = 1,const a = b
})()
console.log(typeof a) // undefined
console.log(typeof b) // number
参考:由ES规范学JavaScript(二):深入理解“连等赋值”问题[21]
编程题
题目和答案见为什么我认为数据结构与算法对前端开发很重要?[22]
二面
说下你的项目流程
有哪些技术难点
平时是怎么去学习的?
Vue 和 React 有哪些不同?
如何做到按需加载
关于后台表单 schema 相关
前端安全这一块你有了解多少?
跨域【解释跨域、如何解决】
看一道题,最后的输出是多少,时间复杂度是多少?
代码语言:javascript复制function my_print(n)
{
for (var i = 0; i < n; i ) {
console.log("-n");
my_print(n - 1);
}
}
my_print(3);
my_print(n);
代码语言:javascript复制f(n) = n (1 f(n-1))
= n n *(n-1) n * (n-1) *(n-2) ... n! = O(n!)
总结
整体的面试体验非常好,供应链时候的面试是参加了专场,当天面完了技术一二面以及 HR
面,流程算是非常快了。笔者现已入职 Shopee
供应链部门,这边的 Leader
和同事们很 Nice
的(年轻有活力的团队)。技术上我们鼓励分享,目前主要技术栈有 Vue
和 React
看了一下,我们现在还有在招人,有想要内推的,可以将你的简历命名"岗位名称_名字_工作地点"发送到我的邮箱:15521091584@163.com
具体内推见详情[23]
一般工作日晚上才处理邮件,如回复慢,请见谅
参考资料
[1]
【面试说】一年半前端 Bigo 一二三 面: https://juejin.im/post/6880028535101227021
[2]
【前端进阶】深入浅出浏览器事件循环【内附练习题】: https://juejin.im/post/6880419772127772679/#heading-1
[3]
「前端进阶」JS中的栈内存堆内存: https://juejin.im/post/6844903873992196110#heading-0
[4]
ES6 - 箭头函数、箭头函数与普通函数的区别: https://juejin.im/post/5c979300e51d456f49110bf0
[5]
浅谈 instanceof 和 typeof 的实现原理: https://juejin.im/post/6844903613584654344#heading-0
[6]
「每日一题」CSRF 是什么?: https://zhuanlan.zhihu.com/p/22521378
[7]
区分http请求状态码来理解缓存(协商缓存和强制缓存): https://www.cnblogs.com/fangsmile/p/13072940.html
[8]
e.target与e.currentTarget的区别: https://www.jianshu.com/p/1dd668ccc97a
[9]
Vue的computed和 watched的区别: https://juejin.im/post/5da7d371f265da5b7d691e3a
[10]
官方文档: https://cn.vuejs.org/v2/guide/mixins.html
[11]
【Vue进阶】——如何实现组件属性透传?: https://juejin.im/post/6865451649817640968
[12]
深入浅出 Vue 中的 key 值: https://juejin.im/post/6844903865930743815
[13]
跨域资源共享 CORS 详解: https://www.ruanyifeng.com/blog/2016/04/cors.html
[14]
Promise.resolve(): https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
[15]
前端须知的 Cookie 知识小结: https://juejin.im/post/6844903841909964813
[16]
The compatibility table in this page is generated from structured data. If you'd like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Request-Method
[17]
请求头 Access-Control-Request-Headers 出现于 preflight request (预检请求)中,用于通知服务器在真正的请求中会采用哪些请求头。: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Request-Headers
[18]
OPTIONS: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/OPTIONS
[19]
JS如何实现函数缓存: https://blog.csdn.net/weixin_30925411/article/details/100090840
[20]
webpack使用HtmlWebpackPlugin进行cdn配置: https://www.jianshu.com/p/9248db0349fb
[21]
由ES规范学JavaScript(二):深入理解“连等赋值”问题: https://segmentfault.com/a/1190000004224719
[22]
为什么我认为数据结构与算法对前端开发很重要?: https://github.com/LeuisKen/leuisken.github.io/issues/2
[23]
详情: https://juejin.im/pin/6844910552704090119