探索 Python 來反補 JavaScript,帶你 Cross Fire —— JS 數據類型的奧祕

寫在最前

數據類型能夠說是編程語言的基石,重要性不言而喻。那麼如今就從數據類型開始,打破你的思惟認知,作一個充滿想象力的FEE。針對上篇的一些偏激評論,我想強調的一點是:我寫的文章,並非給那些偏激到說髒話的人看的,請尊重每一位爲前端貢獻微薄力量的Blogger前端

好像,我這標題起的也太秀了,會不會被打😂。java

多說一句

這篇能夠算是 前端獵奇系列中的 探索 Python 來反補 JavaScript 的中篇。 若是沒有看過上篇文章的,能夠去個人專欄裏讀讀上篇,在知識上沒有啥關聯的地方,相對獨立。基本是我在學習PY的時候,學到某一個地方,忽然會想到JS在這一方面是如何表現的,而後隨着思考,真的會有很多收穫吧。node

關於數據類型

有句話說的好,掌握數據類型是學習一門語言的基礎。咱們從這句話中能夠看出,掌握好數據類型是有多麼重要。你曾經是否是有想過JS的數據類型爲何會是這樣,爲何要有nullundefined。也許你有過疑問,可是疑問觸發後,簡簡單單的探尋後,就把疑問扔到回調函數裏了,這一扔就是到現在。如今我將從PY的角度來反補JS,經過PY去看清JS的數據類型,看清編程語言的一些規律。now go!chrome

JS的數據類型分爲值類型和引用類型:編程

  1. 值類型有:數字、字符串、布爾、Null、Undefined、Symbol、
  2. 引用類型有:Array、Function、Set、Map

PY的數據類型分爲數值類型、序列類型、Set類型、字典類型:segmentfault

  1. 數值類型有:Integer、Long integer、Boolean、Double-precision floating
  2. 序列類型有:String、Tuple、List(和JS的Array相同)
  3. Set類型(和JS的Set相同)
  4. Dictionary類型(和JS的Map相同)

如今咱們看一下PYJS的數據類型,這裏我不闡述具體是什麼,我只是總結一下,當我學習到這的時候,我對JS的數據類型有了什麼樣新的理解。如今,你會發現幾個頗有趣的地方,請看以下:數組

關於 Set 和 Map

這和PYSetDictionary不謀而合,可是ES6規範的制定者,沒有選擇使用Dictionary做爲鍵值對的類名稱,而選擇了使用Map做爲鍵值對的類名稱。而Map正是Java的鍵值對的類名稱。因此給個人感受就是,JS在吸取不少語言的優秀的特性,我我的認爲,命名成Map要比Dictionary好,畢竟少寫7個字符呢😂。瀏覽器

關於 Array 和 List

就這樣就結束了嗎?No,咱們再看上面兩種類型,首先注意PYListJSArray是相同的,都是能夠動態進行修改的。可是不少FEE,由於掌握的知識不夠寬泛,致使了對不少事情不能理解的很透徹。好比,咱們的思惟中就是這樣一種固定的模式:數組是能夠動態修改的,數組就是數組類型。。我我的建議,FEE必定不能將本身的思惟束縛在某個維度裏。這真的會阻礙你 開啓那種瞬間頓悟的能力。前端工程師

若是你瞭解了PY或者其餘語言,你會發現其實JS的數組,其在編程語言裏面,只能算是List類型,屬於序列類型的一種。並且很重要的是,JSArray是動態的,長度不固定,瞭解過Java的同窗應該知道,在Java中,數組是分爲ArrayArrayListAarry是長度固定的,ArrayList是長度能夠動態擴展的。因此JSArray其實只是編程語言 的Array中的一種。若是你知道這些,我以爲這對去深入理解JS的數據類型將有很大的幫助。雖然JS對一些知識點進行了簡化,可是做爲一個合格的計算機工程師,咱們不能習慣的接受簡化的知識點,必定要去多維度理解和 掌握簡化的知識點。瞭解其背後的世界,也是很是奇光異彩的。編程語言

關於 JS 的 String 和 PY 的 String

你會發現JSString是被歸類爲數值類型,而PYString是被歸類爲序列類型。其實我我的更傾向於把JSString歸爲序列類型,爲何這麼說呢,由於JS的字符串自己就帶有不少屬性和方法,既然有方法和屬性,也就意味着至少是個對象吧,也就是隱式執行了new String。字符串對象能夠經過方法和屬性來操做本身的字符序列。因此這被歸類爲數值類型的話,我我的認爲是不科學的,而PY就分的很清楚。

關於 null 和 undefined

null 和 undefined 的爭論就在此結束吧。

