如何在控制臺畫一個(gè)小電視?
眾所周知,每當(dāng)我們在B站PC?Web的大多數(shù)頁面打開控制臺,都會(huì)看到一個(gè)由字符畫組成的小電視和"BILIBILI"的組合圖案,右下角還有當(dāng)前前端程序的構(gòu)建信息。今天我們就來探究一下這個(gè)小電視是怎么畫出來的。

提取對應(yīng)邏輯(函數(shù))

首先我們點(diǎn)擊控制臺輸出內(nèi)容的右上角,找出輸出這個(gè)小電視的initiator(發(fā)起者)。格式化之后可以很容易地看出輸出小電視的是一個(gè)既無參數(shù)也無返回值的函數(shù),它被綁定到了一個(gè)名為`__getClientLogo`的變量,并且在綁定之后立即被調(diào)用。至于為什么綁定到變量,而不是像下面的主邏輯一樣寫成立即執(zhí)行函數(shù),可能是因?yàn)樵谥蟮倪壿嬛袝?huì)再次調(diào)用,不過這個(gè)我們不關(guān)心。函數(shù)定義被寫到了一個(gè)if里面,判斷有無名為`ActiveXObject`的全局變量,用來排除使用ActiveX技術(shù)的早期IE瀏覽器。接下來看這個(gè)函數(shù)是否有依賴,可以看出只依賴了一個(gè)函數(shù),叫做`_toConsumableArray`,從函數(shù)名以及上面的一系列相互依賴的實(shí)現(xiàn)可以判斷出這是一個(gè)用來對各種可迭代對象和不標(biāo)準(zhǔn)的數(shù)組進(jìn)行“格式化”的工具函數(shù),于是大膽猜測可以直接去掉這個(gè)函數(shù)調(diào)用。
分析(化)邏輯(簡)
接下來將輸出小電視的函數(shù)復(fù)制到編輯器,把這一長串base64字符串先剪切出去,保存在另一個(gè)文檔里面,用一個(gè)變量text代替,以防編輯器卡頓。右鍵格式化一下,開始調(diào)教。

首先做一些準(zhǔn)備工作:將`text`提取成函數(shù)參數(shù);去掉對`_toConsumableArray`的調(diào)用;代碼中并沒有`this`,于是將全部函數(shù)改為箭頭函數(shù)。

接下來將變量聲明和賦值拆開成多個(gè)語句,并且先補(bǔ)全所有分號。這是一個(gè)需要膽大心細(xì)的操作,一定要全神貫注。

把作用于函數(shù)對象`console.log`的`apply`方法改為(es6+的)數(shù)組展開語法,并且移除變量`t`。

通過代入值,移除所有只被引用過一次的變量。

通過多次代入值,移除變量`a`。

通過直接展開數(shù)組,移除`concat`方法和數(shù)組展開語法。這兩步同樣需要膽大心細(xì)。

將解析后的`text`的`forEach`-`push`循環(huán)改寫為`map`循環(huán),注意這樣改寫后需要用展開語法代入到綁定到變量`i`的數(shù)組。

通過代入值移除變量`i`,并且改寫箭頭函數(shù)為直接返回形式(反正都是void)。

到這一步為止,`text`還是一個(gè)黑箱,如果不解析`text`,最多只能化簡到這一步。打開一個(gè)新的標(biāo)簽頁,再打開控制臺,解析一下`text`。

可以看出,解析后的`text`是一個(gè)數(shù)組,每一項(xiàng)都是兩項(xiàng)是字符串的元組(第一項(xiàng)是小電視,第二項(xiàng)是"BILIBILI"和構(gòu)建信息),記作類型`[string,?string][]`。區(qū)別于數(shù)組是相同類型的對象的有序集合,元組是長度和每一個(gè)位置的類型都確定的數(shù)組。

將解析`text`的過程提取到函數(shù)體外,并將參數(shù)名改為input。接下來完成最后一步化簡:將`t[0]?+?t[1]`改為數(shù)組的`join`方法。我還是那句話,不建議用加法運(yùn)算符連接字符串,否則容易出現(xiàn)隱式類型轉(zhuǎn)換從而導(dǎo)致意外的結(jié)果。最后為了驗(yàn)證一下,可以把文件擴(kuò)展名改為ts,在函數(shù)參數(shù)處加上類型標(biāo)記,無報(bào)錯(cuò)。還可以再用IntelliSense手動(dòng)檢查一下各個(gè)函數(shù)/方法調(diào)用的類型,確認(rèn)無誤。(將`text`變?yōu)樽址菫榱朔乐箃s模式下報(bào)錯(cuò))
分析一下最終的這段代碼。首先對輸入的數(shù)組進(jìn)行`map`循環(huán),將每一項(xiàng)的元組中的兩個(gè)字符串連接起來,并展開到一個(gè)新的數(shù)組中;這個(gè)新的數(shù)組除了那些展開的項(xiàng)之外,還有一個(gè)字符串首項(xiàng)`%c`,用來提示`console.log`對之后的輸出內(nèi)容應(yīng)用第二個(gè)參數(shù)中的樣式;這個(gè)新的數(shù)組中的每一個(gè)字符串又被連接起來,并且每一個(gè)字符串之間加上了換行符,組合而成的長字符串作為`console.log`的第一個(gè)參數(shù);`console.log`的第二個(gè)參數(shù)則包含了輸出所用的樣式,而#00a1d6的色值正是B站藍(lán)色;最終調(diào)用`console.log`,按照指定的樣式輸出內(nèi)容。
至于原本的邏輯為什么要設(shè)計(jì)成那樣繞來繞去的?編譯時(shí)進(jìn)行的代碼混淆和es6+語法降級可能是原因之一,但是我個(gè)人覺得更可能是程序員為了裝B故意寫成這樣的,畢竟控制臺的小電視本來就是用來裝B的(無誤)。
驗(yàn)證
見證奇跡的時(shí)刻就要到了。打開一個(gè)新的標(biāo)簽頁,再打開控制臺,先寫下`const?text?=?`,然后將base64字符串的內(nèi)容復(fù)制過來。然后將最終代碼的函數(shù)參數(shù)處的類型`:?[string,?string][]`手動(dòng)擦除掉(也就是直接刪掉),將`text`由字符串還原為變量引用,復(fù)制到控制臺,回車運(yùn)行。

和原版完全一致,除了多輸出一個(gè)`undefined`之外(多輸出一個(gè)`undefined`是因?yàn)閞epl模式下會(huì)自動(dòng)輸出函數(shù)的返回值)。
簡單的修改
如果只想輸出小電視,可以把函數(shù)`(t)?=>?t.join('')`替換為`(t)?=>?t[0]`,只取第一項(xiàng)的值;如果只是不想輸出構(gòu)建信息,可以替換為`(t,?i)?=>?i?===?12???t[0]?:?t.join('')`,因?yàn)橹敖馕鯼text`時(shí)可以看到構(gòu)建信息在`[12][1]`處,那么只需要在遍歷到12時(shí)只取第一項(xiàng)的值即可。

可以如此方便地改變輸出,這也許就是數(shù)據(jù)結(jié)構(gòu)是`[string,?string][]`,而不是`string[]`甚至直接是要輸出的`string`的原因之一。