第二章:進(jìn)入JavaScript

特別說明,為便于查閱,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS

在前一章中订雾,我介紹了編程的基本構(gòu)建塊兒,比如變量矛洞,循環(huán)洼哎,條件,和函數(shù)沼本。當(dāng)然噩峦,所有被展示的代碼都是JavaScript。但是在這一章中抽兆,為了作為一個(gè)JS開發(fā)者入門和進(jìn)階识补,我們想要特別集中于那些你需要知道的關(guān)于JavaScript的事情。

我們將在本章中介紹好幾個(gè)概念辫红,它們將會在后續(xù)的 YDKJS 叢書中全面地探索凭涂。你可以將這一章看作是這個(gè)系列的其他書目中將要詳細(xì)講解的話題的一個(gè)概覽祝辣。

特別是如果你剛剛接觸JavaScript,那么你應(yīng)當(dāng)希望花相當(dāng)一段時(shí)間來多次復(fù)習(xí)這里的概念和代碼示例导盅。任何好的基礎(chǔ)都是一磚一瓦積累起來的较幌,所以不要指望你會在第一遍通讀后就立即理解了全部內(nèi)容揍瑟。

你深入學(xué)習(xí)JavaScript的旅途從這里開始白翻。

注意: 正如我在第一章中說過的,在你通讀這一章的同時(shí)绢片,你絕對應(yīng)該親自嘗試這里所有的代碼滤馍。要注意的是,這里的有些代碼假定最新版本的JavaScript(通常稱為“ES6”底循,ECMAScript的第六個(gè)版本 —— ECMAScript是JS語言規(guī)范的官方名稱)中引入的功能是存在的巢株。如果你碰巧在使用一個(gè)老版本的,前ES6時(shí)代的瀏覽器熙涤,這些代碼可能不好用阁苞。應(yīng)當(dāng)使用一個(gè)更新版本的現(xiàn)代瀏覽器(比如Chrome,F(xiàn)irefox祠挫,或者IE)那槽。

值與類型

正如我們在第一章中宣稱的,JavaScript擁有帶類型的值等舔,沒有帶類型的變量骚灸。下面是可用的內(nèi)建類型:

  • string
  • number
  • boolean
  • nullundefined
  • object
  • symbol (ES6新增類型)

JavaScript提供了一個(gè)typeof操作符,它可以檢查一個(gè)值并告訴你它的類型是什么:

var a;
typeof a;               // "undefined"

a = "hello world";
typeof a;               // "string"

a = 42;
typeof a;               // "number"

a = true;
typeof a;               // "boolean"

a = null;
typeof a;               // "object" -- 奇怪的bug

a = undefined;
typeof a;               // "undefined"

a = { b: "c" };
typeof a;               // "object"

來自typeof的返回值總是六個(gè)(ES6中是七個(gè)慌植! —— “symbol”類型)字符串值之一甚牲。也就是,typeof "abc"返回"string"蝶柿,不是string丈钙。

注意在這個(gè)代碼段中變量a是如何持有每種不同類型的值的,而且盡管表面上看起來很像交汤,但是typeof a并不是在詢問“a的類型”雏赦,而是“當(dāng)前a中的值的類型”。在JavaScript中只有值擁有類型蜻展;變量只是這些值的簡單容器喉誊。

typeof null是一個(gè)有趣的例子,因?yàn)楫?dāng)你期望它返回"null"時(shí)纵顾,它錯(cuò)誤地返回了"object"伍茄。

警告: 這是JS中一直存在的一個(gè)bug,但是看起來它永遠(yuǎn)都不會被修復(fù)了施逾。在網(wǎng)絡(luò)上有太多的代碼依存于這個(gè)bug敷矫,因此修復(fù)它將會導(dǎo)致更多的bug例获!

另外,注意a = undefined曹仗。我們明確地將a設(shè)置為值undefined榨汤,但是在行為上這與一個(gè)還沒有被設(shè)定值的變量沒有區(qū)別,比如在這個(gè)代碼段頂部的var a;怎茫。一個(gè)變量可以用好幾種不同的方式得到這樣的“undefined”值狀態(tài)收壕,包括沒有返回值的函數(shù)和使用void操作符。

對象

object類型指的是一種復(fù)合值轨蛤,你可以在它上面設(shè)定屬性(帶名稱的位置)蜜宪,每個(gè)屬性持有各自的任意類型的值。它也許是JavaScript中最有用的類型之一祥山。

var obj = {
    a: "hello world",
    b: 42,
    c: true
};

obj.a;      // "hello world"
obj.b;      // 42
obj.c;      // true

obj["a"];   // "hello world"
obj["b"];   // 42
obj["c"];   // true

可視化地考慮這個(gè)obj值可能會有所幫助:

屬性既可以使用 點(diǎn)號標(biāo)記法(例如圃验,obj.a) 訪問,也可以使用 方括號標(biāo)記法(例如缝呕,obj["a"]) 訪問澳窑。點(diǎn)號標(biāo)記法更短而且一般來說更易于閱讀,因此在可能的情況下它都是首選供常。

如果你有一個(gè)名稱中含有特殊字符的屬性名稱摊聋,方括號標(biāo)記法就很有用,比如obj["hello world!"] —— 當(dāng)通過方括號標(biāo)記法訪問時(shí)话侧,這樣的屬性經(jīng)常被稱為 栗精。[ ]標(biāo)記法要求一個(gè)變量(下一節(jié)講解)或者一個(gè)string 字面量(它需要包裝進(jìn)" .. "' .. ')。

當(dāng)然瞻鹏,如果你想訪問一個(gè)屬性/鍵悲立,但是它的名稱被存儲在另一個(gè)變量中時(shí),方括號標(biāo)記法也很有用新博。例如:

var obj = {
    a: "hello world",
    b: 42
};

var b = "a";

obj[b];         // "hello world"
obj["b"];       // 42

注意: 更多關(guān)于JavaScript的object的信息薪夕,請參見本系列的 this與對象原型,特別是第三章赫悄。

在JavaScript程序中有另外兩種你將會經(jīng)常打交道的值類型:數(shù)組函數(shù)原献。但與其說它們是內(nèi)建類型,這些類型應(yīng)當(dāng)被認(rèn)為更像是子類型 —— object類型的特化版本埂淮。

數(shù)組

一個(gè)數(shù)組是一個(gè)object姑隅,它不使用特殊的帶名稱的屬性/鍵持有(任意類型的)值,而是使用數(shù)字索引的位置倔撞。例如:

var arr = [
    "hello world",
    42,
    true
];

arr[0];         // "hello world"
arr[1];         // 42
arr[2];         // true
arr.length;     // 3

typeof arr;     // "object"

注意: 從零開始計(jì)數(shù)的語言讲仰,比如JS,在數(shù)組中使用0作為第一個(gè)元素的索引痪蝇。

可視化地考慮arr很能會有所幫助:

因?yàn)閿?shù)組是一種特殊的對象(正如typeof所暗示的)鄙陡,所以它們可以擁有屬性冕房,包括一個(gè)可以自動被更新的length屬性。

理論上你可以使用你自己的命名屬性將一個(gè)數(shù)組用作一個(gè)普通對象趁矾,或者你可以使用一個(gè)object但是給它類似于數(shù)組的數(shù)字屬性(0耙册,1,等等)毫捣。然而详拙,這么做一般被認(rèn)為是分別誤用了這兩種類型。

