本文收录于 GitHub 日问: DailyQuestion,内含大厂内推机会、面经大全及若干面试题,每天学习五分钟,一年进入大厂中。
- 大厂面经大全
- 大厂内推
01 什么是防抖和节流,他们的利用场景有哪些
在 Issue 中交换与探讨: 01 什么是防抖和节流,他们的利用场景有哪些
防抖 (debounce)
防抖,顾名思义,避免抖动,免得把一次事件误认为屡次,敲键盘就是一个每天都会接触到的防抖操作。
想要理解一个概念,必先理解概念所利用的场景。在 JS 这个世界中,有哪些防抖的场景呢
- 登录、发短信等按钮防止用户点击太快,以致于发送了屡次申请,须要防抖
- 调整浏览器窗口大小时,resize 次数过于频繁,造成计算过多,此时须要一次到位,就用到了防抖
- 文本编辑器实时保留,当无任何更改操作一秒后进行保留
代码如下,能够看进去 防抖重在清零 clearTimeout(timer)
function debounce (f, wait) {
let timer
return (...args) => {clearTimeout(timer)
timer = setTimeout(() => {f(...args)
}, wait)
}
}
节流 (throttle)
节流,顾名思义,管制水的流量。管制事件产生的频率,如管制为 1s 产生一次,甚至 1 分钟产生一次。与服务端 (server) 及网关 (gateway) 管制的限流 (Rate Limit) 相似。
-
scroll
事件,每隔一秒计算一次地位信息等 - 浏览器播放事件,每个一秒计算一次进度信息等
- input 框实时搜寻并发送申请展现下拉列表,每隔一秒发送一次申请 (也可做防抖)
代码如下,能够看进去 节流重在加锁 timer=timeout
function throttle (f, wait) {
let timer
return (...args) => {if (timer) {return}
timer = setTimeout(() => {f(...args)
timer = null
}, wait)
}
}
总结 (简要答案)
- 防抖:避免抖动,单位工夫内事件触发会被重置,防止事件被误伤触发屡次。代码实现重在清零
clearTimeout
。防抖能够比作等电梯,只有有一个人进来,就须要再等一会儿。业务场景有防止登录按钮屡次点击的反复提交。 - 节流:管制流量,单位工夫内事件只能触发一次,与服务器端的限流 (Rate Limit) 相似。代码实现重在开锁关锁
timer=timeout; timer=null
。节流能够比作过红绿灯,每等一个红灯工夫就能够过一批。
02 在前端开发中,如何获取浏览器的惟一标识
<blockquote> 更多形容: 如何获取浏览器的惟一标识,原理是什么 </blockquote>
在 Issue 中交换与探讨: 02 在前端开发中,如何获取浏览器的惟一标识
因为不同的零碎显卡绘制 canvas
时渲染参数、抗锯齿等算法不同,因而绘制成图片数据的 CRC
校验也不一样。
function getCanvasFp () {const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
ctx.font = '14px Arial'
ctx.fillStyle = '#ccc'
ctx.fillText('hello, shanyue', 2, 2)
return canvas.toDataURL('image/jpeg')
}
因而依据 canvas
能够获取浏览器指纹信息。
- 绘制
canvas
,获取base64
的 dataurl - 对 dataurl 这个字符串进行
md5
摘要计算,失去指纹信息
然而对于常见的需要就有成熟的解决方案,若在生产环境应用,能够应用以下库
- fingerprintjs2
它根据以下信息,获取到浏览器指纹信息,而这些信息,则成为 component
canvas
webgl
UserAgent
AudioContext
- 对旧式 API 的反对水平等
requestIdleCallback(function () {Fingerprint2.get((components) => {const values = components.map((component) => component.value)
const fp = Fingerprint2.x64hash128(values.join(''), 31)
})
})
在 fingerprintjs2
中,对于 component
也有分类
-
browser independent component:有些
component
同一设施跨浏览器也能够失去雷同的值,有些独立浏览器,失去不同的值 -
stable component: 有些
component
刷新后值就会发生变化,称为不稳固组件
在理论业务中,可依据业务抉择适合的组件
const options = {excludes: {userAgent: true, language: true}
}
简答
依据 canvas
能够获取浏览器指纹信息
- 绘制
canvas
,获取base64
的 dataurl - 对 dataurl 这个字符串进行
md5
摘要计算,失去指纹信息
若在生产环境应用,能够应用 fingerprintjs2,依据业务需要,如单设施是否可跨浏览器,以此抉择适合的 component
03 在服务端利用中如何取得客户端 IP
在 Issue 中交换与探讨: 03 在服务端利用中如何取得客户端 IP
如果有 x-forwarded-for
的申请头,则取其中的第一个 IP,否则取建设连贯 socket 的 remoteAddr。
而 x-forwarded-for
根本已成为了基于 proxy 的规范 HTTP 头,格局如下,可见第一个 IP 代表其实在的 IP,能够参考 MDN X-Forwarded-For
X-Forwarded-For: 203.0.113.195, 70.41.3.18, 150.172.238.178
X-Forwarded-For: <client>, <proxy1>, <proxy2>
以下是 koa
获取 IP 的办法
get ips() {
const proxy = this.app.proxy;
const val = this.get(this.app.proxyIpHeader);
let ips = proxy && val
? val.split(/\s*,\s*/)
: [];
if (this.app.maxIpsCount > 0) {ips = ips.slice(-this.app.maxIpsCount);
}
return ips;
},
get ip() {if (!this[IP]) {this[IP] = this.ips[0] || this.socket.remoteAddress || '';
}
return this[IP];
},
参见源码: https://github.com/koajs/koa/…
04 js 如何全副代替一个子串为另一个子串
<blockquote> 更多形容: 假如有一个字符串 hello. hello. hello.
须要替换为 AAA
,即把 hello.
替换为 A
</blockquote>
在 Issue 中交换与探讨: 04 js 如何全副代替一个子串为另一个子串
如果须要全量替换字符串,能够应用 String.prototype.replace(re, replacer)
,其中正则表达式须要开启 global
flag
const s = 'foo foo foo'
s.replce(/foo/g, 'bar')
那如题中,是否能够应用正则表达式来代替子串
答:不能够,因为应用子串构建正则时,有可能有特殊字符,就有可能呈现问题,如下
// 期待后果: 'AhelloX hello3'
> 'hello. helloX hello3'.replace(new RegExp('hello.', 'g'), 'A')
< "AAA"
而在 javascript
中替换子串只能应用一种奇妙的方法:str.split('foo').join('bar')
> 'hello. hello. hello.'.split('hello.').join('A')
< "AAA"
真是一个巧 (笨) 妙(拙)的方法啊!!!!!大略 TC39 也意识到了一个问题,于是出了一个新的 API,在 ESNext
中
String.prototype.replaceAll()
'aabbcc'.replaceAll('b', '.');
// 'aa..cc'
具体文档在 String.prototype.replaceAll
总结(及间接答案)
两种方法
str.split('foo').join('bar')
-
str.replaceAll('foo', 'bar')
,在ESNext
中,目前支持性不好
05 如何获取一个过程的内存并监控
<blockquote> 更多形容: 在编写脚本时,有时会呈现内存过大产生 OOM 的事件,那咱们如何得悉某个过程的内存?另外又如何监控它 </blockquote>
在 Issue 中交换与探讨: 05 如何获取一个过程的内存并监控
通过 ps
能够获知一个过程所占用的内存
$ ps -O rss -p 3506
PID RSS S TTY TIME COMMAND
3506 6984 S pts/1 00:00:00 vim
如果要监控内存,必定应用对过程万能的命令 pidstat
(PS: 这名字一听就晓得是干嘛的)
## -r 显示内存信息
## -p 指定 pid
## 1: 每个一秒打印一次
$ pidstat -r -p 3506 1
Linux 3.10.0-957.21.3.el7.x86_64 (shanyue) 11/04/19 _x86_64_ (2 CPU)
20:47:35 UID PID minflt/s majflt/s VSZ RSS %MEM Command
20:47:36 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:37 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:38 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:39 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:40 0 3506 0.00 0.00 139940 6984 0.18 vim
20:47:41 0 3506 0.00 0.00 139940 6984 0.18 vim
pidstat
是属于 sysstat
下的 linux 性能工具,但在 mac 中,如何定位内存的变动?此时能够应用万能的 top/htop
$ htop -p 31796
总结
简而言之,有以下三个命令
pidstat -r
htop/top -p
ps -O rss -p
对于更多指标的监控能够参考我的文章: linux 各项监控指标小记
06 CORS 如果须要指定多个域名怎么办
在 Issue 中交换与探讨: 06 CORS 如果须要指定多个域名怎么办
CORS
通过管制 Access-Control-Allow-Origin
管制哪些域名能够共享资源,取值如下
Access-Control-Allow-Origin: <origin> | *
其中 *
代表所有域名,origin
代表指定特定域名,那如何设置多个域名了?
此时须要通过代码实现,依据申请头中的 Origin
来设置响应头 Access-Control-Allow-Origin
,那 Origin 又是什么货色?
申请头: Origin
并不是所有申请都会主动带上 Origin
,在浏览器中带 Origin
的逻辑如下
- 如果存在跨域,则带上
Origin
,值为以后域名 - 如果不存在跨域,则不带
Origin
逻辑理分明后,对于服务器中对于 Access-Control-Allow-Origin
设置多域名的逻辑也很清晰了
- 如果申请头不带有
Origin
,证实未跨域,则不作任何解决 - 如果申请头带有
Origin
,证实跨域,依据Origin
设置相应的Access-Control-Allow-Origin: <Origin>
应用伪代码实现如下:
// 获取 Origin 申请头
const requestOrigin = ctx.get('Origin');
// 如果没有,则跳过
if (!requestOrigin) {return await next();
}
// 设置响应头
ctx.set('Access-Control-Allow-Origin', requestOrigin)
Vary: Origin
此时能够给多个域名管制 CORS,但此时假如有两个域名拜访 static.shanyue.tech
的跨域资源
-
foo.shanyue.tech
,响应头中返回Access-Control-Allow-Origin: foo.shanyue.tech
-
bar.shanyue.tech
,响应头中返回Access-Control-Allow-Origin: bar.shanyue.tech
看起来一切正常,但如果两头有缓存怎么办?
-
foo.shanyue.tech
,响应头中返回Access-Control-Allow-Origin: foo.shanyue.tech
,被 CDN 缓存 bar.shanyue.tech
,来由缓存,响应头中返回Access-Control-Allow-Origin: foo.shanyue.tech
,跨域呈现问题
此时,Vary: Origin
就上场了,代表为不同的 Origin
缓存不同的资源
总结 (简要答案)
CORS 如何指定多个域名?
依据申请头中的 Origin
来设置响应头 Access-Control-Allow-Origin
,思路如下
- 总是设置
Vary: Origin
,防止 CDN 缓存毁坏 CORS 配置 - 如果申请头不带有
Origin
,证实未跨域,则不作任何解决 - 如果申请头带有
Origin
,证实浏览器拜访跨域,依据Origin
设置相应的Access-Control-Allow-Origin: <Origin>
应用伪代码实现如下
// 获取 Origin 申请头
const requestOrigin = ctx.get('Origin');
ctx.set('Vary', 'Origin')
// 如果没有,则跳过
if (!requestOrigin) {return await next();
}
// 设置响应头
ctx.set('Access-Control-Allow-Origin', requestOrigin)
相干问题:如何防止 CDN 为 PC 端缓存挪动端页面
07 既然 cors 配置能够做跨域管制,那能够避免 CSRF 攻打吗
在 Issue 中交换与探讨: 07 既然 cors 配置能够做跨域管制,那能够避免 CSRF 攻打吗
对 CORS 一点用也没有
-
form
提交不通过CORS
检测,你能够在本地进行测试 - 即便通过
xhr
及fetch
进行提交被 CORS 拦住,然而对于简略申请而言,申请仍被发送,已造成了攻打
08 如何防止 CDN 为 PC 端缓存挪动端页面
在 Issue 中交换与探讨: 08 如何防止 CDN 为 PC 端缓存挪动端页面
如果 PC 端和挪动端是一套代码则不会呈现这个问题。这个问题呈现在 PC 端和挪动端是两套代码,却共用一个域名。
应用 nginx
配置如下,依据 UA 判断是否挪动端,而走不同的逻辑 (判断 UA 是否挪动端容易出问题)
location / {
// 默认 PC 端
root /usr/local/website/web;
# 判断 UA,拜访挪动端
if ($http_user_agent ~* "(Android|webOS|iPhone|iPad|BlackBerry)" ){root /usr/local/website/mobile;}
index index.html index.htm;
}
解决方案通常应用 Vary
响应头,来管制 CDN 对不同申请头的缓存。
此处能够应用 Vary: User-Agent
,代表如果 User-Agent 不一样,则从新发动申请,而非从缓存中读取页面
Vary: User-Agent
当然,User-Agent
切实过多,此时缓存生效就会过多。
简答
应用 Vary: User-Agent
,依据 UA 进行缓存。
Vary: User-Agent
但最好不要呈现这种状况,PC 端和挪动端如果是两套代码,倡议用两个域名,理由如下
-
nginx
判断是否挪动端容易出错 - 对缓存不敌对
09 如何实现表格单双行条纹款式
在 Issue 中交换与探讨: 09 如何实现表格单双行条纹款式
通过 css3
中伪类 :nth-child
来实现。其中 :nth-child(an+b)
匹配下标 {an + b; n = 0, 1, 2, ...}
且后果为整数的子元素
-
nth-child(2n)
/nth-child(even)
: 双行款式 -
nth-child(2n+1)
/nth-child(odd)
: 单行款式
其中 tr
在表格中代表行,实现表格中单双行款式就很简略了:
tr:nth-child(2n) {background-color: red;}
tr:nth-child(2n+1) {background-color: blue;}
同理:
- 如何匹配最前三个子元素:
:nth-child(-n+3)
- 如何匹配最初三个子元素:
:nth-last-child(-n+3)
10 简述下 css specificity
在 Issue 中交换与探讨: 10 简述下 css specificity
css specificity
即 css 中对于选择器的权重,以下三种类型的选择器顺次降落
-
id
选择器,如#app
-
class
、attribute
与pseudo-classes
选择器,如.header
、[type="radio"]
与:hover
-
type
标签选择器和伪元素选择器,如h1
、p
和::before
其中通配符选择器 *
,组合选择器 + ~ >
,否定伪类选择器 :not()
对优先级无影响
另有内联款式 <div class="foo" style="color: red;"></div>
及 !important
(最高) 具备更高的权重
:not
的优先级影响 – codepen 能够看出:not
对选择器的优先级无任何影响
11 node 中 module.exports 与 exports 有什么区别
在 Issue 中交换与探讨: 11 node 中 module.exports 与 exports 有什么区别
一句话:exports
是 module.exports
的援用,如果 exports
没有重赋值,则二者没有任何区别
相似如下所示
const exports = module.exports
那如下后果会如何导出?
module.exports = 100
exports = 3
很显然会导出 100,毕竟 exports
进行了重赋值。
那在 node 源码中如何实现的呢? 从源码里能够看出 exports 的本质
详见源码: https://github.com/nodejs/nod…,能够看出合乎猜测
家喻户晓,node 中所有的模块代码都被包裹在这个函数中
(function(exports, require, module, __filename, __dirname) {exports.a = 3});
而以下源码指出,exports
是如何得来
const dirname = path.dirname(filename);
const require = makeRequireFunction(this, redirects);
let result;
// 从这里能够看进去 exports 的本质
const exports = this.exports;
const thisValue = exports;
const module = this;
if (requireDepth === 0) statCache = new Map();
if (inspectorWrapper) {
result = inspectorWrapper(compiledWrapper, thisValue, exports,
require, module, filename, dirname);
} else {
// 这里是模块包装函数
result = compiledWrapper.call(thisValue, exports, require, module,
filename, dirname);
}
12 如何获取以后零碎中的在线用户数 (并发用户数)
<blockquote> 更多形容: 一些 SaaS 零碎基于 Pricing 的思考,会限度团队人数及同时在线数,如何实现 </blockquote>
在 Issue 中交换与探讨: 12 如何获取以后零碎中的在线用户数 (并发用户数)
一些 SaaS 零碎基于定价策略的思考,会限度团队人数及同时在线数,如何实现?
通过 redis
的 zset
可实现并发用户数。
当一个用户申请任何接口时,实现一个 middleware,解决以下逻辑
// 当一个用户拜访任何接口时,对该用户 Id,写入 zset
await redis.zadd(`Organization:${organizationId}:concurrent`, Date.now(), `User:${userId}`)
// 查问以后机构的并发数
// 通过查问一分钟内的沉闷用户来确认并发数,如果超过则抛出特定异样
const activeUsers = await redis.zrangebyscore(`Organization:${organizationId}:concurrent`, Date.now() - 1000 * 60, Date.now())
// 查出并发数
const count = activeUsers.length
// 删掉过期的用户
await redis.zrembyscore(`Organization:${organizationId}:concurrent`, Date.now() - 1000 * 60, Date.now())
总结
- 每当用户拜访服务时,把该用户的 ID 写入优先级队列,权重为以后工夫
- 依据权重 (即工夫) 计算一分钟内该机构的用户数
- 删掉一分钟以上过期的用户
13 如何把 json 数据转化为 demo.json 并下载文件
在 Issue 中交换与探讨: 13 如何把 json 数据转化为 demo.json 并下载文件
json 视为字符串,能够利用 DataURL
进行下载
Text -> DataURL
除了应用 DataURL,还能够转化为 Object URL 进行下载
Text -> Blob -> Object URL
能够把以下代码间接粘贴到控制台下载文件
function download (url, name) {const a = document.createElement('a')
a.download = name
a.rel = 'noopener'
a.href = url
// 触发模仿点击
a.dispatchEvent(new MouseEvent('click'))
// 或者 a.click()}
const json = {
a: 3,
b: 4,
c: 5
}
const str = JSON.stringify(json, null, 2)
// 计划一:Text -> DataURL
const dataUrl = `data:,${str}`
download(dataUrl, 'demo.json')
// 计划二:Text -> Blob -> ObjectURL
const url = URL.createObjectURL(new Blob(str.split('')))
download(url, 'demo1.json')
总结
- 模仿下载,能够通过新建一个
<a href="url" download><a>
标签并设置url
及download
属性来下载 - 能够通过把
json
转化为dataurl
来结构 URL - 能够通过把
json
转换为Blob
再转化为ObjectURL
来结构 URL
14 在浏览器中如何监听剪切板中内容
在 Issue 中交换与探讨: 14 在浏览器中如何监听剪切板中内容
通过 Clipboard API
能够获取剪切板中内容,但须要获取到 clipboard-read
的权限,以下是对于读取剪贴板内容的代码:
// 是否可能有读取剪贴板的权限
// result.state == "granted" || result.state == "prompt"
const result = await navigator.permissions.query({name: "clipboard-read"})
// 获取剪贴板内容
const text = await navigator.clipboard.readText()
注: 该办法在
devtools
中不失效
相干问题:【Q019】如何实现选中复制的性能
关注我
我是山月,正致力于 每天用五分钟可能看完的简短答案答复一个大厂高频面试题。
欢送关注公众号【互联网大厂招聘】,定时推送大厂内推信息及面试题简答,每天学习五分钟,半年进入大厂中