可能一開始會對nullundefined比較陌生,可能會有那麼一刻,你懷疑過JSnullundfined爲何會被單獨做爲數據類型,可是過了那一刻,你就默許其是一個語言設計規則了。可是我想說的是,語言設計規則也是人設計的,是人設計的就應該多一份懷疑,沒必要把設計語言的人當作神同樣。編程語言那麼多,哪有那麼多神。網上有不少好文章介紹JSundefinednull的,也都說了有多坑。想深刻理解有多坑的能夠自行百度谷歌。我也不重複造解釋了,好比,undefined竟然不是保留字,也是夠神奇的,看了下博客,有篇解釋的很不錯,能夠瞅瞅爲何undefined能夠被賦值,而null不能夠?。寫博客的時候,並非一味的寫本身的東西,有時候別人總結好的東西,在我寫博客過程當中,也能帶給我不少靈感和收穫。這也是算是和站在巨人的肩膀上是一個道理吧。

不過我仍是有點我的獨特的見解的。並且我認爲個人見解要比網上絕大多數的看法要更加深入(不要臉)。我不想說undefined有多坑,我只想去探究和理解undefined的本質。掌握了本質後,坑不坑也就不重要了。我我的認爲,JSundefined是一種爲了處理其餘問題而強行作出的一種折中方案,且聽我娓娓道來。

既然PYJS都是解釋性語言,那麼爲何PY能夠不依賴undefined,只須要使用None就能夠了呢? 我寫一個簡單的例子,能夠從我下面的分析中,找到一些更深層的真相,找到設計undefined真正的緣由。代碼以下:

let x
console.log(x)
複製代碼
# coding=utf-8
print(x)
複製代碼

咱們來看運行結果:

image

從圖中會發現,JS沒有報錯,可是PY報錯了,到底是什麼緣由? 這裏中斷一下,咱們來看下面這個截圖,是java的一段代碼的運行結果:

image

圖中能夠看出,在Java中,能夠聲明變量,但不賦值,而後不去調用此變量,程序是不報錯的,可是在PY中,請看下面截圖:

image

咱們發現,咱們聲明瞭,也沒有去調用它,程序仍是報錯了。是爲何呢?

爲何在JavaC++C語言中,能夠聲明變量,而不用賦值,而且不會報錯。而在PY中會報錯呢,而在JS中是undefined呢?其實仔細一想,會恍然大悟,一個很是關鍵的一點就是:

JavaC++C是強類型語言,在聲明的時候,就已經肯定了數據類型。因此就算不去賦值,JavaC++等也會根據聲明的數據類型,設置一個默認的數據類型的值。可是這裏注意一點,若是整個程序執行完,在只聲明,卻沒有賦值的狀況下,去輸出或者調用該變量,程序會報錯的。爲何會報錯呢,是由於此變量的地址是系統隨機生成的,並不在此程序內的地址範圍內,也就是說此變量的地址多是指向其餘程序的地址,在這種狀況下,若是去調用該地址,那麼可能會出現很大的危險性,好比你調用了其它很重要的東西。這裏我以爲能夠把它理解爲遊離的指針,雖然這樣形容很差,可是很形象,遊離的指針是很危險的東西。有多危險,哈哈哈,本身體會✧(≖ ◡ ≖✿)。

中斷結束,繼續PS,從上面的敘述知道了Java等語言是強類型語言。可是咱們知道而PYJS是腳本語言,屬於弱類型語言,而弱類型語言的特色就是:在聲明變量的時候,不須要指定數據類型,這樣的好處就是一個變量能夠指向萬物,缺點是性能差一些,須要讓編譯器在賦值的時候本身去作判斷。請緊跟着個人腳步,咱們來看下面這段代碼:

let x
console.log(x)
複製代碼

能夠看到,xJS聲明的變量,因爲腳本語言是動態的,因此這個變量x能夠指向萬物,那麼若是直接使用x,而不讓其報錯的話,該怎麼作呢。

一個原則必定不能忘,就是不賦值的話,調用必定會報錯,OK,那就賦值,給一個默認值,那麼這個默認值用什麼來表示呢,指向萬物的話,那這類型的可能性有好幾種,若是使用null來表示的話,因爲null表明空對象,這裏說一個很關鍵的點,就是,爲何其餘語言中好比JavaC++,他們對於空,都是使用null來表明一個空對象的?