最好且最自然的方法是為數(shù)字定位的值使用數(shù)組培漏,而為命名屬性使用object溪厘。

函數(shù)

另一個(gè)你將在JS程序中到處使用的object子類型是函數(shù):

function foo() {
    return 42;
}

foo.bar = "hello world";

typeof foo;         // "function"
typeof foo();       // "number"
typeof foo.bar;     // "string"

同樣地胡本,函數(shù)也是object的子類型 —— typeof返回"function"牌柄,這暗示著"function"是一種主要類型 —— 因此也可以擁有屬性,但是你一般僅會在有限情況下才使用函數(shù)對象屬性(比如foo.bar)侧甫。

注意: 更多關(guān)于JS的值和它們的類型的信息珊佣,參見本系列的 類型與文法 的前兩章。

內(nèi)建類型的方法

我們剛剛討論的內(nèi)建類型和子類型擁有十分強(qiáng)大和有用的行為披粟,它們作為屬性和方法暴露出來咒锻。

例如:

var a = "hello world";
var b = 3.14159;

a.length;               // 11
a.toUpperCase();        // "HELLO WORLD"
b.toFixed(4);           // "3.1416"

使調(diào)用a.toUpperCase()成為可能的原因,要比這個(gè)值上存在這個(gè)方法的說法復(fù)雜一些守屉。

簡而言之惑艇,有一個(gè)StringS大寫)對象包裝器形式,通常被稱為“原生類型”拇泛,與string基本類型配成一對兒滨巴;正是這個(gè)對象包裝器的原型上定義了toUpperCase()方法。

當(dāng)你通過引用一個(gè)屬性或方法(例如俺叭,前一個(gè)代碼段中的a.toUpperCase())將一個(gè)像"hello world"這樣的基本類型值當(dāng)做一個(gè)object來使用時(shí)恭取,JS自動地將這個(gè)值“封箱”為它對應(yīng)的對象包裝器(這個(gè)操作是隱藏在幕后的)。

一個(gè)string值可以被包裝為一個(gè)String對象熄守,一個(gè)number可以被包裝為一個(gè)Number對象蜈垮,而一個(gè)boolean可以被包裝為一個(gè)Boolean對象。在大多數(shù)情況下裕照,你不擔(dān)心或者直接使用這些值的對象包裝器形式 —— 在所有實(shí)際情況中首選基本類型值形式攒发,而JavaScript會幫你搞定剩下的一切。

注意: 關(guān)于JS原生類型和“封箱”的更多信息晋南,參見本系列的 類型與文法 的第三章惠猿。要更好地理解對象原型,參見本系列的 this與對象原型 的第五章搬俊。

值的比較

在你的JS程序中你將需要進(jìn)行兩種主要的值的比較:等價(jià)不等價(jià)紊扬。任何比較的結(jié)果都是嚴(yán)格的boolean值(truefalse)蜒茄,無論被比較的值的類型是什么。

強(qiáng)制轉(zhuǎn)換

在第一章中我們簡單地談了一下強(qiáng)制轉(zhuǎn)換餐屎,我們在此回顧它檀葛。

在JavaScript中強(qiáng)制轉(zhuǎn)換有兩種形式:明確的隱含的。明確的強(qiáng)制轉(zhuǎn)換比較簡單腹缩,因?yàn)槟憧梢栽诖a中明顯地看到一個(gè)類型轉(zhuǎn)換到另一個(gè)類型將會發(fā)生屿聋,而隱含的強(qiáng)制轉(zhuǎn)換更像是另外一些操作的不明顯的副作用引發(fā)的類型轉(zhuǎn)換。

你可能聽到過像“強(qiáng)制轉(zhuǎn)換是邪惡的”這樣情緒化的觀點(diǎn)藏鹊,這是因?yàn)橐粋€(gè)清楚的事實(shí) —— 強(qiáng)制轉(zhuǎn)換在某些地方會產(chǎn)生一些令人吃驚的結(jié)果润讥。也許沒有什么能比當(dāng)一個(gè)語言嚇到開發(fā)者時(shí)更能喚起他們的沮喪心情了。

強(qiáng)制轉(zhuǎn)換并不邪惡盘寡,它也不一定是令人吃驚的楚殿。事實(shí)上,你使用類型強(qiáng)制轉(zhuǎn)換構(gòu)建的絕大部分情況是十分合理和可理解的竿痰,而且它甚至可以用來 增強(qiáng) 你代碼的可讀性脆粥。但我們不會在這個(gè)話題上過度深入 —— 本系列的 類型與文法 的第四章將會進(jìn)行全面講解。

這是一個(gè) 明確 強(qiáng)制轉(zhuǎn)換的例子:

var a = "42";

var b = Number( a );

a;              // "42"
b;              // 42 -- 數(shù)字影涉!

而這是一個(gè) 隱含 強(qiáng)制轉(zhuǎn)換的例子:

var a = "42";

var b = a * 1;  // 這里 "42" 被隱含地強(qiáng)制轉(zhuǎn)換為 42

a;              // "42"
b;              // 42 -- 數(shù)字变隔!

Truthy 與 Falsy

在第一章中,我們簡要地提到了值的“truthy”和“falsy”性質(zhì):當(dāng)一個(gè)非boolean值被強(qiáng)制轉(zhuǎn)換為一個(gè)boolean時(shí)蟹倾,它是變成true還是false匣缘。

在JavaScript中“falsy”的明確列表如下:

  • "" (空字符串)
  • 0, -0, NaN (非法的number
  • null, undefined
  • false

任何不在這個(gè)“falsy”列表中的值都是“truthy”。這是其中的一些例子:

  • "hello"
  • 42
  • true
  • [ ], [ 1, "2", 3 ] (數(shù)組)
  • { }, { a: 42 } (對象)
  • function foo() { .. } (函數(shù))

重要的是要記住鲜棠,一個(gè)非boolean值僅在實(shí)際上被強(qiáng)制轉(zhuǎn)換為一個(gè)boolean時(shí)才遵循這個(gè)“truthy”/“falsy”強(qiáng)制轉(zhuǎn)換肌厨。把你搞糊涂并不困難 —— 當(dāng)一個(gè)場景看起來像是將一個(gè)值強(qiáng)制轉(zhuǎn)換為boolean,可其實(shí)它不是岔留。

等價(jià)性

有四種等價(jià)性操作符:==夏哭,===!=献联,和!==竖配。!形式當(dāng)然是與它們相對應(yīng)操作符平行的“不等”版本;不等(non-equality) 不應(yīng)當(dāng)與 不等價(jià)性(inequality) 相混淆里逆。

=====之間的不同通常被描述為进胯,==檢查值的等價(jià)性而===檢查值和類型兩者的等價(jià)性。然而原押,這是不準(zhǔn)確的胁镐。描述它們的合理方式是,==在允許強(qiáng)制轉(zhuǎn)換的條件下檢查值的等價(jià)性,而===是在不允許強(qiáng)制轉(zhuǎn)換的條件下檢查值的等價(jià)性盯漂;因此===常被稱為“嚴(yán)格等價(jià)”颇玷。

考慮這個(gè)隱含強(qiáng)制轉(zhuǎn)換,它在==寬松等價(jià)性比較中允許就缆,而===嚴(yán)格等價(jià)性比較中不允許:

var a = "42";
var b = 42;

a == b;         // true
a === b;        // false

a == b的比較中帖渠,JS注意到類型不匹配,于是它經(jīng)過一系列有順序的步驟將一個(gè)值或者它們兩者強(qiáng)制轉(zhuǎn)換為一個(gè)不同的類型竭宰,直到類型匹配為止空郊,然后就可以檢查一個(gè)簡單的值等價(jià)性。

如果你仔細(xì)想一想切揭,通過強(qiáng)制轉(zhuǎn)換a == b可以有兩種方式給出true狞甚。這個(gè)比較要么最終成為42 == 42,要么成為"42" == "42"廓旬。那么是哪一種呢哼审?

答案:"42"變成42,于是比較成為42 == 42嗤谚。在一個(gè)這樣簡單的例子中棺蛛,只要最終結(jié)果是一樣的,處理的過程走哪一條路看起來并不重要巩步。但在一些更復(fù)雜的情況下,這不僅對比較的最終結(jié)果很重要桦踊,而且對你 如何 得到這個(gè)結(jié)果也很重要椅野。

a === b產(chǎn)生false,因?yàn)閺?qiáng)制轉(zhuǎn)換是不允許的籍胯,所以簡單值的比較很明顯將會失敗竟闪。許多開發(fā)者感覺===更可靠,所以他們提倡一直使用這種形式而遠(yuǎn)離==杖狼。我認(rèn)為這種觀點(diǎn)是非常短視的炼蛤。我相信==是一種可以改進(jìn)程序的強(qiáng)大工具,如果你花時(shí)間去學(xué)習(xí)它的工作方式蝶涩。

