如果我去參加前端面試,我應(yīng)該能做出大圣老師的這道題...
本文轉(zhuǎn)載自微信公眾號(hào)「Piper蛋窩」,作者Piper蛋。轉(zhuǎn)載本文請(qǐng)聯(lián)系Piper蛋窩公眾號(hào)。
我是一名自學(xué)敲代碼的管理學(xué)研究生,喜歡 js/ts 但是菜得不行,平常挺關(guān)注國內(nèi)的前端圈。
除了讀雪碧大佬[1]等等大佬的知乎外(蒟蒻還看不太懂),平常也看看大圣老師[2]等等的B站。
有一次看大圣老師直播點(diǎn)評(píng)簡(jiǎn)歷,他提到:“如果我來面試你,我就把我面前的筆記本給你,隨便給你打開個(gè)網(wǎng)頁比如淘寶,你給我用瀏覽器現(xiàn)場(chǎng)統(tǒng)計(jì)一下各個(gè)標(biāo)簽出現(xiàn)的次數(shù)。”
!這道題應(yīng)該不難?我分析無非就是考察了三點(diǎn):
- 最最基礎(chǔ)的瀏覽器調(diào)試能力
- 算法能力
- 基礎(chǔ)的 JavaScript API 應(yīng)用
剛和爸媽打完球回來,那我就做做這道題。
首先咱捋一下思路:
- 其實(shí)早在聽到這個(gè)題目時(shí),我腦子中就蹦出兩個(gè)字:『遞歸』!
- 畢竟,我們的網(wǎng)頁就是一棵 DOM 樹,從根部有子節(jié)點(diǎn),子節(jié)點(diǎn)還有子節(jié)點(diǎn),對(duì)于每個(gè)節(jié)點(diǎn),我們能夠知道這個(gè)節(jié)點(diǎn)是什么標(biāo)簽并且對(duì)其子節(jié)點(diǎn)做同樣的事就可以了
然后我們捋一下需要哪些技術(shù)細(xì)節(jié):
- 首先我們應(yīng)該獲取根節(jié)點(diǎn),這個(gè)好說,我們?cè)跒g覽器的控制臺(tái)里試一試就知道:document.children[0]
- 然后我們應(yīng)該能夠獲取每個(gè)標(biāo)簽對(duì)象的字符串名字和子節(jié)點(diǎn)列表,分別是 tagName 和 children
- 至于如何實(shí)現(xiàn)「遞歸」呢?這里未必要用到遞歸,我用的是寬度優(yōu)先搜索 BFS ,簡(jiǎn)單一個(gè)隊(duì)列就能實(shí)現(xiàn)
值得一提的是,我近一個(gè)月里寫了基于 C++ 、Python 、 JavaScript/TypeScript 、 Scala/Java 的不同項(xiàng)目/小腳本(工作要求...),所以我也記不住 JavaScript 的 API ,我都是在瀏覽器控制臺(tái)里試出來的,比如 獲取標(biāo)簽的名字是 tagName 、 獲取子節(jié)點(diǎn) Array 是 children 。如下圖,我試關(guān)鍵詞試出來的,要不然誰記得住啊。
輸入 tag 會(huì)不會(huì)得到我想要的 API 呢?果然!
下面動(dòng)手來做吧
第零步,打開瀏覽器的 Sources ,新建一個(gè) Snippet 。
Sources
首先我不知道 JavaScript 里有沒有現(xiàn)成的隊(duì)列數(shù)據(jù)結(jié)構(gòu),應(yīng)該是沒有,那我就自己實(shí)現(xiàn)一個(gè)吧。
- class Queue {
- #array = []
- constructor () {
- this.#array = []
- }
- top () {
- return this.#array[0]
- }
- size () {
- return this.#array.length
- }
- pop () {
- this.#array.shift()
- }
- push (ele) {
- this.#array.push(ele)
- }
- }
很簡(jiǎn)單的封裝!我平時(shí)做算法題都是用 C++ ,所以這里方法的名稱就都盡量接近 C++ 的 std::queue
接下來咱們寫 BFS 就行了!
我看現(xiàn)在大佬們都把每個(gè)邏輯封裝在函數(shù)里,所以咱也把腳本運(yùn)行邏輯 main() 里,然后再在外面調(diào)用一下 main() ,看著整潔點(diǎn)。
- const main = () => {
- const dict = {}
- const queue = new Queue()
- const htmlTag = document.children[0]
- dict[htmlTag.tagName] += 1 // !!!
- queue.push(htmlTag)
- while (queue.size() > 0) {
- const t = queue.top()
- queue.pop()
- for (let i = 0; i < t.children.length; i ++) {
- childTag = t.children[i]
- dict[htmlTag.tagName] += 1 // !!!
- queue.push(childTag)
- }
- }
- for (let item in dict) {
- console.log(item, ': ', dict[item])
- }
- }
- main()
上面是最最簡(jiǎn)單的 BFS 實(shí)現(xiàn)了,可見這道題著實(shí)不是用算法難為我們,很實(shí)在的一道題。
注意我標(biāo)注的 !!! 兩行,這里有一個(gè)問題:
- dict = {} 中,對(duì)于未聲明過的鍵值,如果直接調(diào)用運(yùn)算,會(huì)報(bào)錯(cuò) dict[未聲明的鍵值] +=1 // 報(bào)錯(cuò)!
- 而 js 又不是 Python ,沒有 setdefault 給我們用比如 dict.setdefault(鍵值, 0); dict[鍵值] += 1
- js 也不是 C++ ,直接默認(rèn)未出現(xiàn)過的鍵值的值為 0
- 因此我們需要再寫一個(gè) dict[未聲明的鍵值] +=1 功能
咱們把這個(gè)邏輯寫成一個(gè) Effect ,返回一個(gè)函數(shù),以顯示咱很注重邏輯復(fù)用性(劃去)。
- const addDictEffect = (dict) => {
- return (name) => {
- if (dict[name]) {
- dict[name] += 1
- } else {
- dict[name] = 1
- }
- }
- }
OK 那下面在修改一下 main ,一共有三處!
- const main = () => {
- const dict = {}
- const addDict = addDictEffect(dict) // 第一處!
- const queue = new Queue()
- const htmlTag = document.children[0]
- addDict(htmlTag.tagName) // 第二處!
- queue.push(htmlTag)
- while (queue.size() > 0) {
- const t = queue.top()
- queue.pop()
- for (let i = 0; i < t.children.length; i ++) {
- childTag = t.children[i]
- addDict(childTag.tagName) // 第三處!
- queue.push(childTag)
- }
- }
- for (let item in dict) {
- console.log(item, ': ', dict[item])
- }
- }
- main()
啪!很快啊,本題目解決。www.taobao.com 結(jié)果如下。
代碼
結(jié)果
其他網(wǎng)頁均可測(cè)試。
完整代碼
- class Queue {
- #array = []
- constructor () {
- this.#array = []
- }
- top () {
- return this.#array[0]
- }
- size () {
- return this.#array.length
- }
- pop () {
- this.#array.shift()
- }
- push (ele) {
- this.#array.push(ele)
- }
- }
- const addDictEffect = (dict) => {
- return (name) => {
- if (dict[name]) {
- dict[name] += 1
- } else {
- dict[name] = 1
- }
- }
- }
- const main = () => {
- const dict = {}
- const addDict = addDictEffect(dict)
- const queue = new Queue()
- const htmlTag = document.children[0]
- addDict(htmlTag.tagName)
- queue.push(htmlTag)
- while (queue.size() > 0) {
- const t = queue.top()
- queue.pop()
- for (let i = 0; i < t.children.length; i ++) {
- childTag = t.children[i]
- addDict(childTag.tagName)
- queue.push(childTag)
- }
- }
- for (let item in dict) {
- console.log(item, ': ', dict[item])
- }
- }
- main()
目前不會(huì)js/ts+沒做過項(xiàng)目,菜到都沒法給大圣老師發(fā)簡(jiǎn)歷讓他點(diǎn)評(píng)。期待早日能到發(fā)簡(jiǎn)歷的地步。