其實最本質的緣由仍是由於他們是強類型語言,必須在變量前面聲明數據類型,對於值類型,他們系統能夠自動給一個默認值。因此在強類型語言中的null,其做用只是給引用類型用的。而到了弱類型語言中,好比PYJS,咱們看PY,因爲PY老哥不想使用undefied,也只想用一個null。那麼天然而然的結果就是:直接不容許在未賦值以前,直接調用聲明的變量,只要調直接提示報錯,那麼你可能會有疑問了,爲何PY語言中,連只聲明變量,不去調用它,程序都會報錯呢。其實我我的以爲緣由是由於弱類型語言的數據類型不肯定致使的,編譯器沒法去給一個默認值,也就意味着不肯定因素增長,既然不肯定,那PY的作法就是直接使其報錯。經過編譯器報錯來顯式讓開發者去遵循編碼規則。

而小可愛JS就不同了,因爲設計者就是不想使其報錯,想容許聲明,而且能夠在未賦值的時候還能夠直接調用而不報錯。因此也就意味着他要給聲明的變量賦一個默認值,怎麼賦值呢?這估計也是困擾了設計者良久,下面我舉一個很簡單易懂的例子,請看下面代碼:

let x;
let y = [1,2,3]
console.log(x, y[3])
複製代碼

從代碼能夠看出,若是想不報錯,有幾種可能:

第一種: 按照其餘語言的規範,只保留一個空值null,ok,繼續往下推導,因爲JS是弱類型,變量指向萬物,因此確定只能給全部聲明但未賦值的變量設置null爲默認值了。可是這樣的話,問題來了。

看第三行代碼,其實y[3]也是聲明未賦值的變量,是否是有點不相信,以爲超出認知了。沒事,先繼續往下看,既然y[3]也是未賦值的變量,那把y[3]的默認值也設置爲null嗎?很明顯,不合理。

由於y[3]多是各類類型,若是直接都設置爲null。那用戶直接打印y[3],而後蹦出來一個null,仍是object類型,豈不要炸?因此到這裏,我會慢慢發現,其實JS中的nullundefined是徹底不一樣的兩碼事,很容易去區分。

綜上,我猜一下JS做者的腦洞應該是這樣的,既然我想讓調用聲明未賦值的變量不報錯,那ojbk。不是弱語言麼,不是指向萬物嗎?那要來就來刺激點,我就單獨設置一個數據類型,名爲undefined。專門用來counter指向萬物的聲明卻未賦值的變量。哈哈哈哈,是否是很刺激😂。

解決最後一千米的疑惑

看下面代碼

let x
let y = [1,2,3]
console.log(x, y[3])
複製代碼

你會發現xy[3]都是undefined。咱們來透過現象看本質,本質上就是聲明瞭,可是未賦值。爲何能夠這麼說,難道y[3],也是聲明瞭,但未賦值嗎?我能夠明確告訴你,是的,沒毛病。你可能不相信我說的話,下面我在白板上畫一個圖就頓悟了。。請看圖:

image

圖中能夠看到,其實數組的每個下標也是在棧裏進行聲明的。和用let x進行聲明的操做是同樣的。let x的聲明以下圖:

image

因此是否是發現其實undefined也就那麼回事吧。通常來講,若是某一個知識點越繞人,那咱們就應該從更底層的角度去看清這個知識點。只要你真的是從一個更加深入和底層的角度去看待undefined,其實 just so so 啦。對了,null我也順帶解釋了,只不過沒有重點關注,可是整篇下來,其實null是什麼,也差很少一清二楚了。總之nullundefined就是徹底不一樣的兩碼事。

總結

JSPY的數據類型,咱們能夠看出,PY在設計數據類型的時候,明顯考慮的不少,或者說,PY語言在被創造的時候,其數據類型的設計是比較規範的。而咱們去看JS,會發現,有不少坑,感受是當初爲了簡化知識點難度,而留下了不少坑。雖然我沒有經歷過IE時代的前端,但如今也能深入體會到前端工程師的不容易。之前還有同行說前端很簡單啊,如今也有,我都遇到過好幾回這種人了:

我:我是前端開發。

人家:噢,我知道了,就是寫網頁的對吧。。。

我內心os:對你個錘子。。

FEE們都是從坑裏一步步爬上來的,真的不容易。總之,如今的前端正在一步步走上規範,走上體面。。。

文末彩蛋一,動態參數

PY中如何處理動態參數的呢,其實PY是經過元組或者字典來處理動態參數的,代碼以下,這裏只寫使用 元組 實現動態參數的代碼

# coding=utf-8
def add(x, *tupleName):
    print(x, tupleName)

add('hello', 'godkun', '大王叫我來巡山')
複製代碼

執行結果圖以下:

image

咱們再看JS是如何實現的

function fun(a, ...tupleName) {
  console.log(a, tupleName)
}
fun('hello', 'godkun', '大王叫我來巡山')
複製代碼