我們不會詳細(xì)地講解強(qiáng)制轉(zhuǎn)換在==比較中是如何工作的理朋。它的大部分都是相當(dāng)合理的,但是有一些重要的極端用例要小心绿聘。你可以閱讀ES5語言規(guī)范的11.9.3部分(http://www.ecma-international.org/ecma-262/5.1/)來了解確切的規(guī)則嗽上,而且與圍繞這種機(jī)制的所有負(fù)面炒作比起來,你會對這它是多么的直白而感到吃驚熄攘。

為了將這許多細(xì)節(jié)歸納為一個(gè)簡單的包裝,并幫助你在各種情況下判斷是否使用=====,這是我的簡單規(guī)則:

  • 如果一個(gè)比較的兩個(gè)值之一可能是truefalse值掩驱,避免==而使用===
  • 如果一個(gè)比較的兩個(gè)值之一可能是這些具體的值(0逐沙,"",或[] —— 空數(shù)組)洼畅,避免==而使用===酱吝。
  • 所有 其他情況下,你使用==是安全的土思。它不僅安全务热,而且在許多情況下它可以簡化你的代碼并改善可讀性。

這些規(guī)則歸納出來的東西要求你嚴(yán)謹(jǐn)?shù)乜紤]你的代碼:什么樣的值可能通過這個(gè)被比較等價(jià)性的變量己儒。如果你可以確定這些值崎岂,那么==就是安全的,使用它闪湾!如果你不能確定這些值冲甘,就使用===。就這么簡單途样。

!=不等價(jià)形式對應(yīng)于==江醇,而!==形式對應(yīng)于===。我們剛剛討論的所有規(guī)則和注意點(diǎn)對這些非等價(jià)比較都是平行適用的何暇。

如果你在比較兩個(gè)非基本類型值陶夜,比如object(包括functionarray),那么你應(yīng)當(dāng)特別小心=====的比較規(guī)則裆站。因?yàn)檫@些值實(shí)際上是通過引用持有的条辟,=====比較都將簡單地檢查這個(gè)引用是否相同,而不是它們底層的值宏胯。

例如?羽嫡,array默認(rèn)情況下會通過使用逗號(,)連接所有值來被強(qiáng)制轉(zhuǎn)換為string。你可能認(rèn)為兩個(gè)內(nèi)容相同的array將是==相等的肩袍,但它們不是:

var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";

a == c;     // true
b == c;     // true
a == b;     // false

注意: 更多關(guān)于==等價(jià)性比較規(guī)則的信息杭棵,參見ES5語言規(guī)范(11.9.3部分),和本系列的 類型與文法 的第四章氛赐;更多關(guān)于值和引用的信息魂爪,參見它的第二章。

不等價(jià)性

<鹰祸,>甫窟,<=,和>=操作符用于不等價(jià)性比較蛙婴,在語言規(guī)范中被稱為“關(guān)系比較”粗井。一般來說它們將與number這樣的可比較有序值一起使用。3 < 4是很容易理解的。

但是JavaScriptstring值也可進(jìn)行不等價(jià)性比較浇衬,它使用典型的字母順序規(guī)則("bar" < "foo")懒构。

那么強(qiáng)制轉(zhuǎn)換呢?與==比較相似的規(guī)則(雖然不是完全相同T爬蕖)也適用于不等價(jià)操作符胆剧。要注意的是,沒有像===嚴(yán)格等價(jià)操作符那樣不允許強(qiáng)制轉(zhuǎn)換的“嚴(yán)格不等價(jià)”操作符醉冤。

考慮如下代碼:

var a = 41;
var b = "42";
var c = "43";

a < b;      // true
b < c;      // true

這里發(fā)生了什么秩霍?在ES5語言規(guī)范的11.8.5部分中,它說如果<比較的兩個(gè)值都是string蚁阳,就像b < c铃绒,那么這個(gè)比較將會以字典順序(也就是像字典中字母的排列順序)進(jìn)行。但如果兩個(gè)值之一不是string螺捐,就像a < b颠悬,那么兩個(gè)值就將被強(qiáng)制轉(zhuǎn)換成number,并進(jìn)行一般的數(shù)字比較定血。

在可能不同類型的值之間進(jìn)行比較時(shí)赔癌,你可能遇到的最大的坑 —— 記住,沒有“嚴(yán)格不等價(jià)”可用 —— 是其中一個(gè)值不能轉(zhuǎn)換為合法的數(shù)字澜沟,例如:

var a = 42;
var b = "foo";

a < b;      // false
a > b;      // false
a == b;     // false

等一下灾票,這三個(gè)比較怎么可能都是false?因?yàn)樵?code><和>的比較中倔喂,值b被強(qiáng)制轉(zhuǎn)換為了“非法的數(shù)字值”铝条,而且語言規(guī)范說NaN既不大于其他值,也不小于其他值席噩。

==比較失敗于不同的原因。如果a == b被解釋為42 == NaN或者"42" == "foo"都會失敗 —— 正如我們前面講過的贤壁,這里是前一種情況悼枢。

注意: 關(guān)于不等價(jià)比較規(guī)則的更多信息,參見ES5語言規(guī)范的11.8.5部分脾拆,和本系列的 類型與文法 第四章馒索。

變量

在JavaScript中,變量名(包括函數(shù)名)必須是合法的 標(biāo)識符(identifiers)名船。當(dāng)你考慮非傳統(tǒng)意義上的字符時(shí)绰上,比如Unicode,標(biāo)識符中合法字符的嚴(yán)格和完整的規(guī)則就有點(diǎn)兒復(fù)雜渠驼。如果你僅考慮典型的ASCII字母數(shù)字的字符蜈块,那么這個(gè)規(guī)則還是很簡單的。

