從hello world看JavaScript隱藏的黑魔法

寫在最前

事情的起因是這段看起來不像代碼的代碼:

_20170825232240

有興趣的同學(xué)可以自己先嘗試下!

([]+[][(![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])()(([]+{})[+[]])[+[]]+(!+[]+!![]+[])+(+!![]+[]))

作者對著這段代碼足足看了一下午,我只想說這不是什么深奧的黑魔法。一點點看下來你就知道其中的原理了。最后會有一段作者自己封裝的代碼叫nb.js(源碼在這里),它實現(xiàn)了輸入數(shù)字字母后自動生成這種玄學(xué)代碼片段。就像這樣:

_20170825233827

歡迎關(guān)注我的博客,不定期更新中——

JavaScript小眾系列開始更新啦

——何時完結(jié)不確定,寫多少看我會多少!這是已經(jīng)更新的地址:

這個系列旨在對一些人們不常用遇到的知識點,以及可能常用到但不曾深入了解的部分做一個重新梳理,雖然可能有些部分看起來沒有什么用,因為平時開發(fā)真的用不到!但個人認為糟粕也好精華也罷里面全部蘊藏著JS一些偏本質(zhì)的東西或者說底層規(guī)范,如果能適當避開舒適區(qū)來看這些小細節(jié),也許對自己也會有些幫助~文章更新在我的博客,歡迎不定期關(guān)注。

轉(zhuǎn)換思路

基礎(chǔ)思路:通過關(guān)鍵字來獲取字母

什么意思?比如:f。看到f你會想到哪個關(guān)鍵字?同時這個關(guān)鍵字是要在類型轉(zhuǎn)換的機制下能夠被打印出來的。如果類型轉(zhuǎn)換你還不是很了解,可以先讀下這篇來理解一下:從[] == ![]看隱式強制轉(zhuǎn)換機制。我相信很多同學(xué)可以想到是false這個關(guān)鍵字。那么我們的思路就有了也就是要讓代碼實現(xiàn)'false'[0]這件事,這個認識統(tǒng)一之后我相信下面的代碼一定不難理解了:

[[[] == []] + []][+![]][+![]]
//過程理解為
[] == [] => false
[[] == []] => [false]
[[[] == []] + []] => ['false'], [+![]] => 0
[[[] == []] + []][+![]] => 'false'
[[[] == []] + []][+![]][+![]] => 'false'[0] => 'f'

其中大體形式可以理解為:['false'][0][0] => 'f'

是不是瞬間覺得也不過如此?

可通過關(guān)鍵字獲取的字符

當你知道可以用上面的方式來獲取自己需要的字母之后,接下來要做的是思考一下你能從關(guān)鍵字中獲取哪些字母呢,作者總結(jié)了以下你可以通過關(guān)鍵字獲得的字母:

([][[]]+[]) => 'undefined'
+[1+[[][0]+[]][0][3]+400][0]+[] => 'Infinity'
[[[] == []] + []][+![]] => 'false'
[[[] != []] + []][+![]] => 'true'
([]+{}) => "[object Object]"

感興趣的同學(xué)自己打印下就明白為什么了。

接下來要說的是剩下的字母怎么辦?當然了你仍然可以通過試圖尋找關(guān)鍵字的方式來獲取字母。但是如果標點我也想要呢?或者說26個字母我都想要怎么辦?
具體點來說對于“hello world!”這段字符串來看,至少“w”,"!"的獲取方法通過關(guān)鍵字的形式我們是無從下手的。

unescape

unescape() 函數(shù)可對通過 escape() 編碼的字符串進行解碼。但是已經(jīng)廢棄了

是的現(xiàn)在已經(jīng)不建議如此使用了,但是瀏覽器下基本還是支持這個函數(shù)的。通過這個函數(shù)我們可以通過ascll碼來直接得到我們需要的字符:

unescape('%77') => 'w'

如此看來,除了我們可以快速得得到一些關(guān)鍵字字母外,用這個方法我們便可以實現(xiàn)任意字母的組合。而作者封裝的nb.js也是基于這兩者來實現(xiàn)輸出黑魔法字符串的。

那么現(xiàn)在的問題是如果通過字符串來執(zhí)行unescape('%77')這段代碼?

來看下hello world那段代碼是如何實現(xiàn)的:


在這里也不繞彎子了,作者打印了很多次之后才發(fā)現(xiàn)是如此調(diào)用的:

[]['sort']['constructor']('return unescape')

因為JS調(diào)用方法不光是“.”調(diào)用,通過[]也是可以調(diào)用的。同時通過return unescape,返回了一個匿名函數(shù)形成了閉包。故調(diào)用的時候采用如下方式:

[]['sort']['constructor']('return unescape')()('%77') => 'w'

至于為什么這段代碼寫出來如此長是因為上面的每一個字母都是一點點拼出來的,也行好上面通過關(guān)鍵字的方式可以得到這些字母=。=不然的話——

封裝nb.js

所以經(jīng)過上面的分析你會發(fā)現(xiàn),除了字符串長度感人之外,這種通過拼接字符串可以返回函數(shù)并且執(zhí)行的方式還真是蠻炫酷的。為了達到裝逼的效果。作者決定封裝一個支持字母和數(shù)字的函數(shù),當你傳入普通的字符串之后,會返回帶有黑魔法氣息的冗長字符串,盡情拿去裝x吧,不客氣~

封裝過程

維護基礎(chǔ)對象與ascll表對象

var baseAlibrary = {
    'a': '[[[] == []][0]+[]][0][1]',
    'b': null,
    'c': '[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]]+[]][0][3]',
    'd': '([][[]]+[])[+!![]+!![]]',
    'e': '([][[]]+[])[+!![]+!![]+!![]]',
    'f': '([][[]]+[])[+!![]+!![]+!![]+!![]]',
    'g': null,
    ...
    '0': '(+![])',
    '1': '(+!![])',
    '2': '(+!![]+!![])',
    ...
    ',': null,
    '!': null,
    }
    var ascll = { //ascll表可自行配置, 新添加后需要在上面對象中配置相同key,只是value為null
        'A': '41',
        'B': '42',
        ...
    }

將簡單的字母轉(zhuǎn)換方式直接存儲下來,如果需要的字符無法從基礎(chǔ)對象獲取,就記為null,并在ascll表中寫入相關(guān)轉(zhuǎn)碼方式。

封裝unesacpe

var result = ''
    var unescapeStr = '[][(![]((!![]+[])[+!![]((!![]+[])[+!![]]+([][[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!![]]+([][[]]+[])[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+([]+{})[+!![]]+([]+{})[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][[]]+[])[!+[]+!![]+!![]+!![]+!![]]+([]+{})[+!![]]+([][[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][[]]+[])[!+[]+!![]+!![]])'
    //將[]['sort']['constructor']('return unescape')的黑魔法形式存儲起來之后直接調(diào)用
    function changeAscll(ascllItem) {
        var ascllResult = ''
        var middleValue = ''
        ascll[ascllItem].split('').forEach(function(item) {
            if(isNaN(item)) { //ascll中遇到字母則需要再次進行unescape轉(zhuǎn)碼
                var str = ''
                ascll[item].split('').forEach(function(data) {
                    str += '+[' + baseAlibrary[data] + ']'
                })
                middleValue += '+' + unescapeStr + '()('+ baseAlibrary['%']+'+' +  str.slice(1) + ')'
            } else {
                middleValue += '+[' + baseAlibrary[item] + ']'
            }   
        })
        ascllResult += '+' + unescapeStr + '()('+ baseAlibrary['%']+'+' +  middleValue.slice(1) + ')'
        return ascllResult
    }
    function getUnEscape(str) {

    }
    strArr.forEach(function(item) {
        Object.keys(baseAlibrary).forEach(function(obj, i) {
            if(item.toLocaleLowerCase() === obj) {
                if(!baseAlibrary[item]) {
                    Object.keys(ascll).forEach(function(ascllItem) {
                        if(obj === ascllItem) {
                            var cbValue = changeAscll(ascllItem).slice(1)
                            result += '+' + cbValue
                        }
                    })
                } else {
                    result += '+' + baseAlibrary[obj]
                }
            }
        })
    })
    console.log(result.slice(1))

效果

也就是一開始大家看到的:


_20170825233827

作者將函數(shù)綁定在了this上,通過this.reallyNb()即可得到你想要的~

PS:代碼請部署在服務(wù)器中再打開頁面,否則個別字母通過location方法會取不到,主要就是t,p。不過這個問題之后作者會將其以ascll表的方式存儲,就沒有環(huán)境限制了。只是作者嫌棄那樣做打印的字符串太長了~

最后

不定時更新中——
有問題歡迎在issues下交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容