執行結果圖以下:

image

看上面兩種方式,看完你應該就明白了,ES6增長展開符的緣由是什麼,以及爲何要設計成這個樣子。使用...做爲標記。同時爲何要將全部可變參數放在一個數組裏面。

其實語言都是有相同性的,尤爲對於JS語言來講,採納了不少語言的優勢,這對於咱們前端來講,是一個很大的優點,若是平時善於去這樣比較和反補,我我的以爲,FEE去承擔其餘開發崗位,也是徹底能Hold住的。

番外二,深夜寫博客時的意外驚喜(意不意外,刺不刺激)

當我寫下這段代碼:

function a(a, b, c) {
  console.log(arguments);
  console.log({ 0: "1", 1: "2" });
  console.log([1, 2, 3]);
}
a(1, 2, 3);
複製代碼

第一種狀況:我在node.js環境運行:結果如圖所示:

image

第二種狀況:我在chrome瀏覽器下執行這段代碼,結果如圖所示:

image

第三種狀況:我在IE瀏覽器下執行這段代碼,結果如圖所示:

ie arguments 1

上面第二種狀況,你會發如今chrome瀏覽器下,輸出的結果形式爲:

Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
  0: 1
  1: 2
  2: 3
  callee: ƒ a(a,b,c)
  length: 3
  Symbol(Symbol.iterator): ƒ values()
  __proto__: Object
複製代碼

我靠,什麼鬼。竟然把arguments寫成了數組的形式:

[1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]

可是 __proto__ 仍是 Object。嚇的我趕忙試了下面這段代碼,代碼如圖所示:

image

靠,還果然返回了長度。。。可是爲何__proto__Object。。。。

不行,我又看了IE瀏覽器和node.js環境下的結果,都是相同的結果,使用{}表示類對象數組

{0: 1, 1: 2, 2: 3, callee: function a(a,b,c){}, length: 3}
複製代碼

我陷入了沉思。。。。

不知道是chrome開發者故意這樣設計的,仍是寫錯了。。小老弟,你怎麼回事? chrome會弄錯? 本着上帝也不是萬能的理念,我打開了個人腦洞。

chrome瀏覽器既然不按照{}這種寫法,直接將arguments寫成[],使其直接支持數組操做,同時,其原型又繼續是對象原型。仔細看會發現又加了一行

Symbol(Symbol.iterator): ƒ values()

這樣作的目的是什麼,爲何要這樣設計?搜了blog,然而沒搜到。。。這一連串的疑問,讓我再次陷入了沉思。。。

思考了一會,動筆畫了畫,發現好像能夠找到理由解釋了。我以爲能夠這麼解釋:

chrome想讓類數組對象這種不倫不類的東西從谷歌瀏覽器中消失。因此下面這種輸出結果

{0: 1, 1: 2, 2: 3, callee: function a(a,b,c){}, length: 3}
複製代碼

就一去不復返了,那麼若是不這樣寫,用什麼方法去替代它呢。答案就是寫一個原型鏈繼承爲對象類型的數組,同時給繼承對象類型的數組(其仍是對象,不是數組) 增長Symbol.iterator屬性,使其能夠for of

爲何要這樣作呢,由於一些內置類型自帶迭代器行爲,好比StringArraySetMap,可是Object是不帶迭代器的,也就意味着咱們能夠推斷出,若是從chrome瀏覽器的那種寫法的表面上分析,假定argumentsArray,那麼就徹底不必增長Symbol.iterator,因此矛盾,因此能夠得出,arguments仍是對象,而對象是不帶迭代器的。因此要給形式爲 []arguments 增長 Symbol.iterator。使其具備迭代器功能。從而可使用for of。從而完成了 [1,2,3]{'0':1, '1':2, '2':3}的轉變

因此:上述答案被證實爲正確。

固然,也多是:

有理有據的胡謅。。。

備註:

  1. 我是根據學習PY來去思考JS的數據類型的,對於好比JS的Symbol,Set,Map,沒有去說官方用法,我以爲沒有必要吧。
  2. 我說的一些都是我我的出於一個心流狀態下的一些思考。可能有點問題,可是都是吾之所感。

文末的可愛聲明: 若是轉發或者引用,請貼上原文連接,尊重一下勞動成果😂。文章可能 (確定) 有一些錯誤,歡迎評論指出,也歡迎一塊兒討論。文章可能寫的不夠好,還請多多包涵。人生苦短,我學前端,多一點貢獻,多一分開心,歡迎關注,後續更加精彩哦~

小夥伴以爲我寫得還不錯的話,就點個贊 以茲鼓勵 一下吧😊。

相關文章
相關標籤/搜索