一個(gè)標(biāo)識符必須以a-zA-Z百揭,$爽哎,或_開頭。它可以包含任意這些字符外加數(shù)字0-9器一。

一般來說课锌,變量標(biāo)識符的規(guī)則也通用適用于屬性名稱。然而祈秕,有一些不能用作變量名渺贤,但是可以用作屬性名的單詞。這些單詞被稱為“保留字(reserved words)”请毛,包括JS關(guān)鍵字(for志鞍,inif获印,等等)和null述雾,truefalse

注意: 更多關(guān)于保留字的信息兼丰,參見本系列的 類型與文法 的附錄A玻孟。

函數(shù)作用域

你使用var關(guān)鍵字聲明的變量將屬于當(dāng)前的函數(shù)作用域,如果聲明位于任何函數(shù)外部的頂層鳍征,它就屬于全局作用域黍翎。

提升

無論var出現(xiàn)在一個(gè)作用域內(nèi)部的何處,這個(gè)聲明都被認(rèn)為是屬于整個(gè)作用域艳丛,而且在作用域的所有位置都是可以訪問的匣掸。

這種行為稱為 提升,比喻一個(gè)var聲明在概念上 被移動 到了包含它的作用域的頂端氮双。技術(shù)上講碰酝,這個(gè)過程通過代碼的編譯方式進(jìn)行解釋更準(zhǔn)確,但是我們先暫且跳過那些細(xì)節(jié)戴差。

考慮如下代碼:

var a = 2;

foo();                  // 可以工作送爸, 因?yàn)?`foo()` 聲明被“提升”了

function foo() {
    a = 3;

    console.log( a );   // 3

    var a;              // 聲明被“提升”到了 `foo()` 的頂端
}

console.log( a );   // 2

警告: 在一個(gè)作用域中依靠變量提升來在var聲明出現(xiàn)之前使用一個(gè)變量是不常見的,也不是個(gè)好主意暖释;它可能相當(dāng)使人困惑袭厂。而使用被提升的函數(shù)聲明要常見得多,也更為人所接受球匕,就像我們在foo()正式聲明之前就調(diào)用它一樣纹磺。

嵌套的作用域

當(dāng)你聲明了一個(gè)變量時(shí),它就在這個(gè)作用域內(nèi)的任何地方都是可用的亮曹,包括任何下層/內(nèi)部作用域橄杨。例如:

function foo() {
    var a = 1;

    function bar() {
        var b = 2;

        function baz() {
            var c = 3;

            console.log( a, b, c ); // 1 2 3
        }

        baz();
        console.log( a, b );        // 1 2
    }

    bar();
    console.log( a );               // 1
}

foo();

注意cbar()的內(nèi)部是不可用的秘症,因?yàn)樗莾H在內(nèi)部的baz()作用域中被聲明的,并且b因?yàn)橥瑯拥脑蛟?code>foo()內(nèi)是不可用的讥珍。

如果你試著在一個(gè)作用域內(nèi)訪問一個(gè)不可用的變量的值历极,你就會得到一個(gè)被拋出的ReferenceError。如果你試著為一個(gè)還沒有被聲明的變量賦值衷佃,那么根據(jù)“strict模式”的狀態(tài)趟卸,你會要么得到一個(gè)在頂層全局作用域中創(chuàng)建的變量(不好!)氏义,要么得到一個(gè)錯(cuò)誤锄列。讓我們看一下:

function foo() {
    a = 1;  // `a` 沒有被正式聲明
}

foo();
a;          // 1 -- 噢,自動全局變量 :(

這是一種非常差勁兒的做法惯悠。別這么干邻邮!總是給你的變量進(jìn)行正式聲明。

除了在函數(shù)級別為變量創(chuàng)建聲明克婶,ES6允許你使用let關(guān)鍵字聲明屬于個(gè)別塊兒(一個(gè){ .. })的變量筒严。除了一些微妙的細(xì)節(jié),作用域規(guī)則將大致上與我們剛剛看到的函數(shù)相同:

function foo() {
    var a = 1;

    if (a >= 1) {
        let b = 2;

        while (b < 5) {
            let c = b * 2;
            b++;

            console.log( a + c );
        }
    }
}

foo();
// 5 7 9

因?yàn)槭褂昧?code>let而非var情萤,b將僅屬于if語句而不是整個(gè)foo()函數(shù)的作用域鸭蛙。相似地,c僅屬于while循環(huán)筋岛。對于以更加細(xì)粒度的方式管理你的變量作用域來說娶视,塊兒作用域是非常有用的,它將使你的代碼隨著時(shí)間的推移更加易于維護(hù)睁宰。

注意: 關(guān)于作用域的更多信息肪获,參見本系列的 作用域與閉包。更多關(guān)于let塊兒作用域的信息柒傻,參見本系列的 ES6與未來孝赫。

條件

除了我們在第一章中簡要介紹過的if語句,JavaScript還提供了幾種其他值得我們一看的條件機(jī)制红符。

有時(shí)你可能發(fā)現(xiàn)自己在像這樣寫一系列的if..else..if語句:

if (a == 2) {
    // 做一些事情
}
else if (a == 10) {
    // 做另一些事請
}
else if (a == 42) {
    // 又是另外一些事情
}
else {
    // 這里是備用方案
}

這種結(jié)構(gòu)好用寒锚,但有一點(diǎn)兒繁冗,因?yàn)槟阈枰獮槊恳环N情況都指明a的測試违孝。這里有另一種選項(xiàng),switch語句:

switch (a) {
    case 2:
        // 做一些事情
        break;
    case 10:
        // 做另一些事請
        break;
    case 42:
        // 又是另外一些事情
        break;
    default:
        // 這里是備用方案
}

如果你想僅讓一個(gè)case中的語句運(yùn)行泳赋,break是很重要的雌桑。如果你在一個(gè)case中省略了break,并且這個(gè)case成立或運(yùn)行祖今,那么程序的執(zhí)行將會不管下一個(gè)case語句是否成立而繼續(xù)執(zhí)行它校坑。這種所謂的“掉落”有時(shí)是有用/期望的:

switch (a) {
    case 2:
    case 10:
        // 一些很酷的事情
        break;
    case 42:
        // 另一些事情
        break;
    default:
        // 備用方案
}

這里拣技,如果a210,它就會執(zhí)行“一些很酷的事情”的代碼語句耍目。

在JavaScript中的另一種條件形式是“條件操作符”膏斤,經(jīng)常被稱為“三元操作符”。它像是一個(gè)單獨(dú)的if..else語句的更簡潔的形式邪驮,比如:

var a = 42;

var b = (a > 41) ? "hello" : "world";

// 與此相似:

// if (a > 41) {
//    b = "hello";
// }
// else {
//    b = "world";
// }

如果測試表達(dá)式(這里是a > 41)求值為true莫辨,那么就會得到第一個(gè)子句("hello"),否則得到第二個(gè)子句("world")毅访,而且無論結(jié)果為何都會被賦值給b沮榜。

條件操作符不一定非要用于賦值,但是這絕對是最常見的用法喻粹。

注意: 關(guān)于測試條件和switch? :的其他模式的更多信息蟆融,參見本系列的 類型與文法

Strict模式

