摘要:語言中規定的類型為以及。這兩個值有不同的類型。內建類型定義了七種內建類型中新增提示以上類型,除的被稱為基本類型。新增列出的六種類型的值都會返回一個對應類型名稱的字符串。是中新增的數據類型,我們會在第三章詳細介紹。
譯者的前言
一直都想好好研究這個在 GitHub 上很有名氣的系列,而翻譯恰是最好的閱讀途徑之一。可以讓我閱讀的時候,不那么不求甚解。
圖靈社區出版了該系列兩部分的中文版——《作用域和閉包》以及《this和對象原型》,我就打算從《類型和語法》這本開始做起。
同時,我對本書的翻譯進度會在 GitHub 上同步,希望能有更多的同行參與進來,將更多的干貨貢獻社區。
翻譯 GitHub 倉庫地址:https://github.com/StarStudio/You-Dont-Know-JS
原著:https://github.com/getify/You-Dont-Know-JS
PS:最近對于翻譯英文原版系列很有興趣,如果有好的干貨英文文章(且大家信得過我的水平),可以放在評論區,有時間我一定會翻譯!
第一章:類型大多數開發人員認為,動態語言(如 JavaScript)并沒有類型。讓我們來看看 ES5.1 的 規范 對于這部分內容是怎么說的:
本規范中所有算法所操作的值都有一個類型與之對應。這些值的類型均在本規范中對應。當然,這些類型也可能是 ECMAScript 語言中規定的類型的子類型。
在 ECMAScript 語言中,每個 ECMAScript 類型所對應的值都被 ECMAScript 程序開發人員直接操作。ECMAScript 語言中規定的類型為 Undefined, Null, Boolean, String, Number 以及Object。
如果你是強類型語言(靜態語言)的粉絲,你也許會對這樣使用“類型”感到很反感。在那些語言里,“類型”所擁有的含義可比在 JS 里的多得多。
有人說 JS 不應該聲稱它有“類型,應該把這種東西稱為“標簽”,或是“子類型”。
好吧。我們將使用這一粗略的定義(類似于規范中所描述的):一個類型是一個固有的,內建的特征集,無論是編譯引擎還是開發人員,都可以用它來確定一個值的行為,并把這個值和其他值加以區分。
簡單來說,如果在編譯引擎和開發人員眼里,值 42(數字)和值 "42"(字符串)處理的方法不同,那么我們就說他們有不同的類型—— number 和 string。當你處理 42 時,你將使用一些處理數字的方法,比如數學運算。而當你處理 "42" 時,你則會使用一些字符串處理方法,比如輸出到頁面,等等。這兩個值有不同的類型。
雖然這并不是什么嚴謹的定義,但對于我們接下來的討論,已經綽綽有余了。而且這樣的定義,和JS如何形容自己是一致的。
類型——或是別的什么不考慮學術上的爭論,我們來想想為什么 JavaScript 會需要類型?
對于每種類型及其基本行為都有所了解,有助于更高效的將值進行類型轉換(詳見第四章,類型轉換)。幾乎所有的JS程序,都存在著這樣那樣的類型轉換,所以了解這些,對你來說很重要。
如果你有一個值為 42 的 number,但想對它進行 string 類型的操作,如移除 1 位置的字符 "2",你最好先將這個值的類型從 number 轉換為 string。
這看似很簡單。
但是進行這樣的類型轉換,有很多方式。有些方式很明確,很簡單就能說出來龍去脈,并且也值得信賴.但如果你不夠細心,類型轉換可能以一種匪夷所思的方式展現在你面前。
類型轉換可能是 JavaScript 最大的疑惑之一了。這點經常被視為這一語言的缺陷,是應該避免使用的。
由于有了對 JavaScript 類型的全面了解,我們希望能夠說明為何類型轉換的壞名聲言過其實,甚至是不恰當的——我們會改變你的傳統觀點,讓你看到類型轉換的強大力量和實用性。不過首先,我們先來了解一下值和類型。
內建類型JavaScript 定義了七種內建類型:
null
undefined
boolean
number
string
object
symbol —— ES6 中新增
提示:以上類型,除 object 的被稱為基本類型。
typeof 運算符會檢測所給值得類型,并返回以下其中字符串類型的值——然而奇怪的是,返回的結果和我們剛剛列出的的內建類型并不一一對應。
typeof undefined === "undefined"; // true typeof true === "boolean"; // true typeof 42 === "number"; // true typeof "42" === "string"; // true typeof { life: 42 } === "object"; // true // ES6新增! typeof Symbol() === "symbol"; // true
列出的六種類型的值都會返回一個對應類型名稱的字符串。Symbol 是 ES6 中新增的數據類型,我們會在第三章詳細介紹。
你也許注意到了,我將 null 從列表中除去了。因為他很特殊——當使用 typeof 運算符時,它表現的就像 bug 一樣:
typeof null === "object"; // true
如果它返回的是 "null" 的話,那可真是件好事,可惜的是,這個 bug 已經存在了 20 年,而且由于有太多的 web 程序依賴這一 bug 運行,修復這一 bug 的話,將會創造更多的 bug,并且使很多 web 應用無法運行,所以估計將來也不會修復。
如果你想要確定一個 null 類型的值是這一類型,你需要使用復合判定:
var a = null; (!a && typeof a === "object"); // true
null 是基本類型中唯一值表現的像 false 一樣的類型(詳見第四章),但如果運行 typeof 進行檢查,返回的還是 "object"。
那么,typeof 返回的第七種字符串類型的值是什么?
typeof function a(){ /* .. */ } === "function"; // true
單拍腦袋想的話,很容易理解 function(函數)會是 JS 中頂級的內建類型,尤其是它針對 typeof 運算符的表現。然而,如果你閱讀相關的標準,會發現它實際上是對象類型(object)的子類型。更確切的說,函數是一種“可以被調用的對象”——一類擁有名為 [[Call]] 的內建屬性且可以被調用的對象。
函數實際上是對象這點其實很有用。最重要的一點就是,它可以有屬性。例如:
function a(b,c) { /* .. */ }
該函數具有一個 length 屬性,值為函數形式參數的個數。
a.length; // 2
本例中,函數聲明中包括兩個形參(b 和 c),所以“函數的長度”是 2。
那么數組呢?他們也是 JS 內置的類型,會不會有什么特殊的表現?
typeof [1,2,3] === "object"; // true
然而并沒有,只是普通的對象罷了。一般將它們也視為對象的“子類型”(詳見第三章),與普通對象不同的是,它們可以通過數字來序列化(就像普通對象那樣可以通過字符串類型的 key(鍵)來序列化一樣),并且操作有可以自動更新的 length 屬性。
值和類型在 JavaScript 中,變量不具有類型——值有類型。變量可以在任何時刻保存任何值。
換句話說,JS 并不是強類型的語言,編譯引擎不會讓一個變量始終保存和這個變量最開始所保存的值擁有相同的類型。變量可以保存一個 string 類型的值,并在接下來的賦值操作中保存一個number類型,以此類推。
一個值如 42 是 number 類型的,而且這個類型是不能改變的。另一個值,如 "42" 是 string 類型,可以通過對 number 類型的 42 進行類型轉換(詳見第四章)來得到。
如果你用 typeof 運算符去操作一個變量,看上去就像是在求“變量是什么類型?”,然而 JS 中的變量并不具有類型。所以,其實是在求“變量中保存的值是什么類型?”。
var a = 42; typeof a; // "number" a = true; typeof a; // "boolean"
typeof 運算符返回的必然是字符串類型:
typeof typeof 42; // "string"
其中typeof 42會返回"number",然后typeof "number"就會返回"string"。
undefined vs "undeclared"(未定義和未聲明)當變量沒有被賦值的時候,其值為 undefined。調用 typeof 運算符對它進行操作會返回 "undefined":
var a; typeof a; // "undefined" var b = 42; var c; // 然后另 b = c; typeof b; // "undefined" typeof c; // "undefined"
對于許多開發者都認為“未定義(undefined)”相當于是“未聲明”的代名詞,然而在 JS 中,這兩個概念截然不同。
一個“未定義(undefined)”的變量是已經在當前作用域中聲明了的,只不過是目前它并沒有保存其他的值而已。而“未聲明(undeclared)”則是指在當前作用域中沒有聲明的變量。
考慮如下的示例:
var a; a; // undefined b; // ReferenceError: b is not defined(錯誤的中文大意是:引用錯誤:b 尚未定義)
瀏覽器對于這一錯誤的描述可以說相當讓人困惑。“b 尚未定義”很容易讓人理解成“b 是未定義”。然后,“未定義”和“尚未定義”間的差別實在是太大了。如果瀏覽器要是能報個像“未找到變量 b”或是“b 尚未聲明”之類的錯誤,就不會這么讓人迷糊了。
同樣的,typeof 運算符的特殊行為加重了這一困惑,請看例子:
var a; typeof a; // "undefined" typeof b; // "undefined"
對于“未聲明”或著說“尚未定義”的變量,typeof 會返回 "undefined"。你會發現,雖然 b 是一個沒有聲明的變量,但是當我們執行 typeof b 的時候卻沒有報錯。會出現這種情況,源于 typeof 運算符特殊的安全機制。
和前面的例子一樣,如果對于沒有聲明的變量,typeof 會返回一個“未聲明”之類的東西,而不是將其和“undefined”混為一談的話,就不會有這么多麻煩了。
typeof 對處理未聲明的處理然而,在瀏覽器端這種,多個腳本文件均可以在全局命名空間下加載變量的 JavaScript 環境中,這種安全機制反而很有用。
提示:許多開發者堅信,在全局命名空間下不應該有任何變量,所有的東西都應該在模塊或者是私有/分離的命名空間中。理論上,這很棒,而且確實是我們追求的一個目標,然而在實踐中,這幾乎是不可能的。不過 ES6 中加入了對模塊的支持,這使得我們能夠更接近這一目標。
例如,在你的程序中,你通過一個全局變量 DEBUG 實現了一個調試模式。你希望在開始進行 debug,如在控制臺輸出一條調試信息之前,檢查這個變量是否已經聲明。你可以將全局的 var DEBUG = true 聲明寫在一個名為"debug.js"的文件夾下,當你在進行開發/測試下才在瀏覽器中引入,而不是在生產環境。
而你需要注意的,就是如何去在你的其他代碼中檢查這個全局的 DEBUG 變量,畢竟你可不希望報一個 ReferenceError。在這種場景下,typeof 運算符就成了我們的好幫手。
// 注意,這種方法會報錯! if (DEBUG) { console.log( "Debugging is starting" ); } // 更為安全的檢查方式 if (typeof DEBUG !== "undefined") { console.log( "Debugging is starting" ); }
這類檢查不僅對于用戶定義的變量很有用,當你在見此一個內建的 API 的時候,這種不會拋出錯誤的檢查也非常棒:
if (typeof atob === "undefined") { atob = function() { /*..*/ }; }
提示:當你在對一個目前不存在的特性寫“polyfill(膩子腳本)”的時候,你需要避免用 var 來聲明變量 atob。如果你在 if 語句里面使用 var atob 來聲明,即使 if 語句的條件不滿足,變量的聲明也會被提升到作用域的最頂級(詳見本系列中的《作用域和閉包》)。在部分瀏覽器中,對一些特殊的全局的內建對象類型(常稱為“宿主對象”,如瀏覽器中的 DOM 對象),這種重復的聲明會報錯。所以最好避免使用 var 來阻止變量提升。
另一種不使用 typeof 安全機制,進行檢查的方法,就是利用所有的全局變量都是(global)全局對象(在瀏覽器中就是 window 對象)這一點。所以,上面的檢查還有如下等價的寫法(同樣很安全):
if (window.DEBUG) { // .. } if (!window.atob) { // .. }
和引用一個未聲明的變量不同,當你嘗試獲取一個對象(即便是 window 對象)不存在的屬性的時候,并不會拋出什么 ReferenceError。
而另一方面,一些開發者極力避免使用 window 對象來引用全局變量,尤其是當你的代碼運行在多種 JS 環境(不光是瀏覽器,比如服務端的 node.js)時,全局(global)對象可不一定叫 window。
即便當你不使用全局變量的時候,typeof 的安全機制也有它的用武之地,雖然這種情況很少見,也有一些開發人員認為這種設計并不值得。比如你準備寫一個可供他人復制粘貼的通用函數,想要知道程序中是否定義了某一特定的變量(將會影響你函數的執行),你可以這樣:
function doSomethingCool() { var helper = (typeof FeatureXYZ !== "undefined") ? FeatureXYZ : function() { /*.. 默認值 ..*/ }; var val = helper(); // .. }
doSomethingCool() 會檢查是否存在一個名為 FeatureXYZ 的變量,有的話就使用,沒有的話,就使用默認值。現在,如果有人在他的程序/模塊中使用了這一公共函數,檢查它們是否定義了 FeatureXYZ 就顯得尤為重要:
// IIFE (詳見本系列《作用域和閉包》一書中的立即執行函數表達式) (function(){ function FeatureXYZ() { /*.. my XYZ feature ..*/ } // include `doSomethingCool(..)` function doSomethingCool() { var helper = (typeof FeatureXYZ !== "undefined") ? FeatureXYZ : function() { /*.. default feature ..*/ }; var val = helper(); // .. } doSomethingCool(); })();
在這里,FeatureXYZ 并不是一個全局變量,但我們仍然使用 typeof 運算符的安全機制來檢查。注意到,在這種情況下,我們可沒有全局對象用于這一檢查(像使用 window.___ 那樣),所以 typeof 真的很有幫助。
有些開發者可能會喜歡一種叫做“依賴注入”的設計模式,讓 doSomethingCool() 不去檢查 FeatureXYZ 是否在它外部/附近被定義,而是通過顯示的判斷來確定,如:
function doSomethingCool(FeatureXYZ) { var helper = FeatureXYZ || function() { /*.. 默認值 ..*/ }; var val = helper(); // .. }
要實現這一功能,其實有很多解決方案。沒有一種模式是“對的”或“錯的”——要對各種方法進行權衡。不過總的來說,typeof 的安全機制確實給了我們更多的選擇。
總結JavaScript 擁有七種內建類型:null,undefined,boolean,number,string,object,symbol。可以通過使用 typeof 運算符來對它們進行區分。
變量不具有類型,但值有。這些類型定義了值的行為。
許多開發者會將“未定義(undefined)”和“未聲明”混為一談,但是在 JavaScript 它們完全不同。undefined是一個可供已經聲明的變量保存的值。“未聲明”意味著一個未經聲明的變量。
不幸的是,JavaScript 中很多地方都將兩者混為一談,比如錯誤信息("ReferenceError: a is not defined"),以及用 typeof 操作,兩者都返回 "undefined"。
不過,typeof 這種安全機制(阻止報錯)在某些場景中,如需要檢查一個變量是否存在的時候還是很有用的。
原書 《You Don"t Know JS: Types & Grammar》
本章原文 Chapter 1: Types
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92348.html
摘要:的抽象語法樹中可能會有一個叫作的頂級節點,接下來是一個叫作它的值是的子節點,以及一個叫作的子節點。值得注意的是,是非常重要的異常類型。嚴格模式下,未聲明的和倆者行為相同,都會是。 你不知道的JS(上卷)筆記 你不知道的 JavaScript JavaScript 既是一門充滿吸引力、簡單易用的語言,又是一門具有許多復雜微妙技術的語言,即使是經驗豐富的 JavaScript 開發者,如果...
摘要:最近剛剛看完了你不知道的上卷,對有了更進一步的了解。你不知道的上卷由兩部分組成,第一部分是作用域和閉包,第二部分是和對象原型。附錄詞法這一章并沒有說明機制,只是介紹了中的箭頭函數引入的行為詞法。第章混合對象類類理論類的機制類的繼承混入。 最近剛剛看完了《你不知道的 JavaScript》上卷,對 JavaScript 有了更進一步的了解。 《你不知道的 JavaScript》上卷由兩部...
摘要:強制類型轉換本章介紹了的數據類型之間的轉換即強制類型轉換包括顯式和隱式。強制類型轉換常常為人詬病但實際上很多時候它們是非常有用的。隱式強制類型轉換則沒有那么明顯是其他操作的副作用。在處理強制類型轉換的時候要十分小心尤其是隱式強制類型轉換。 前言 《你不知道的 javascript》是一個前端學習必讀的系列,讓不求甚解的JavaScript開發者迎難而上,深入語言內部,弄清楚JavaSc...
摘要:然而,臨近規范發布時,有建議提及未來的版本號切換為編年制,比如用同來指代在年末前被定稿的所有版本。總得來說就是版本號不再那么重要了,開始變得更像一個萬古長青的活標準。 你不知道的JS(下卷)ES6與之未來 第一章:ES的今與明 在你想深入這本書之前,你應該對(在讀此書時)JavaScript的最近標準掌握熟練,也就是ES5(專業來說是ES 5.1)。在此,我們決定全方面地談論關于將近的...
摘要:這時候控制臺看到的是對象的快照,然而點開看詳情的話是這段代碼在運行的時候,瀏覽器可能會認為需要把控制臺延遲到后臺,這種情況下,等到瀏覽器控制臺輸出對象內容時,可能已經運行,因此會在點開的時候顯示,這是的異步化造成的。 本書屬于基礎類書籍,會有比較多的基礎知識,所以這里僅記錄平常不怎么容易注意到的知識點,不會全記,供大家和自己翻閱; 上中下三本的讀書筆記: 《你不知道的JavaScri...
閱讀 1961·2021-11-23 09:51
閱讀 873·2021-11-19 09:40
閱讀 829·2021-10-27 14:20
閱讀 5004·2021-10-09 09:52
閱讀 3297·2021-10-09 09:44
閱讀 1729·2021-10-08 10:05
閱讀 5053·2021-09-09 11:47
閱讀 3481·2019-08-30 12:47