前言
一轉(zhuǎn)眼又到了跳槽季,是時(shí)候跟大家分享一波面試題了,通常來(lái)說(shuō)大家在跳槽之前都會(huì)刷題,甚至說(shuō)從不刷題的多年開發(fā)經(jīng)驗(yàn)的大神在面試中可能干不過(guò)刷過(guò)各種題的面試者,導(dǎo)致了一頓內(nèi)卷,這也側(cè)面印證了看面試題的重要性。
我也看過(guò)很多的面試題文章,比較反感的是那種代碼片段巨長(zhǎng)
或者牽扯到的知識(shí)點(diǎn)巨廣
抑或是與各種復(fù)雜的數(shù)學(xué)公式相關(guān)的
還有就是過(guò)于底層
的那種題:像什么手寫Promise A+
規(guī)范、手撕紅黑樹
、用公式實(shí)現(xiàn)個(gè)什么Canvas xx特效
、瀏覽器是怎么實(shí)現(xiàn)xxx API
的、如果瀏覽器沒(méi)有提供這個(gè) API 的話要怎么去模仿實(shí)現(xiàn)該 API
,Vue3 的 diff 算法和 React 的 diff 算法有什么區(qū)別
、能不能手寫個(gè)他倆的結(jié)合版 diff 算法
、手寫個(gè) React 的時(shí)間切片
……
相信大部分人也和我一樣,每次看這種文章的時(shí)候,不是看著看著就沒(méi)耐心往下繼續(xù)看了、就是看到一半就忍不住翻到評(píng)論區(qū)看評(píng)論了,然后點(diǎn)贊、關(guān)注、收藏一鍵三連,收藏夾里都快積攢上千篇文章了,這種文章雖然技術(shù)含量很高,但是過(guò)于的枯燥乏味,抑或是牽扯到的知識(shí)點(diǎn)過(guò)廣,要是其中哪個(gè)知識(shí)點(diǎn)自己不太熟的話,后續(xù)的內(nèi)容也就都看不懂了。就像是上數(shù)學(xué)課一樣,剛開始沒(méi)認(rèn)真聽(tīng)講,落下了某個(gè)知識(shí)點(diǎn)沒(méi)聽(tīng)到,再回過(guò)神來(lái)的時(shí)候發(fā)現(xiàn)已經(jīng)聽(tīng)不懂了。
比如有一次看一篇文章是實(shí)現(xiàn)個(gè)什么非常炫酷的 Canvas
特效,看著看著突然冒出來(lái)了三角函數(shù),雖然中學(xué)的時(shí)候也都學(xué)過(guò)這些,但經(jīng)過(guò)這么多年后早就把什么 sin、cos、tan 這些符號(hào)的意思忘的差不多了,但也懶不想再打開瀏覽器一頓搜索一頓查,就繼續(xù)往下看吧,看著看著又出現(xiàn)個(gè)什么矩陣算法,大學(xué)的時(shí)候其實(shí)也學(xué)過(guò),總之看到最后實(shí)現(xiàn)出來(lái)的效果非常酷,但具體是怎么實(shí)現(xiàn)的心里也是云里霧里的。除非真的工作中要用到這個(gè),才會(huì)仔細(xì)看文章去鉆研,即使是工作中不會(huì)用到的同時(shí)還仔細(xì)鉆研了一番,通常很快也就會(huì)忘記。
而另外一種文章則是非常立竿見(jiàn)影的:那就是講述的知識(shí)點(diǎn)并不復(fù)雜,只是以前從未想過(guò)可以這樣用,相當(dāng)于是一種思路,抑或是自己以前不知道的一個(gè) API,用起來(lái)很方便。這種文章看著也不會(huì)特別的枯燥乏味、并且還看的津津有味的,感嘆:原來(lái)還可以這樣用啊!自己以前怎么就沒(méi)有想到呢?
這種文章看過(guò)了不會(huì)特別容易忘記、甚至在工作的過(guò)程中還會(huì)找機(jī)會(huì)去用一下試驗(yàn)試驗(yàn)。給大家舉幾個(gè)例子:
一定時(shí)間無(wú)操作時(shí)播放視頻
當(dāng)時(shí)處于剛剛?cè)胄械碾A段,經(jīng)驗(yàn)比較差,所以有些很普通的需求自己卻沒(méi)思路。當(dāng)時(shí)做的是 Electron 項(xiàng)目,放在陽(yáng)明古鎮(zhèn)的一面墻上展示,需求是當(dāng)用戶十分鐘都不操作界面的話就自動(dòng)播放陽(yáng)明古鎮(zhèn)的宣傳視頻,當(dāng)時(shí)腦子就像是卡住了一樣:怎么才能知道用戶十分鐘都沒(méi)有操作呢?為此還專門去查找有沒(méi)有這樣的 API,后來(lái)看到一篇文章讓我大呼真妙!??
原理也超級(jí)簡(jiǎn)單,就是在頁(yè)面上設(shè)置一個(gè)十分鐘的變量:
let minute = 10
復(fù)制代碼
然后設(shè)置個(gè)定時(shí)器每分鐘 -1:
setInterval(() => {
minute--
if (minute <= 0) {
// 播放視頻
}
}, 1000 * 60)
復(fù)制代碼
當(dāng)有事件發(fā)生時(shí)就代表用戶在操作,需要還原變量:
window.addEventListener('click', () => minute = 10)
復(fù)制代碼
還可以監(jiān)聽(tīng)
mousemove
或者鍵盤等事件,但那個(gè)項(xiàng)目是觸摸大屏,沒(méi)有鼠標(biāo)或者鍵盤,所以監(jiān)聽(tīng)點(diǎn)擊事件就夠了
短短幾行代碼就解決了我的燃眉之急,當(dāng)然那時(shí)候也菜,這么簡(jiǎn)單的需求都沒(méi)想出來(lái),不過(guò)誰(shuí)還不是從小白一步步走上來(lái)的呢?正是靠著這些文章一步步擴(kuò)展了思路才會(huì)很快的進(jìn)步。
Vue 性能優(yōu)化
看了黃老師出品的 《揭秘 Vue.js 九個(gè)性能優(yōu)化技巧》
才知道原來(lái) computed
里面的函數(shù)是可以接收一個(gè) this
參數(shù)的:
computed: {
a () { return 1 },
b ({ a }) {
return a + 10
}
}
復(fù)制代碼
這樣就不會(huì)在組件刷新時(shí)重復(fù)獲取 getter
了,以前從來(lái)沒(méi)注意過(guò)這些。
純 CSS 實(shí)現(xiàn)拖拽效果
以前我們做拖拽的時(shí)候基本都會(huì)用 JS
去實(shí)現(xiàn),很麻煩,但看了閱文前端團(tuán)隊(duì)的《純 CSS 也能實(shí)現(xiàn)拖拽效果》令我佩服的五體投地:
在傳統(tǒng) web 中,頁(yè)面滾動(dòng)是一個(gè)很常見(jiàn)交互,操作上就是利用鼠標(biāo)滾輪或者直接拖動(dòng)滾動(dòng)條。但是,移動(dòng)端可不一樣,直接用手指拖動(dòng)頁(yè)面就可以滾動(dòng)了。通常頁(yè)面是要么垂直方向滾動(dòng),要么水平方向滾動(dòng),如果兩個(gè)方向都可以滾動(dòng)呢?例如:
.dragbox {
width: 300px;
height: 300px;
overflow: auto
}
.dragcon {
width: 500px;
height: 500px;
}
復(fù)制代碼
只需要內(nèi)部元素寬高都大于容器就實(shí)現(xiàn)兩個(gè)方向的滾動(dòng)了(記得設(shè)置overflow:auto),示意如下:
一般情況下,鼠標(biāo)滾輪只能同時(shí)滾動(dòng)一個(gè)方向(按住Shift可以滾動(dòng)另一方向),但是移動(dòng)端可以直接拖著內(nèi)容任意滾動(dòng),如下所示:
現(xiàn)在,在內(nèi)容中間添加一個(gè)元素,跟隨內(nèi)容區(qū)域一起滾動(dòng):
接下來(lái),把后面的文本隱藏起來(lái):
是不是有點(diǎn)拖拽的味道了?原理就是這么簡(jiǎn)單!
Vue3 的新語(yǔ)法
現(xiàn)在一搜 Vue3
出來(lái)的要不就是 Composition API
要么就是 新的響應(yīng)式原理
,這些東西講起來(lái)都比較復(fù)雜,而且大家都忽略了好多其他的點(diǎn),比如很多時(shí)候我們想要 CSS
也能是響應(yīng)式的,比如曾經(jīng)幻想過(guò)的語(yǔ)法:
<template>
<h1>{{ color }}</h1>
</template>
<script>
export default {
data () {
return {
color: 'red'
}
}
}
</script>
<style>
h1 {
color: this.color;
}
</style>
復(fù)制代碼
不過(guò)由于 CSS
和 JS
隸屬不同上下文,這一點(diǎn)很難做到,但自從看了這篇《Vue超好玩的新特性:在CSS中引入JS變量》才發(fā)現(xiàn)原來(lái)還可以這么寫:
<template>
<h1>{{ color }}</h1>
</template>
<script>
export default {
data () {
return {
color: 'yellow'
}
}
}
</script>
<style>
h1 {
color: v-bind(color)
}
</style>
復(fù)制代碼
當(dāng) this.color
發(fā)生變化時(shí),css
也會(huì)一同做出響應(yīng)。
還有就是《Vue超好玩的新特性:DOM傳送門》,這些小技巧能夠非常方便的提升我們的開發(fā)效率,但如今的 Vue3
相關(guān)文章卻很少有人提及到這些。
九宮格面試題
這種面試題代碼量不多,但卻甚少人能夠做對(duì),這篇《千萬(wàn)別小瞧九宮格 一道題就能讓候選人原形畢露!》給我們提供了很好的一個(gè)思路,因?yàn)樵谧鲞@種九宮格時(shí):
很多人以為只需要給每個(gè)格子加上一個(gè)邊框即可,而實(shí)際上如果這么做的話會(huì)變成下面這樣:
因?yàn)樵诮o每個(gè)盒子加入了邊框之后,相鄰的兩個(gè)邊框就會(huì)貼合在一起,肉眼看起來(lái)就是一個(gè)兩倍粗的邊框。而《千萬(wàn)別小瞧九宮格 一道題就能讓候選人原形畢露!》利用負(fù)邊距輕輕松松的就解決了這個(gè)難題:
你不知道的 CSS 負(fù)值
提到負(fù)邊距就讓人想起這篇《你所不知道的 CSS 負(fù)值技巧與細(xì)節(jié)》:
[圖片上傳中...(image-d30ceb-1615723259233-13)]
<template>
<div></div>
</template>
<style>
div {
width: 200px;
height: 200px;
outline: 20px solid #000;
outline-offset: -118px;
}
</style>
復(fù)制代碼
真的是沒(méi)想到這樣就能實(shí)現(xiàn)加號(hào)。
題目
說(shuō)了這么多有點(diǎn)跑題了,本意其實(shí)是想說(shuō)明:本篇文章的算法題就像上面列舉出來(lái)的文章那樣,代碼量不多、甚至還很簡(jiǎn)單,但重點(diǎn)就是考察你對(duì)技術(shù)的靈活運(yùn)用程度,你的思維能不能轉(zhuǎn)得過(guò)彎來(lái)。
當(dāng)然也不是說(shuō)那些代碼量很多很復(fù)雜的文章不好,其實(shí)那些文章技術(shù)含量都很高,但畢竟大部分人沒(méi)有心思那么仔細(xì)的鉆研各種復(fù)雜的算法,不過(guò)你要去的如果是字節(jié)跳動(dòng)
、百度
、阿里
、騰訊
這類大廠去面試的話,鉆研一下那些復(fù)雜的文章還是非常有必要的。
情景再現(xiàn)
面試那天我來(lái)到了一個(gè)看起來(lái)像是會(huì)議室的屋子里,面試官給了我?guī)讖埦碜雍鸵恢还P,讓我先寫,然后他就出去了。我還在想:沒(méi)人看著我難道就不怕我用手機(jī)搜索答案么?是不是有攝像頭然后通過(guò)屏幕來(lái)觀察我有沒(méi)有用手機(jī)搜索答案,進(jìn)而考察候選人的誠(chéng)實(shí)與否…
當(dāng)然我也沒(méi)有想用手機(jī)
卷子有點(diǎn)像是中學(xué)考試那樣:選擇題 + 填空題 + 大題
大題就是手寫代碼,其實(shí)挺煩這種的… 一方面寫大括號(hào)只能先寫一半,因?yàn)椴恢涝诖罄ㄌ?hào)里會(huì)寫多少行代碼,不像是編輯器里那樣,尾括號(hào)隨著行數(shù)的增加會(huì)自動(dòng)移動(dòng);另一方面是沒(méi)有控制臺(tái),自己也不知道自己寫的到底對(duì)不對(duì),只能通過(guò)直覺(jué)來(lái)判斷。
其中一道題目是:寫一個(gè)函數(shù),這個(gè)函數(shù)會(huì)返回一個(gè)數(shù)組,數(shù)組里面是 2 ~ 32 之間的隨機(jī)整數(shù)(不能重復(fù)),這個(gè)函數(shù)還可以傳入一個(gè)參數(shù),參數(shù)是幾,返回的數(shù)組長(zhǎng)度就是幾
就像這樣:
剛看到題目的時(shí)候還在想這有啥難的,寫唄!首先先來(lái)生成從 2 ~ 32 之間的隨機(jī)數(shù)…怎么生成 2 ~ 32 之間的隨機(jī)數(shù)呢?Math.random() * 32
,可是這是生成 0 ~ 32 的,有了!先生成 0 ~ 30 之間的隨機(jī)數(shù)然后再加上 2 不就得了:
const fn = num => {
let arr = []
for (let i = num; i-- > 0;) {
arr.push(Math.round(Math.random() * 30 + 2))
}
return arr
}
復(fù)制代碼
這樣寫帶來(lái)的問(wèn)題就是,隨機(jī)生成的數(shù)字會(huì)有重復(fù):
這時(shí)我想到了 ES6 的新增數(shù)據(jù)結(jié)構(gòu) Set,它里面是可以保證沒(méi)有重復(fù)值的,而且它還可以用 ...
操作符很方便的轉(zhuǎn)為數(shù)組,于是繼續(xù)寫:
const fn = num => {
let arr = []
for (let i = num; i-- > 0;) {
arr.push(Math.round(Math.random() * 30 + 2))
}
arr = [...new Set(arr)]
return arr
}
復(fù)制代碼
這樣寫雖然解決了重復(fù)值的問(wèn)題,但卻帶來(lái)了新的問(wèn)題:如果有幾個(gè)重復(fù)值數(shù)組的長(zhǎng)度就會(huì)少幾,就像這樣:
當(dāng)時(shí)我的思路是這樣的:假如 fn(10)
傳的參數(shù)是 10,如果最終出來(lái)的數(shù)組不為 10,那就用 10 減去數(shù)組的長(zhǎng)度,就是相差的位數(shù)了。比如 fn(10)
導(dǎo)致 arr.length = 8
,那么 10 - 8
就代表只需要再生成 2 個(gè)隨機(jī)數(shù)就可以了,但隨機(jī)的兩個(gè)數(shù)也可能會(huì)和現(xiàn)有的 8 位數(shù)組重合,所以我們要把隨機(jī)生成的兩位數(shù)連接到原來(lái)的 8 位數(shù)組中去,然后再用 Set
數(shù)據(jù)結(jié)構(gòu)去重,用 while
循環(huán)判斷,如果傳進(jìn)來(lái)的參數(shù) 10
減去數(shù)組長(zhǎng)度 arr.length
不等于 0
的話就證明依然還是有重復(fù)項(xiàng),那就繼續(xù)再生成隨機(jī)數(shù)重復(fù)剛才的步驟,直到生成 10 位所有數(shù)字都不重復(fù)的數(shù)組就會(huì)自動(dòng)跳出 while
循環(huán),然后返回這個(gè)數(shù)組:
const fn = num => {
let arr = []
for (let i = num; i-- > 0;) {
arr.push(Math.round(Math.random() * 30 + 2))
}
arr = [...new Set(arr)]
let len = arr.length
while (num - len > 0) {
arr = [...new Set(arr.concat(fn(num - len)))]
len = arr.length
}
return arr
}
復(fù)制代碼
運(yùn)行結(jié)果:
當(dāng)然我在筆試的過(guò)程中是看不到運(yùn)行結(jié)果的,這是我回到家之后憑借著印象寫出來(lái)的代碼,想試驗(yàn)一下寫的對(duì)不對(duì)。
二面加難度后的題
到了二面的時(shí)候(省略問(wèn)的其他問(wèn)題),面試官說(shuō)那道題雖然你做對(duì)了,但其實(shí)有點(diǎn)像是暴力破解的感覺(jué),效率很差。比如我在函數(shù)里傳入 30
,從 2 ~ 32
總共也就 30 個(gè)數(shù),你想想生成隨機(jī)數(shù)的這個(gè)方法會(huì)運(yùn)行多少次,假如足夠幸運(yùn),第一次運(yùn)行函數(shù)就生成了 29
位不同數(shù)字的數(shù)組,那么還差一位就齊了,你想想最后這一位重復(fù)的幾率有多大?是不是30分之29
?重復(fù)一次就要再運(yùn)行一遍、重復(fù)一次再運(yùn)行… 每次都要新建數(shù)組然后再新建 Set
再轉(zhuǎn)回?cái)?shù)組,開銷很大的,你有沒(méi)有什么想優(yōu)化的點(diǎn)?
此時(shí)我想的是不用 Set
來(lái)去重,想的是怎么復(fù)用原來(lái)的數(shù)組不讓它重新生成,每次只返回一個(gè)數(shù)然后遞歸?還是加入第二個(gè)參數(shù),傳入原來(lái)生成的數(shù)組?
我把我的想法說(shuō)給他聽(tīng)后,他并不滿意,覺(jué)得我沒(méi)有說(shuō)到他想要的點(diǎn)上,于是他給了我點(diǎn)提示:假設(shè)生成隨機(jī)數(shù)這個(gè)操作是特別費(fèi)時(shí)的一項(xiàng)操作,你有沒(méi)有辦法只讓他運(yùn)行傳進(jìn)來(lái)的參數(shù)那么多次?就好比 fn(10)
,能不能只讓 Math.random
這個(gè)函數(shù)只運(yùn)行 10
次?
當(dāng)時(shí)我一聽(tīng)頭都大了,這怎么可能呢?既然是在一定范圍內(nèi)生成隨機(jī)數(shù),那么肯定無(wú)可避免的會(huì)有重復(fù)項(xiàng),哪怕不用 Set
用別的方式去重,那也必須得運(yùn)行超過(guò) 10
次啊!不過(guò)他既然這么問(wèn)了就證明肯定有什么辦法能夠做到,于是我絞盡腦汁想啊想,最終還是鉆了牛角尖:認(rèn)為無(wú)論什么方法都無(wú)法避免生成重復(fù)項(xiàng),即使是足夠幸運(yùn)運(yùn)行一次就得到了想要的結(jié)果,那也不算是技術(shù)實(shí)現(xiàn)出來(lái)的,只能算是運(yùn)氣好,最后只好攤牌說(shuō)自己沒(méi)思路。
我本以為面試到這里就要結(jié)束了,沒(méi)想到他居然主動(dòng)跟我說(shuō)了一下這道題的解法,但當(dāng)時(shí)心里有些沮喪,他說(shuō)的那一大堆都沒(méi)聽(tīng)進(jìn)去,只記住了他說(shuō)要定義兩個(gè)數(shù)組,在坐地鐵回家的路上我一直在想:兩個(gè)數(shù)組… 兩個(gè)數(shù)組?
回到家打開電腦開始寫代碼,先把我原來(lái)的那個(gè)解法做個(gè)測(cè)試,看看性能到底有沒(méi)有他說(shuō)的那么差:
0.1 毫秒,也還行啊!可能是數(shù)字小了吧,如果是生成從 0 ~ 10000 之間的隨機(jī)數(shù)應(yīng)該就崩潰了吧?修改一下函數(shù):
const fn = num => {
let arr = []
for (let i = num; i-- > 0;) {
arr.push(Math.round(Math.random() * 10000))
}
arr = [...new Set(arr)]
let len = arr.length
while (num - len > 0) {
arr = [...new Set(arr.concat(fn(num - len)))]
len = arr.length
}
return arr
}
復(fù)制代碼
運(yùn)行結(jié)果:
這回確實(shí)明顯的感覺(jué)到卡頓了,兩秒多鐘才出來(lái)結(jié)果,在計(jì)算機(jī)運(yùn)算里兩千毫秒已經(jīng)算得上是天文數(shù)字了。那再來(lái)試試他說(shuō)的兩個(gè)數(shù)組:我的理解是先事先定義一個(gè)里面裝著從 2 ~ 32 范圍內(nèi)的所有整數(shù),然后再定義一個(gè)空數(shù)組用來(lái)存放結(jié)果,在數(shù)組的長(zhǎng)度(length)范圍內(nèi)隨機(jī)生成整數(shù),用這個(gè)生成出來(lái)的整數(shù)當(dāng)作下標(biāo)從那個(gè)數(shù)組中取出數(shù)字來(lái)放入空數(shù)組中,這樣即使生成出來(lái)的隨機(jī)數(shù)有重復(fù)項(xiàng)也沒(méi)有關(guān)系,因?yàn)檫@兩個(gè)數(shù)組不會(huì)有重復(fù)項(xiàng):
const fn = num => {
const allNums = Array.from({ length: 31 }, (_, i) => i + 2)
const result = []
for (let i = num; i-- > 0;) {
result.push(allNums.splice(Math.floor(Math.random() * allNums.length), 1)[0])
}
return result
}
復(fù)制代碼
這回再來(lái)試一下:
沒(méi)有任何毛病,那性能呢?來(lái)測(cè)一下:
確實(shí)是比以前快得多,再來(lái)試一下 0 ~ 10000 的隨機(jī)數(shù):
const fn = num => {
const allNums = Array.from({ length: 10001 }, (_, i) => i)
const result = []
for (let i = num; i-- > 0;) {
result.push(allNums.splice(Math.floor(Math.random() * allNums.length), 1)[0])
}
return result
}
復(fù)制代碼
運(yùn)行結(jié)果:
這回差距就特別明顯了:一個(gè)兩千三百多毫秒,能夠讓人明顯的感覺(jué)到卡頓、而另一個(gè)只需要七毫秒,人類的感覺(jué)就是按下回車就能夠出結(jié)果。
其實(shí)這個(gè)函數(shù)封裝的還不夠徹底,因?yàn)樯蓮膸椎綆椎碾S機(jī)數(shù)完全是寫死在函數(shù)內(nèi)部的,如果不想要生成從 2 ~ 32 的隨機(jī)數(shù)的話還需要去函數(shù)內(nèi)部改代碼,這明顯是不符合開放封閉原則的,并且用起來(lái)也不夠靈活,咱們來(lái)再次封裝一下,令其成為一個(gè)更加通用的函數(shù):
const fn = (len, from = 0, to = 100) => {
const allNums = Array.from({ length: to - from }, (_, i) => i + from)
const result = []
for (let i = len; i-- > 0;) {
result.push(allNums.splice(Math.floor(Math.random() * allNums.length), 1)[0])
}
return result
}
復(fù)制代碼
運(yùn)行結(jié)果:
可以看到非常完美的運(yùn)行出了我們想要的結(jié)果,不過(guò)生成十位從 2 到 12 的數(shù)字為什么沒(méi)有 12 這個(gè)數(shù)呢?原來(lái)咱們封裝的這個(gè)函數(shù)是為了符合程序中左閉右開
的潛規(guī)則,細(xì)心的同學(xué)應(yīng)該早就發(fā)現(xiàn)了在程序中左閉右開
的這么一種現(xiàn)象,比方說(shuō)我們用 substring
方法來(lái)舉例:
'0123456'.substring(1, 5)
復(fù)制代碼
運(yùn)行結(jié)果:
[圖片上傳中...(image-62f0bf-1615723259232-2)]
可以看到咱們傳入的參數(shù)是從 1 到 5,但是最后的結(jié)果卻包含 1 而不包括 5。咱們中學(xué)的時(shí)候就學(xué)過(guò)開區(qū)間閉區(qū)間的這么一種概念,開區(qū)間指的是不包括
這個(gè)數(shù),而閉區(qū)間是包括
。想當(dāng)年數(shù)學(xué)老師就一直反復(fù)強(qiáng)調(diào)過(guò)這個(gè)概念,所以程序中的左閉右開
應(yīng)該也是為了盡量符合數(shù)學(xué)規(guī)則而設(shè)計(jì)的吧!
但我覺(jué)得并不能說(shuō)后面實(shí)現(xiàn)的這個(gè)隨機(jī)數(shù)生成器
就比前面的那個(gè)好,也是要分情況的,舉個(gè)極端點(diǎn)的案例:假如從 0 ~ 10000 里隨機(jī)生成 10 個(gè)不重復(fù)的數(shù)字,在范圍這么大的情況下重復(fù)的幾率是不是就很低了?所以第一種方案很可能只需要生成十個(gè)隨機(jī)數(shù)就滿足需求了。但如果是第二種方案的話:先要生成一個(gè)從 0 ~ 10000 的數(shù)組,這個(gè)數(shù)組太大了,但是卻只需要其中的 10 個(gè)數(shù),有點(diǎn)像是高射炮打蚊子、殺雞焉用宰牛刀的感覺(jué),只有在范圍內(nèi)占比越大的情況下,第二種函數(shù)才越合適。如果想要封裝得更智能一點(diǎn)的話,可以給大家提供個(gè)思路:
用 to - from 除以 len 的值,就是比例了。比如從 0 ~ 10000 里獲取 10 個(gè)數(shù),就相當(dāng)于 10 / 10000,也就是千分之一,這種情況下就用第一種函數(shù)去獲取隨機(jī)數(shù)、而如果是從 0 ~ 10000 里獲取 3000 個(gè)數(shù),就是十分之三的比例,此時(shí)用第二種函數(shù)會(huì)更加合適一點(diǎn):
const fn = (len, from = 0, to = 100) => {
const ratio = (to - from) / len
let result = []
if (ratio > 0.3) {
const allNums = Array.from({ length: to - from }, (_, i) => i + from)
for (let i = len; i-- > 0;) {
result.push(allNums.splice(Math.floor(Math.random() * allNums.length), 1)[0])
}
} else {
for (let i = len; i-- > 0;) {
result.push(Math.round(Math.random() * to + from))
}
result = [...new Set(result)]
let length = result.length
while (len - length > 0) {
result = [...new Set(result.concat(fn(len - length, from, to)))]
length = result.length
}
}
return result
}
復(fù)制代碼
當(dāng)然這個(gè)函數(shù)還缺少很多判斷:比如當(dāng) from
比 to
大的時(shí)候怎么辦?傳負(fù)數(shù)
的話怎么辦?傳小數(shù)
的話怎么辦?bigint
和number
混著傳的時(shí)候該怎么辦?len
比 to - from
還大的時(shí)候怎么辦?這些就不在這里浪費(fèi)篇幅的去挨個(gè)封裝了,大家感興趣的話可以自己去封裝一下。
用處
大家是不是覺(jué)得這個(gè)函數(shù)除了能當(dāng)面試題幾乎沒(méi)有其他的用處了?還真不一定!做完這道題我立馬就想起來(lái)以前做[青島銀行]文化體驗(yàn)桌時(shí)的夫子問(wèn)答模塊:
這個(gè)文化體驗(yàn)桌其實(shí)是讓來(lái)到青島銀行辦業(yè)務(wù)的朋友在等待的過(guò)程中不那么無(wú)聊,尤其是有那種帶小孩來(lái)的顧客,在宣傳山東文化的同時(shí)順便插播兩條廣告賺點(diǎn)外快(是他們賺不是我賺),這個(gè)模塊一開始后端讓我從論語(yǔ)里挑二三十道題發(fā)給他,他錄入到數(shù)據(jù)庫(kù)里,然后我通過(guò)請(qǐng)求來(lái)隨機(jī)獲取 10 道題。后來(lái)他實(shí)在太忙了,說(shuō)反正也沒(méi)幾道題,讓我全寫在前端自己隨機(jī)獲取吧!
于是隨便找了些論語(yǔ)放到里面去,那么接下來(lái)就是算法了,用戶肯定不希望每次進(jìn)去都是完全相同的十道題,最起碼得帶點(diǎn)隨機(jī)性吧?當(dāng)時(shí)比較趕進(jìn)度趕時(shí)間,沒(méi)有好好寫算法,只寫了個(gè)簡(jiǎn)易版的:
const arr = ['題', '題', '題', ...'題']
const result = arr.filter(() => Math.random() > 0.5)
result.length = 10
復(fù)制代碼
這么寫是實(shí)現(xiàn)了需求,但是并不嚴(yán)謹(jǐn),導(dǎo)致的結(jié)果就是數(shù)組里面越靠前的題目越會(huì)經(jīng)常出現(xiàn),而越靠后的題目就越不容易出現(xiàn),來(lái)測(cè)試一下:
const arr = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
const fn = num => {
const result = arr.filter(() => Math.random() > 0.5)
result.length = num
return result
}
復(fù)制代碼
運(yùn)行結(jié)果:
可以看到越靠后的數(shù)字出現(xiàn)的幾率越小,前幾位數(shù)倒是經(jīng)常會(huì)出現(xiàn),0(第一題) 或 1(第二題) 幾乎次次都出現(xiàn),而最后一題在大部分情況下甚至連出場(chǎng)的機(jī)會(huì)都沒(méi)有。
不過(guò)由于當(dāng)時(shí)天天加班到凌晨,累得不行根本沒(méi)精力想算法,找了找?guī)欤?Lodash
、Underscore
等庫(kù)里面也并沒(méi)有發(fā)現(xiàn)能實(shí)現(xiàn)類似功能的方法,于是就先這樣了,等測(cè)試說(shuō)這里有問(wèn)題的時(shí)候再改吧!先把軟件做出來(lái)才是頭等大事。不過(guò)后來(lái)也沒(méi)人發(fā)現(xiàn)這個(gè)問(wèn)題,只有我自己知道(現(xiàn)在你們也知道啦!),天知地知、你知我知,不要去青島銀行里跟別人說(shuō)哦!
當(dāng)然有了我們前面面試題做出來(lái)的那個(gè)函數(shù),這一切都會(huì)迎刃而解,生成出來(lái)的隨機(jī)題目將會(huì)非常均衡,不會(huì)出現(xiàn)前三題出場(chǎng)機(jī)會(huì)偏高,后三題靠碰運(yùn)氣才能遇到的情況啦!如果屏幕前的你是青島人或者身處青島地區(qū)的話可以去青島銀行看看,試一下夫子問(wèn)答是不是會(huì)有這種情況。
如果你問(wèn)我明明寫出來(lái)算法了為什么不把青島銀行里的文化體驗(yàn)桌算法替換掉呢?因?yàn)橐呀?jīng)離職了呀!我沒(méi)有權(quán)限再碰這個(gè)項(xiàng)目了,而且在青島駐場(chǎng)的那些同事們也都撤回來(lái)了,公司跟青島銀行的合同也已經(jīng)結(jié)束了,驗(yàn)收的時(shí)候甲方領(lǐng)導(dǎo)也很滿意,并未提出什么整改意見(jiàn),于是這個(gè)項(xiàng)目也就這么圓滿的落下帷幕了…
我只能把那個(gè)算法的缺陷埋藏在歲月之中,在這里跟你們傾訴一下了。