ES5在語言中加入了一個(gè)“strict模式”守呜,它收緊了一些特定行為的規(guī)則型酥。一般來說,這些限制被視為使代碼符合一組更安全和更合理的指導(dǎo)方針查乒。另外弥喉,堅(jiān)持strict模式一般會使你的代碼對引擎有更強(qiáng)的可優(yōu)化性。strict模式對代碼有很大的好處侣颂,你應(yīng)當(dāng)在你所有的程序中使用它档桃。

根據(jù)你擺放strict模式注解的位置,你可以為一個(gè)單獨(dú)的函數(shù)憔晒,或者是整個(gè)一個(gè)文件切換到strict模式:

function foo() {
    "use strict";

    // 這部分代碼是strict模式的

    function bar() {
        // 這部分代碼是strict模式的
    }
}

// 這部分代碼不是strict模式的

將它與這個(gè)相比:

"use strict";

function foo() {
    // 這部分代碼是strict模式的

    function bar() {
        // 這部分代碼是strict模式的
    }
}

// 這部分代碼是strict模式的

使用strict模式的一個(gè)關(guān)鍵不同(改善T逡蕖)是,它不允許因?yàn)槭÷粤?code>var而進(jìn)行隱含的自動全局變量聲明:

function foo() {
    "use strict";   // 打開strict模式
    a = 1;          // 缺少`var`拒担,ReferenceError
}

foo();

如果你在代碼中打開strict模式嘹屯,并且得到錯(cuò)誤,或者代碼開始變得有bug从撼,這可能會誘使你避免使用strict模式州弟。但是縱容這種直覺不是一個(gè)好主意。如果strict模式在你的程序中導(dǎo)致了問題低零,那么這標(biāo)志著在你的代碼中幾乎可以肯定有應(yīng)該修改的東西婆翔。

strict模式不僅將你的代碼保持在更安全的道路上,也不僅將使你的代碼可優(yōu)化性更強(qiáng)掏婶,它還代表著這種語言未來的方向啃奴。對于你來說,現(xiàn)在就開始習(xí)慣于strict模式要比一直回避它容易得多 —— 以后再進(jìn)行這種轉(zhuǎn)變只會更難雄妥!

注意: 關(guān)于strict模式的更多信息最蕾,參見本系列的 類型與文法 的第五章依溯。

函數(shù)作為值

至此,我們已經(jīng)將函數(shù)作為JavaScript中主要的 作用域 機(jī)制討論過了瘟则。你可以回想一下典型的function聲明語法是這樣的:

function foo() {
    // ..
}

雖然從這種語法中看起來不明顯黎炉,foo基本上是一個(gè)位于外圍作用域的變量,它給了被聲明的function一個(gè)引用醋拧。也就是說慷嗜,function本身是一個(gè)值,就像42[1,2,3]一樣趁仙。

這可能聽起來像是一個(gè)奇怪的概念洪添,所以花點(diǎn)兒時(shí)間仔細(xì)考慮一下。你不僅可以向一個(gè)function傳遞一個(gè)值(參數(shù)值)雀费,而且 一個(gè)函數(shù)本身可以是一個(gè)值干奢,它能夠賦值給變量,傳遞給其他函數(shù)盏袄,或者從其它函數(shù)中返回忿峻。

因此,一個(gè)函數(shù)值應(yīng)當(dāng)被認(rèn)為是一個(gè)表達(dá)式辕羽,與任何其他的值或表達(dá)式很相似逛尚。

考慮如下代碼:

var foo = function() {
    // ..
};

var x = function bar(){
    // ..
};

第一個(gè)被賦值給變量foo的函數(shù)表達(dá)式稱為 匿名 函數(shù)表達(dá)式,因?yàn)樗鼪]有“名稱”刁愿。

第二個(gè)函數(shù)表達(dá)式是 命名的bar)绰寞,它還被賦值給變量x作為它的引用。命名函數(shù)表達(dá)式 一般來說更理想铣口,雖然 匿名函數(shù)表達(dá)式 仍然極其常見滤钱。

更多信息參見本系列的 作用域與閉包

立即被調(diào)用的函數(shù)表達(dá)式(IIFE)

在前一個(gè)代碼段中脑题,哪一個(gè)函數(shù)表達(dá)式都沒有被執(zhí)行 —— 除非我們使用了foo()x()件缸。

有另一種執(zhí)行函數(shù)表達(dá)式的方法,它通常被稱為一個(gè) 立即被調(diào)用的函數(shù)表達(dá)式 (IIFE):

(function IIFE(){
    console.log( "Hello!" );
})();
// "Hello!"

圍繞在函數(shù)表達(dá)式(function IIFE(){ .. })外部的( .. )只是一個(gè)微妙的JS文法叔遂,我們需要它來防止函數(shù)表達(dá)式被看作一個(gè)普通的函數(shù)聲明他炊。

在表達(dá)式末尾的最后的() —— })();這一行 —— 才是實(shí)際立即執(zhí)行它前面的函數(shù)表達(dá)式的東西。

這看起來可能很奇怪已艰,但它不像第一眼看上去那么陌生痊末。考慮這里的fooIIFE之間的相似性:

function foo() { .. }

// `foo` 是函數(shù)引用表達(dá)式哩掺,然后用`()`執(zhí)行它
foo();

// `IIFE` 是函數(shù)表達(dá)式舌胶,然后用`()`執(zhí)行它
(function IIFE(){ .. })();

如你所見,在執(zhí)行它的()之前列出(function IIFE(){ .. })疮丛,與在執(zhí)行它的()之前定義foo實(shí)質(zhì)上是相同的幔嫂;在這兩種情況下,函數(shù)引用都使用立即在它后面的()執(zhí)行誊薄。

因?yàn)镮IFE只是一個(gè)函數(shù)履恩,而函數(shù)可以創(chuàng)建變量 作用域,以這樣的風(fēng)格使用一個(gè)IIFE經(jīng)常被用于定義變量呢蔫,而這些變量將不會影響圍繞在IIFE外面的代碼:

var a = 42;

(function IIFE(){
    var a = 10;
    console.log( a );   // 10
})();

console.log( a );       // 42

IIFE還可以有返回值:

var x = (function IIFE(){
    return 42;
})();

x;  // 42

42從被執(zhí)行的命名為IIFE的函數(shù)中return切心,然后被賦值給x

閉包

閉包 是JavaScript中最重要片吊,卻又經(jīng)常最少為人知的概念之一绽昏。我不會在這里涵蓋更深的細(xì)節(jié),你可以參照本系列的 作用域與閉包俏脊。但我想說幾件關(guān)于它的事情全谤,以便你了解它的一般概念。它將是你的JS技術(shù)結(jié)構(gòu)中最重要的技術(shù)之一爷贫。

你可以認(rèn)為閉包是這樣一種方法:即使函數(shù)已經(jīng)完成了運(yùn)行认然,它依然可以“記住”并持續(xù)訪問函數(shù)的作用域。

考慮如下代碼:

function makeAdder(x) {
    // 參數(shù) `x` 是一個(gè)內(nèi)部變量

    // 內(nèi)部函數(shù) `add()` 使用 `x`漫萄,所以它對 `x` 擁有一個(gè)“閉包”
    function add(y) {
        return y + x;
    };

    return add;
}

每次調(diào)用外部的makeAdder(..)所返回的對內(nèi)部add(..)函數(shù)的引用可以記住被傳入makeAdder(..)x值【碓保現(xiàn)在,讓我們使用makeAdder(..)

// `plusOne` 得到一個(gè)指向內(nèi)部函數(shù) `add(..)` 的引用腾务,
// `add()` 函數(shù)擁有對外部 `makeAdder(..)` 的參數(shù) `x`
// 的閉包
var plusOne = makeAdder( 1 );

// `plusTen` 得到一個(gè)指向內(nèi)部函數(shù) `add(..)` 的引用毕骡,
// `add()` 函數(shù)擁有對外部 `makeAdder(..)` 的參數(shù) `x`
// 的閉包
var plusTen = makeAdder( 10 );

plusOne( 3 );       // 4  <-- 1 + 3
plusOne( 41 );      // 42 <-- 1 + 41

plusTen( 13 );      // 23 <-- 10 + 13

這段代碼的工作方式是:

  1. 當(dāng)我們調(diào)用makeAdder(1)時(shí),我們得到一個(gè)指向它內(nèi)部的add(..)的引用岩瘦,它記住了x1未巫。我們稱這個(gè)函數(shù)引用為plusOne(..)
  2. 當(dāng)我們調(diào)用makeAdder(10)時(shí)担钮,我們得到了另一個(gè)指向它內(nèi)部的add(..)引用橱赠,它記住了x10。我們稱這個(gè)函數(shù)引用為plusTen(..)箫津。
  3. 當(dāng)我們調(diào)用plusOne(3)時(shí)狭姨,它在3(它內(nèi)部的y)上加1(被x記住的),于是我們得到結(jié)果4苏遥。
  4. 當(dāng)我們調(diào)用plusTen(13)時(shí)饼拍,它在13(它內(nèi)部的y)上加10(被x記住的),于是我們得到結(jié)果23田炭。

如果這看起來很奇怪和令人困惑师抄,不要擔(dān)心 —— 它確實(shí)是的!要完全理解它需要很多的練習(xí)教硫。

但是相信我叨吮,一旦你理解了它辆布,它就是編程中最強(qiáng)大最有用的技術(shù)之一。讓你的大腦在閉包中煎熬一會是絕對值得的茶鉴。在下一節(jié)中锋玲,我們將進(jìn)一步實(shí)踐閉包。

模塊

在JavaScript中閉包最常見的用法就是模塊模式涵叮。模塊讓你定義對外面世界不可見的私有實(shí)現(xiàn)細(xì)節(jié)(變量惭蹂,函數(shù)),和對外面可訪問的公有API割粮。

考慮如下代碼:

function User(){
    var username, password;

    function doLogin(user,pw) {
        username = user;
        password = pw;

        // 做登錄的工作
    }

    var publicAPI = {
        login: doLogin
    };

    return publicAPI;
}

// 創(chuàng)建一個(gè) `User` 模塊的實(shí)例
var fred = User();

fred.login( "fred", "12Battery34!" );

函數(shù)User()作為一個(gè)外部作用域持有變量usernamepassword盾碗,以及內(nèi)部doLogin()函數(shù);它們都是User模塊內(nèi)部的私有細(xì)節(jié)舀瓢,是不能從外部世界訪問的廷雅。

警告: 我們在這里沒有調(diào)用new User(),這是有意為之的氢伟,雖然對大多數(shù)讀者來說那可能更常見榜轿。User()只是一個(gè)函數(shù),不是一個(gè)要被初始化的對象朵锣,所以它只是被一般地調(diào)用了谬盐。使用new將是不合適的,而且實(shí)際上會浪費(fèi)資源诚些。

執(zhí)行User()創(chuàng)建了User模塊的一個(gè) 實(shí)例 —— 一個(gè)全新的作用域會被創(chuàng)建飞傀,而每個(gè)內(nèi)部變量/函數(shù)的一個(gè)全新的拷貝也因此而被創(chuàng)建。我們將這個(gè)實(shí)例賦值給fred诬烹。如果我們再次運(yùn)行User()砸烦,我們將會得到一個(gè)與fred完全分離的新的實(shí)例。

內(nèi)部的doLogin()函數(shù)在usernamepassword上擁有閉包绞吁,這意味著即便User()函數(shù)已經(jīng)完成了運(yùn)行幢痘,它依然持有對它們的訪問權(quán)。

publicAPI是一個(gè)帶有一個(gè)屬性/方法的對象家破,login是一個(gè)指向內(nèi)部doLogin()函數(shù)的引用颜说。當(dāng)我們從User()中返回publicAPI時(shí),它就變成了我們稱為fred的實(shí)例汰聋。

在這個(gè)時(shí)候门粪,外部的User()函數(shù)已經(jīng)完成了執(zhí)行。一般說來烹困,你會認(rèn)為像usernamepassword這樣的內(nèi)部變量將會消失玄妈。但是在這里它們不會,因?yàn)樵?code>login()函數(shù)里有一個(gè)閉包使它們繼續(xù)存活。

這就是為什么我們可以調(diào)用fred.login(..) —— 和調(diào)用內(nèi)部的doLogin(..)一樣 —— 而且它依然可以訪問內(nèi)部變量usernamepassword拟蜻。

這樣對閉包和模塊模式的簡單一瞥绎签,你很有可能還是有點(diǎn)兒糊涂。沒關(guān)系瞭郑!要把它裝進(jìn)你的大腦確實(shí)需要花些功夫辜御。

以此為起點(diǎn),關(guān)于更多深入細(xì)節(jié)的探索可以去讀本系列的 作用域與閉包屈张。

this 標(biāo)識符

在JavaScript中另一個(gè)經(jīng)常被誤解的概念是this標(biāo)識符。同樣袱巨,在本系列的 this與對象原型 中有好幾章關(guān)于它的內(nèi)容阁谆,所以在這里我們只簡要地介紹一下概念。

雖然this可能經(jīng)秤淅希看起來是與“面向?qū)ο竽J健庇嘘P(guān)的场绿,但在JS中this是一個(gè)不同的概念。

如果一個(gè)函數(shù)在它內(nèi)部擁有一個(gè)this引用嫉入,那么這個(gè)this引用通常指向一個(gè)object焰盗。但是指向哪一個(gè)object要看這個(gè)函數(shù)是如何被調(diào)用的。

重要的是要理解this 不是 指函數(shù)本身咒林,這是最常見的誤解熬拒。

這是一個(gè)快速的說明:

function foo() {
    console.log( this.bar );
}

var bar = "global";

var obj1 = {
    bar: "obj1",
    foo: foo
};

var obj2 = {
    bar: "obj2"
};

// --------

foo();              // "global"
obj1.foo();         // "obj1"
foo.call( obj2 );   // "obj2"
new foo();          // undefined

關(guān)于this如何被設(shè)置有四個(gè)規(guī)則,它們被展示在這個(gè)代碼段的最后四行中:

  1. foo()最終在非strict模式中將this設(shè)置為全局對象 —— 在strict模式中垫竞,this將會是undefined而且你會在訪問bar屬性時(shí)得到一個(gè)錯(cuò)誤 —— 所以this.bar的值是global澎粟。
  2. obj1.foo()this設(shè)置為對象obj1
  3. foo.call(obj2)this設(shè)置為對象obj2欢瞪。
  4. new foo()this設(shè)置為一個(gè)新的空對象活烙。

底線:要搞清楚this指向什么,你必須檢視當(dāng)前的函數(shù)是如何被調(diào)用的遣鼓。它將是我們剛剛看到的四種中的一種啸盏,而這將會回答this是什么。

注意: 關(guān)于this的更多信息骑祟,參見本系列的 this與對象原型 的第一和第二章回懦。

原型

JavaScript中的原型機(jī)制十分復(fù)雜。我們在這里僅僅掃它一眼曾我。要了解關(guān)于它的所有細(xì)節(jié)粉怕,你需要花相當(dāng)?shù)臅r(shí)間來學(xué)習(xí)本系列的 this與對象原型 的第四到六章。

當(dāng)你引用一個(gè)對象上的屬性時(shí)抒巢,如果這個(gè)屬性不存在贫贝,JavaScript將會自動地使用這個(gè)對象的內(nèi)部原型引用來尋找另外一個(gè)對象,在它上面查詢你想要的屬性。你可以認(rèn)為它幾乎是在屬性缺失時(shí)的備用對象稚晚。

從一個(gè)對象到它備用對象的內(nèi)部原型引用鏈接發(fā)生在這個(gè)對象被創(chuàng)建的時(shí)候崇堵。說明它的最簡單的方法是使用稱為Object.create(..)的內(nèi)建工具。

考慮如下代碼:

var foo = {
    a: 42
};

// 創(chuàng)建 `bar` 并將它鏈接到 `foo`
var bar = Object.create( foo );

bar.b = "hello world";

bar.b;      // "hello world"
bar.a;      // 42 <-- 委托到 `foo`

將對象foobar以及它們的關(guān)系可視化也許會有所幫助:

屬性a實(shí)際上不存在于對象bar上客燕,但是因?yàn)?code>bar被原型鏈接到foo鸳劳,JavaScript自動地退到對象foo上去尋找a,而且在這里找到了它也搓。

這種鏈接看起來是語言的一種奇怪的特性赏廓。這種特性最常被使用的方式 —— 我會爭辯說這是一種濫用 —— 是用來模擬/模仿“類”機(jī)制的“繼承”。

使用原型的更自然的方式是一種稱為“行為委托”的模式傍妒,在這種模式中你有意地將你的被鏈接的對象設(shè)計(jì)為可以從一個(gè)委托到另一個(gè)的部分所需的行為中幔摸。

注意: 更多關(guān)于原型和行為委托的信息,參見本系列的 this與對象原型 的第四到六章颤练。

舊的與新的

我們已經(jīng)介紹過的JS特性既忆,和將在這個(gè)系列的其他部分中講解的相當(dāng)一部分特性都是新近增加的,不一定在老版本的瀏覽器中可用嗦玖。事實(shí)上患雇,語言規(guī)范中的一些最新特性甚至在任何穩(wěn)定的瀏覽中都沒有被實(shí)現(xiàn)。

那么宇挫,你拿這些新東西怎么辦苛吱?你只能等上幾年或者十幾年直到老版本瀏覽器歸于塵土?

這確實(shí)是許多人認(rèn)為的情況捞稿,但是它不是JS健康的進(jìn)步方式又谋。

有兩種主要的技術(shù)可以將新的JavaScript特性“帶到”老版本的瀏覽器中:填補(bǔ)和轉(zhuǎn)譯。

填補(bǔ)

“填補(bǔ)(Polyfilling)”是一個(gè)人為發(fā)明的詞(由Remy Sharp創(chuàng)造)(https://remysharp.com/2010/10/08/what-is-a-polyfill)娱局。它是指拿來一個(gè)新特性的定義并制造一段行為等價(jià)的代碼彰亥,但是這段代碼可以運(yùn)行在老版本的JS環(huán)境中。

例如衰齐,ES6定義了一個(gè)稱為Number.isNaN(..)的工具任斋,來為檢查NaN值提供一種準(zhǔn)確無誤的方法,同時(shí)廢棄原來的isNaN(..)工具耻涛。這個(gè)工具可以很容易填補(bǔ)废酷,因此你可開始在你的代碼中使用它,而不管最終用戶是否在一個(gè)ES6瀏覽器中抹缕。

考慮如下代碼:

if (!Number.isNaN) {
    Number.isNaN = function isNaN(x) {
        return x !== x;
    };
}

if語句決定著在這個(gè)工具已經(jīng)存在的ES6環(huán)境中不再進(jìn)行填補(bǔ)澈蟆。如果它還不存在,我們就定義Number.isNaN(..)卓研。

注意: 我們在這里做的檢查利用了NaN值的怪異之處趴俘,即它們是整個(gè)語言中唯一與自己不相等的值睹簇。所以NaN是唯一可能使x !== xtrue的值。

并不是所有的新特性都可以完全填補(bǔ)寥闪。有時(shí)一種特性的大部分行為可以被填補(bǔ)太惠,但是仍然存在一些小的偏差。在實(shí)現(xiàn)你自己的填補(bǔ)時(shí)你應(yīng)當(dāng)非常非常小心疲憋,來確保你盡可能嚴(yán)格地遵循語言規(guī)范凿渊。

或者更好地,使用一組你信任的缚柳,經(jīng)受過檢驗(yàn)的填補(bǔ)埃脏,比如那些由ES5-Shim(https://github.com/es-shims/es5-shim)和ES6-Shim(https://github.com/es-shims/es6-shim)提供的。

轉(zhuǎn)譯

沒有任何辦法可以填補(bǔ)語言中新增加的語法秋忙。在老版本的JS引擎中新的語法將因?yàn)椴豢勺R別/不合法而拋出一個(gè)錯(cuò)誤剂癌。

所以更好的選擇是使用一個(gè)工具將你的新版本代碼轉(zhuǎn)換為等價(jià)的老版本代碼。這個(gè)處理通常被稱為“轉(zhuǎn)譯(transpiling)”翰绊,表示轉(zhuǎn)換 + 編譯。

實(shí)質(zhì)上旁壮,你的源代碼是使用新的語法形式編寫的监嗜,但是你向?yàn)g覽器部署的是轉(zhuǎn)譯過的舊語法形式。你一般會將轉(zhuǎn)譯器插入到你的構(gòu)建過程中抡谐,與你的代碼linter和代碼壓縮器類似裁奇。

你可能想知道為什么要麻煩地使用新語法編寫程序又將它轉(zhuǎn)譯為老版本代碼 —— 為什么不直接編寫老版本代碼呢?

關(guān)于轉(zhuǎn)譯你應(yīng)當(dāng)注意幾個(gè)重要的原因:

  • 在語言中新加入的語法是為了使你的代碼更具可讀性和維護(hù)性而設(shè)計(jì)的麦撵。老版本的等價(jià)物經(jīng)常會繞多得多的圈子刽肠。你應(yīng)當(dāng)首選編寫新的和干凈的語法,不僅為你自己免胃,也為了開發(fā)團(tuán)隊(duì)的其他的成員音五。
  • 如果你僅為老版本瀏覽器轉(zhuǎn)譯,而給最新的瀏覽器提供新語法羔沙,那么你就可以利用瀏覽器對新語法進(jìn)行的性能優(yōu)化躺涝。這也讓瀏覽器制造商有更多真實(shí)世界的代碼來測試它們的實(shí)現(xiàn)和優(yōu)化方法。
  • 提早使用新語法可以允許它在真實(shí)世界中被測試得更加健壯扼雏,這給JavaScript委員會(TC39)提供了更早的反饋坚嗜。如果問題被發(fā)現(xiàn)的足夠早,他們就可以在那些語言設(shè)計(jì)錯(cuò)誤變得無法挽回之前改變/修改它诗充。

這是一個(gè)轉(zhuǎn)譯的簡單例子苍蔬。ES6增加了一個(gè)稱為“默認(rèn)參數(shù)值”的新特性。它看起來像是這樣:

function foo(a = 2) {
    console.log( a );
}

foo();      // 2
foo( 42 );  // 42

簡單蝴蜓,對吧碟绑?也很有用!但是這種新語法在前ES6引擎中是不合法的。那么轉(zhuǎn)譯器將會對這段代碼做什么才能使它在老版本環(huán)境中運(yùn)行呢蜈敢?

function foo() {
    var a = arguments[0] !== (void 0) ? arguments[0] : 2;
    console.log( a );
}

如你所見辜荠,它檢查arguments[0]值是否是void 0(也就是undefined),而且如果是抓狭,就提供默認(rèn)值2伯病;否則,它就賦值被傳遞的任何東西否过。

除了可以現(xiàn)在就在老版本瀏覽器中使用更好的語法以外午笛,觀察轉(zhuǎn)譯后的代碼實(shí)際上更清晰地解釋了意圖中的行為。

僅從ES6版本的代碼看來苗桂,你可能還不理解undefined是唯一不能作為參數(shù)默認(rèn)值的明確傳遞的值药磺,但是轉(zhuǎn)譯后的代碼使這一點(diǎn)清楚的多。

關(guān)于轉(zhuǎn)譯要強(qiáng)調(diào)的最后一個(gè)細(xì)節(jié)是煤伟,現(xiàn)在它們應(yīng)當(dāng)被認(rèn)為是JS開發(fā)的生態(tài)系統(tǒng)和過程中的標(biāo)準(zhǔn)部分癌佩。JS將繼續(xù)以比以前快得多的速度進(jìn)化,所以每幾個(gè)月就會有新語法和新特性被加入進(jìn)來便锨。

如果你默認(rèn)地使用一個(gè)轉(zhuǎn)譯器围辙,那么你將總是可以在發(fā)現(xiàn)新語法有用時(shí),立即開始使用它放案,而不必為了讓今天的瀏覽器被淘汰而等上好幾年姚建。

有好幾個(gè)了不起的轉(zhuǎn)譯器供你選擇。這是一些在本書寫作時(shí)存在的好選擇:

非JavaScript

至此,我們討論過的所有東西都限于JS語言本身∮仰ǎ現(xiàn)實(shí)是大多數(shù)JS程序都是在瀏覽器這樣的環(huán)境中運(yùn)行并與之互動的稿湿。你所編寫的很大一部分代碼,嚴(yán)格地說沥阱,不是直接由JavaScript控制的缎罢。這聽起來可能有點(diǎn)奇怪。

你將會遇到的最常見的非JavaScript程序是DOM API考杉。例如:

var el = document.getElementById( "foo" );

當(dāng)你的代碼運(yùn)行在一個(gè)瀏覽器中時(shí)策精,變量document作為一個(gè)全局變量存在。它不是由JS引擎提供的崇棠,也不為JavaScript語言規(guī)范所控制咽袜。它采取了某種與普通JS object極其相似的形式,但它不是真正的object枕稀。它是一種特殊的object询刹,經(jīng)常被稱為“宿主對象”谜嫉。

另外,document上的getElementById(..)方法看起來像一個(gè)普通的JS函數(shù)凹联,但它只是一個(gè)微微暴露出來的接口雏掠,指向由瀏覽器DOM提供的內(nèi)建方法句伶。在一些(新一代的)瀏覽器中,這一層可能也是由JS實(shí)現(xiàn)的,但是傳統(tǒng)的DOM及其行為是由像C/C++這樣的語言實(shí)現(xiàn)的坏挠。

另一個(gè)例子是輸入/輸出(I/O)师骗。

大家最喜愛的alert(..)在用戶的瀏覽器窗口中彈出一個(gè)消息框屁擅。alert(..)是由瀏覽器提供給你的JS程序的仲义,而不是JS引擎本身。你進(jìn)行的調(diào)用將消息發(fā)送給瀏覽器內(nèi)部杠巡,它來處理消息框的繪制與顯示量窘。

console.log()也一樣;你的瀏覽器提供這樣的機(jī)制并將它們掛在開發(fā)者工具中氢拥。

這本書蚌铜,和整個(gè)這個(gè)系列,聚焦于JavaScript語言本身嫩海。這就是為什么你看不到任何涵蓋這些非JavaScript機(jī)制的重要內(nèi)容厘线。不管怎樣,你需要小心它們出革,因?yàn)樗鼈儗⒃谀銓懙拿恳粋€(gè)JS程序中存在!

復(fù)習(xí)

學(xué)習(xí)JavaScript風(fēng)格編程的第一步是對它的核心機(jī)制有一個(gè)基本的了解渡讼,比如:值骂束,類型,函數(shù)閉包成箫,this展箱,和原型。

當(dāng)然蹬昌,這些話題中的每一個(gè)都會衍生出比你在這里見到的多得多的內(nèi)容混驰,這也是為什么它們在這個(gè)系列剩下的部分中擁有自己的章節(jié)和書目。在你對本章中的概念和代碼示例感到相當(dāng)適應(yīng)之后皂贩,這個(gè)系列的其他部分正等著你真正地深入挖掘和了解這門語言栖榨。

這本書的最后一章將會對這個(gè)系列的每一卷的內(nèi)容,以及它們所涵蓋的我們在這里還沒有探索過的概念明刷,進(jìn)行簡單地總結(jié)婴栽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市辈末,隨后出現(xiàn)的幾起案子愚争,更是在濱河造成了極大的恐慌映皆,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轰枝,死亡現(xiàn)場離奇詭異捅彻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鞍陨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門步淹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人湾戳,你說我怎么就攤上這事贤旷。” “怎么了砾脑?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵幼驶,是天一觀的道長。 經(jīng)常有香客問我韧衣,道長盅藻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任畅铭,我火速辦了婚禮氏淑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘硕噩。我一直安慰自己假残,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布炉擅。 她就那樣靜靜地躺著辉懒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谍失。 梳的紋絲不亂的頭發(fā)上眶俩,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音快鱼,去河邊找鬼颠印。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抹竹,可吹牛的內(nèi)容都是我干的线罕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼窃判,長吁一口氣:“原來是場噩夢啊……” “哼闻坚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起兢孝,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤窿凤,失蹤者是張志新(化名)和其女友劉穎仅偎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雳殊,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡橘沥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夯秃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片座咆。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖仓洼,靈堂內(nèi)的尸體忽然破棺而出介陶,到底是詐尸還是另有隱情,我是刑警寧澤色建,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布哺呜,位于F島的核電站,受9級特大地震影響箕戳,放射性物質(zhì)發(fā)生泄漏某残。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一陵吸、第九天 我趴在偏房一處隱蔽的房頂上張望玻墅。 院中可真熱鬧,春花似錦壮虫、人聲如沸澳厢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赏酥。三九已至,卻和暖如春谆构,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背框都。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工搬素, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人魏保。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓熬尺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谓罗。 傳聞我的和親對象是個(gè)殘疾皇子粱哼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容