前言
在學(xué)習(xí) JavaScript 的過程中掠拳,我們通常會(huì)把 JavaScript 分為以下三個(gè)部分:
- JavaScript核心語言(The Core (ECMAScript))
- 文檔對象模型(The Document Object Model (DOM))
- 瀏覽器對象模型(The Browser Object Model (BOM))
本手冊將把重點(diǎn)放到 JavaScript 語言本身身上,也就是第一部分 JavaScript 核心語言(The Core (ECMAScript)。
如果你把 JavaScript 選為你的第一門語言,那么希望本手冊能夠幫助到你盡快掌握它。
如果你已經(jīng)在日常開發(fā)中使用 JavaScript 了膀篮,那么本手冊的內(nèi)容也是一個(gè)很好的復(fù)習(xí)。
現(xiàn)在岂膳,讓我們從 JavaScript 的歷史開始誓竿。
了解歷史
在正式開始學(xué)習(xí)前,我們先來簡短地看一下 JavaScript 的歷史闷营,這有助于第一次接觸 JavaScript 的人更好地學(xué)習(xí)。
誕生
JavaScript 創(chuàng)建于 1995 年知市。由當(dāng)時(shí) Netscape (網(wǎng)景) 公司的 Brendan Eich (布蘭登·艾克) 負(fù)責(zé)開發(fā)傻盟,最初命名為 Mocha,后來改名為 LiveScript嫂丙,最后才重命名為 JavaScript娘赴。
在創(chuàng)建的初期,JavaScript 并沒有對應(yīng)的標(biāo)準(zhǔn)(沒有統(tǒng)一的語法或功能)跟啤,這造成了瀏覽器腳本編寫的困難诽表,所以當(dāng)時(shí)業(yè)界都希望可以把 JavaScript 語言標(biāo)準(zhǔn)化。
標(biāo)準(zhǔn)化
1997 年隅肥,JavaScript 1.1 版本被提交到了 ECMA(歐洲計(jì)算機(jī)制造商協(xié)會(huì))竿奏,ECMA 把它分配給了 TC39 技術(shù)委員會(huì),希望可以制定出一個(gè)標(biāo)準(zhǔn)腥放、通用泛啸、跨平臺且和各大瀏覽器廠商都無關(guān)的標(biāo)準(zhǔn)。
TC39 技術(shù)委員會(huì)在當(dāng)時(shí)包括了 Netscape秃症、Sun候址、Microsoft、Borland 等占據(jù)市場主流的技術(shù)公司种柑,幾個(gè)月的技術(shù)會(huì)議后岗仑,最終發(fā)布了 ECMA-262。
ECMA-262 這份標(biāo)準(zhǔn)定義了一種新的腳本語言聚请,名稱就是我們現(xiàn)在聽到的 ECMAScript荠雕,至于為什么叫 ECMAScript 而不是就叫 JavaScript,這有可能是因?yàn)楫?dāng)時(shí)一些相關(guān)的法律或者品牌的原因所造成的。
版本名稱的變化
JavaScript 其實(shí)是 ECMAScript 標(biāo)準(zhǔn)的實(shí)現(xiàn)舞虱,這就是為什么您會(huì)聽到有關(guān) ES6欢际、ES2015、ES2016矾兜、ES2017损趋、ES2018、ES2019椅寺、ES2020 等的原因浑槽,ES 指的就是 ECMAScript。
如果你看到我上面羅列的一些版本名稱返帕,你會(huì)奇怪有一個(gè) ES6桐玻,而其他都是 ES + 年份,這其實(shí)是因?yàn)檫@個(gè)更改的時(shí)間有點(diǎn)晚荆萤。
當(dāng)時(shí) ES5 是 2009 年時(shí)發(fā)布 ECMAScript 規(guī)范的名稱镊靴,這導(dǎo)致 ES5 的下一個(gè)版本被人們稱為 ES6,而且官方?jīng)Q定宣布使用 ES2015 而不是 ES6 的時(shí)間點(diǎn)其實(shí)很晚链韭,這也就導(dǎo)致了目前在社區(qū)會(huì)有 ES6偏竟、ES7、ES8敞峭、ES9踊谋、ES10 這些版本名稱的出現(xiàn),但是其實(shí) ES + 年份才是目前的官方名稱旋讹。
其他
除了 JavaScript 外殖蚕,其實(shí)也有其他語言也實(shí)現(xiàn)了 ECMAScript,比如 ActionScript沉迹,它是 Flash 的腳本語言睦疫,但是目前已經(jīng)逐漸消失了。
現(xiàn)在鞭呕,支持 ECMAScript 規(guī)范的語言就是 JavaScript笼痛。
其中,ES5 從 2009 年到現(xiàn)在琅拌,已經(jīng)過去超過 10 年缨伊,雖然 ES5 在 JavaScript 的歷史中是一個(gè)非常重要的版本,但是 ES5 的很多知識已經(jīng)不值得再投入過多時(shí)間进宝。
現(xiàn)在刻坊,我們應(yīng)該轉(zhuǎn)向 ES2015(ES6) 或之后的版本進(jìn)行學(xué)習(xí)。
語法概述
大小寫
JavaScript 是區(qū)分大小寫的党晋,不管是變量谭胚、函數(shù)名還是運(yùn)算符都區(qū)分大小寫徐块,比如變量名 Apple 和 apple 就代表了兩個(gè)變量。
標(biāo)識符的合法性
我們上面提到的變量 apple灾而,其實(shí)就是程序中的標(biāo)識符胡控。
標(biāo)識符是用于描述程序中變量、函數(shù)旁趟、屬性或參數(shù)等的名稱昼激。
在 JavaScript 中,一個(gè)合法的標(biāo)識符規(guī)則如下:
- 第一個(gè)字符必須是字母、下劃線( _ )或美元符號( $ )。
- 其他所有字符可以是字母驾霜、下劃線( _ )、美元符號( $ ) 或 數(shù)字凡傅。
- 不能是 JavaScript 中的關(guān)鍵字或保留字。
另外肠缔,對于標(biāo)識符夏跷,有三點(diǎn)需要提到:
- 標(biāo)識符如果包含 兩個(gè)或兩個(gè)以上 的單詞時(shí),那么應(yīng)該采用 駝峰式 的寫法明未,第一個(gè)單詞的首字母小寫槽华,后面的單詞首字母大寫,比如 myApple亚隅。
- 美元符號通常在引用 DOM 元素時(shí)被使用硼莽,又或者被一些常用的庫所使用(比如 jQuery)庶溶,所以平時(shí)我們命名變量也很少使用美元符號( $ )煮纵。
- 我們很少會(huì)去背關(guān)鍵字和保留字,一般都是學(xué)習(xí)中或使用中慢慢熟悉偏螺。
注釋
JavaScript 采用 C語言 風(fēng)格的注釋行疏,即使用 // 的單行注釋,和使用 /* */ 的多行注釋套像。
如:
// 單行注釋
/*
多
行注釋
*/
分號
分號(;) 用于結(jié)束一條語句酿联,而一些足夠聰明的解釋器可以識別一條語句什么時(shí)候結(jié)束。
這就導(dǎo)致了目前的一個(gè)爭議夺巩,一些開發(fā)人員建議始終使用分號贞让,而另一些開發(fā)人員認(rèn)為不需要使用分號。
這里的重點(diǎn)是柳譬,保持統(tǒng)一的做法喳张。
如果使用分號就都使用,不要在一個(gè)項(xiàng)目中美澳,一個(gè)地方使用分號销部,另一個(gè)地方不使用摸航。
以下兩條語句的區(qū)別只在于代碼風(fēng)格的不同:
const hello = 1;
let world = 2
風(fēng)格可以討論,但不需要一個(gè)定論舅桩。
值和類型
JavaScript 有很多種類型酱虎,但是目前還沒到展開的時(shí)候,我們現(xiàn)在需要了解的是每一個(gè)值都有對應(yīng)的類型擂涛。
比如 100 是一個(gè)值读串,而數(shù)字是這個(gè)值的類型。又比如 apple 是一個(gè)值歼指,而字符串是這個(gè)值的類型爹土。
通常,我們會(huì)說踩身,字符串 apple胀茵,數(shù)字 100。
而當(dāng)我們需要使用這些值的時(shí)候挟阻,我們需要把它們存儲(chǔ)到變量中琼娘,而每個(gè)變量都有一個(gè)變量名(也就是標(biāo)識符)來標(biāo)識它,這樣我們就可以通過變量名來找到我們要使用的值了附鸽。
變量
首先脱拼,變量是什么?
對于初學(xué)者來說坷备,一個(gè)易于理解的例子就是熄浓,變量是程序中的一個(gè)盒子,并且盒子上貼有唯一的標(biāo)簽(變量名)省撑,而盒子里面的內(nèi)容赌蔑,就是我們的值。
另外竟秫,可以想象盒子的大小或者形狀則是我們的類型娃惯。
我們將使用兩個(gè)關(guān)鍵字去聲明變量,一個(gè)是 const(常量)肥败,另一個(gè)是 let(變量)趾浅。
const foo = 1
let bar = 2
const
const(常量) 聲明了該變量不能為這個(gè)變量重新分配一個(gè)值了。
例如馒稍,以下操作將會(huì)報(bào)錯(cuò):
const foo = 1
foo = 2
JavaScript中的常量和一些編程語言中的常量是有區(qū)別的皿哨。
對于 const,我們并沒有說不能改變它的值纽谒,而是說不能為這個(gè)變量重新賦值证膨。
那么,如果我們使用 const 聲明一個(gè)對象佛舱,其實(shí)是可以修改這個(gè)對象的屬性的椎例。
這一點(diǎn)挨决,我們將在學(xué)習(xí)對象中討論。
關(guān)于常量订歪,我們還需要提到關(guān)于命名脖祈。
現(xiàn)實(shí)中,對于常量的命名刷晋,我們有一個(gè)常規(guī)做法就是都大寫盖高,比如上面的例子,我們將命名為 FOO眼虱。
但是也有另外一種情況喻奥,就是我們的常量的值并非是提前知道的,而是需要在執(zhí)行期間才能獲得捏悬,那么撞蚕,此時(shí)的常量的命名仍然使用駝峰式。
以下 const 變量名命名的例子:
const APPLE_COLOR_RED = "#F00"
const codeExcuTime = /* 值將來自代碼執(zhí)行后 */
let
let 聲明的變量可以重新分配一個(gè)值过牙。
例如甥厦,以下操作將會(huì)成功:
let bar = 2
bar = 3
var
var 是 ES6 之前用于聲明變量的關(guān)鍵字。
下面是一個(gè)例子
var foo = 2
var 類似我們的 let寇钉,這里不會(huì)進(jìn)入過多的討論刀疙,現(xiàn)實(shí)中,我們應(yīng)該避免使用 var扫倡。
建議
總的來說谦秧,我們通過聲明變量來存放我們的數(shù)據(jù)。
對于現(xiàn)在撵溃,我們應(yīng)該只使用兩個(gè)關(guān)鍵字來聲明變量疚鲤,let 和 const。
其中的不同是征懈,如果是使用 const 聲明變量石咬,那就表示我們不希望這個(gè)變量的值被再次賦值揩悄。
注意卖哎,JavaScript 中,const 雖然表示常量删性,但并沒有表示不能修改它的值亏娜。
我們也提到的另外一個(gè)關(guān)鍵字 var,但我認(rèn)為應(yīng)該在你需要的時(shí)候再花時(shí)間去了解它蹬挺。
項(xiàng)目中维贺,我們應(yīng)該避免使用 var,更多考慮使用的是 const巴帮,然后才是 let溯泣。
類型
從一般的編程概念來看虐秋,變量的類型定義了可以存放到這個(gè)變量中的值,以及可以對這個(gè)值所進(jìn)行的操作垃沦。
比如一個(gè)變量存放的值是數(shù)字類型客给,那么這個(gè)變量可以執(zhí)行加減乘除操作。
通常肢簿,我們會(huì)把類型劃分為兩類:
- Primitive Types(原始類型)
- Object Types (對象類型)
原始類型
JavaScript 中靶剑,原始類型包括了 Number、BigInt池充、String桩引、Boolean、Symbol收夸。
另外坑匠,我們把 Null 和 Undefined 這兩個(gè)特殊的類型也劃分到原始類型中,所以總共有 7 種原始類型卧惜。
對象類型
對象類型笛辟,也就是 Object 類型。
可以說 JavaScript 中序苏,除去原始類型就是對象類型了手幢。
對象類型涉及到 properties(屬性) 和 methods(方法),我們將會(huì)在對象的知識點(diǎn)中討論忱详。
在各大經(jīng)典的 JavaScript 教程中都使用了原始類型和對象類型來對 JavaScript 中的類型進(jìn)行劃分围来。
但是在規(guī)范(ECAMScript2020)中并沒有這種劃分。只是直接列出了 8 種類型匈睁,也就是我們提到的 Undefined监透、Null、Boolean航唆、String胀蛮、Symbol、Number糯钙、BigInt 和 Object粪狼。
其中 Symbol 和 BigInt 算是后加進(jìn)來的,所以在一些舊的教程中任岸,你可能看到的是 6 種再榄,而不是 8 種。
表達(dá)式
表達(dá)式概述
表達(dá)式總是和語句產(chǎn)生關(guān)聯(lián)享潜,這也在社區(qū)中造成了一些爭議困鸥,但從掌握一門編程語言的過程來看,我認(rèn)為區(qū)分表達(dá)式和語句還是有必要的剑按,這對我們學(xué)習(xí)和理解函數(shù)式編程也有幫助疾就。
表達(dá)式和語句的區(qū)別
語句其實(shí)是由關(guān)鍵字組成的一條命令澜术,用于告訴 JavaScript 做什么,比如我們常會(huì)說的導(dǎo)入某某庫猬腰,這就需要我們寫語句來告訴 JavaScript了瘪板。
以下是一個(gè)語句的另外一個(gè)例子,用于告訴 JavaScript漆诽,我們聲明了一個(gè)變量侮攀,并把一個(gè)值存儲(chǔ)到該變量中:
let foo = 101
而表達(dá)式可以看做就是值,以下就是一些表達(dá)式:
101
1.38
'apple'
true
false
我們可以看到厢拭,表達(dá)式其實(shí)是表達(dá)一個(gè)值兰英,這就導(dǎo)致了表達(dá)式通常被放到等號的右邊。
而語句通常都會(huì)涉及到關(guān)鍵字供鸠,比如循環(huán)語句畦贸,用于告訴 JavaScript 這里需要重復(fù)執(zhí)行。
在我們后面接觸的語句越來越多的時(shí)候楞捂,就可以更好地理解了薄坏。
表達(dá)式的分類
通過表達(dá)式,我們會(huì)得到一個(gè)值寨闹,這里如果細(xì)分胶坠,又可以分為算術(shù)表達(dá)式,這將得到一個(gè)數(shù)字繁堡,比如:
1 + 2
i++
i * 100
又或者字符串表達(dá)式和邏輯表達(dá)式沈善,比如:
// 結(jié)果是 hello world!
’hello' + ' ' + 'world!'
// 結(jié)果是 true 或者 false
isCar && isHouse
除此之外,還可以是我們后面學(xué)到的函數(shù)椭蹄、對象和數(shù)組等闻牡,后面的章節(jié)將會(huì)介紹它們。
運(yùn)算符
運(yùn)算符的其實(shí)是 Operator 的翻譯绳矩。
前面我們提到罩润,類型定義了可以對值所進(jìn)行的操作。
所以翼馆,通常的翻譯是操作符割以,而運(yùn)算符,我覺得更多是數(shù)學(xué)上的概念写妥,可能會(huì)更通俗一些拳球。
但某些操作使用操作符可能會(huì)更好理解审姓,所以珍特,有時(shí)候也會(huì)使用操作符。
賦值運(yùn)算符
我們已經(jīng)見過不少運(yùn)算符了魔吐,第一個(gè)要正式介紹的運(yùn)算符我們也已經(jīng)見過扎筒,就是等號( = )莱找。
等號其實(shí)是賦值運(yùn)算符,用于分配值嗜桌。
另一種說法是初始化奥溺。
以下就是一個(gè)初始化變量的例子:
// 把 score 初始化為 100
let score = 100
算術(shù)運(yùn)算符
最常見的算術(shù)運(yùn)算符當(dāng)然就是 加( + )、減( - )骨宠、乘( * )浮定、除( / ) 了。
除此之外层亿,還有取余(%) 和 求冪()** 桦卒。
以下是 例子:
let foo = 1 + 2
foo = 1 - 2
foo = 1 * 2
foo = 1 / 2
foo = 2 % 3
foo = 1 ** 2
Infinity 和 NaN
Infinity 和 NaN 其實(shí) JavaScript 全局對象中的屬性,屬性的值就是他們本身匿又。
我們還沒有聊得到對象方灾,但是這里由于算術(shù)表達(dá)式會(huì)導(dǎo)致這兩個(gè)值出現(xiàn),所以我們有必要了解一下碌更。
Infinity
除法中裕偿,如果除以零,JavaScript 給出的結(jié)果是Infinity(正無窮大)痛单,或者是 -Infinity(負(fù)無窮大)嘿棘,而不是報(bào)錯(cuò)。
let foo = 1 / 0 // Infinity
foo = -1 / 0 // -Infinity
另外旭绒,Infinity 和數(shù)學(xué)中的無窮大概念很類似蔫巩,比如任何正數(shù)乘以 Infinity 會(huì)得到 正Infinity(負(fù)數(shù)則得負(fù) Infinity,任何正數(shù)值除以 Infinity快压,則得到正 0圆仔。
NaN
NaN(Not-A-Number) 表示不是一個(gè)數(shù)字。
我們都知道 0 不能做除數(shù)蔫劣,那么如果這么做坪郭,JavaScript 會(huì)告訴你得到的不是一個(gè)數(shù)字,也就是 NaN脉幢。
如下是一個(gè)例子:
let foo = 1 % 0 // NaN
foo = -1 % 0 // NaN
通常 NaN 的出現(xiàn)歪沃,其實(shí)就和它的含義一樣,通過計(jì)算嫌松,得到了一個(gè)不能表示為數(shù)字的值沪曙。
比如操作符中兩個(gè)變量的類型不同,又或者其中一個(gè)變量的值已經(jīng)為 NaN 了.
如:
'a' / 1 // NaN
1 + ('a' / 1) // NaN
對于運(yùn)算中何時(shí)會(huì)出現(xiàn) Infinity萎羔,何時(shí)會(huì)出現(xiàn) NaN液走,在 ECMA 的規(guī)范中羅列了很多情況,剛開始學(xué)習(xí)的時(shí)候我們并沒有必要每一條都找出來,只需要知道這兩個(gè)值得含義就好了缘眶。
下圖是 ECMA2020 中關(guān)于僅關(guān)于除法操作出現(xiàn)的情況嘱根,大家感受一下:
運(yùn)算符的優(yōu)先級
我們都知道在數(shù)學(xué)中,加減乘除運(yùn)算是有優(yōu)先級的巷懈,通掣檬悖口訣就是先乘除,后加減顶燕。
在 JavaScript 中也是一樣的凑保,運(yùn)算符優(yōu)先級決定了運(yùn)算執(zhí)行的先后順序。
比如:
1 + 2 * 3 // 1 + 6 結(jié)果為 7
所有操作符都有優(yōu)先級涌攻,有人匯總成了一個(gè)表格愉适,但是我們并不需要去背這個(gè)。
因?yàn)槲覀冇幸粋€(gè)處理優(yōu)先級的超強(qiáng)辦法癣漆,就是使用小括號(或者說是圓括號)维咸。
下面是一個(gè)例子:
(1 + 2) * 3 // 3 * 3 結(jié)果為 9
小括號的優(yōu)先級是最高的,并且本身沒有關(guān)聯(lián)性惠爽。
關(guān)聯(lián)性指的是癌蓖,如果大家的優(yōu)先級相同,那么我們應(yīng)該怎么執(zhí)行婚肆。
一般來說租副,關(guān)聯(lián)性分為左關(guān)聯(lián)(從左到右執(zhí)行) 和 右關(guān)聯(lián)(從右到左執(zhí)行)。
比如我們的加減乘除就是左關(guān)聯(lián)的较性,那么 1 + 2 + 3用僧,會(huì)優(yōu)先考慮計(jì)算 1 加 2,然后再加 3 了赞咙。
右關(guān)聯(lián)的一個(gè)例子就是我們的賦值操作符责循。
在一條賦值語句中,JavaScript 會(huì)把等號右邊的表達(dá)式算出來后再賦值給等號左邊的變量中:
let foo = 1 + 2 * 3 // 把7算出來后攀操,再存儲(chǔ)到 foo 變量中
比較運(yùn)算符
第三個(gè)要了解的運(yùn)算符是比較運(yùn)算符院仿。
比較過后,我們會(huì)得到一個(gè)布爾類型的值速和,總共就兩個(gè)歹垫,分別是 true 和 false。
常見的比較運(yùn)算符如下:
- < (小于)
- <= (小于或等于)
- >(大于)
- >=(大于或等于)
- === (是否相等)
- !== (是否不相等)
下面是例子:
let foo = 1
foo >= 1 // true
關(guān)于相等和不相等颠放,其實(shí)還有另外的運(yùn)算符排惨,== 和 !=。對于初學(xué)者來說碰凶,我們應(yīng)該選擇使用 === 來進(jìn)行檢查是否相等暮芭,使用 !== 來檢查是否不相等鹿驼。
這也是現(xiàn)實(shí)編程中的常規(guī)選擇。
有了比較運(yùn)算符之后谴麦,我們就可以討論開始條件語句了蠢沿。
條件語句
通常伸头,代碼會(huì)從上到下一行接一行地被執(zhí)行匾效。
但是有時(shí)候,我們需要根據(jù)條件來選擇執(zhí)行不同的代碼恤磷。
為此面哼,我們使用 if 語句。
if 語句實(shí)在是太常用了扫步,以至于幾乎所有的編程語言都會(huì)提供魔策,所以當(dāng)你掌握一種編程語言的 if 語句后,其他語言的完全不在話下河胎。
三種 if 語句
一般來說闯袒,和其他編程語言一樣,JavaScript 也提供了 if 語句的三種語法:
// 如果條件為真(true)游岳,則執(zhí)行大括號里面的語句政敢。
if (condition) {
statements
}
/**
* 如果條件為真(true),則執(zhí)行大括號里面的語句胚迫。
* 否則執(zhí)行else語句塊的內(nèi)容喷户。
*/
if (condition) {
statements
}
/**
* 如果條件1為真(true),則執(zhí)行大括號里面的語句访锻。
* 否則通過 else if 語句判斷條件2褪尝,為真(true)則執(zhí)行大括號里面的語句。
* 如果前面的if和elseif(一個(gè)或多個(gè))都不為真(true)期犬,則執(zhí)行else語句塊的內(nèi)容河哑。
*/
if (condition_1) {
statements
} else if (condition_2) {
statements
} else {
statements
}
如果你有學(xué)過其他編程語言的 if語句 的話,那么上面就是 JavaScript 中的 if語句 的用法龟虎,復(fù)習(xí)一下之后灾馒,你就可以進(jìn)入下一節(jié)的內(nèi)容了。
我們來一個(gè)個(gè)看遣总,首先是一個(gè)第一個(gè)簡單的例子:
// 永遠(yuǎn)被執(zhí)行
if (true) {
const score = 100;
}
// 永遠(yuǎn)不被執(zhí)行I
if (false) {
const socre = 0;
}
接著我們可以為 if語句 提供 else睬罗,作為當(dāng) if 為假的時(shí)候的選擇:
// 如果是真,score賦值為100旭斥,否則賦值為0
if (true) {
const score = 100;
} else {
const score = 0;
}
最后容达,我們的 else 其實(shí)也是可以添加語句的,所以垂券,我們可以嵌套一個(gè) if/esle 語句花盐,這樣羡滑,我們就可以對多個(gè)條件進(jìn)行判斷:
if (score === 0) {
// ......
} else if ( foo === true) {
// ......
} else {
// ......
}
注意锰茉,一個(gè) if 語句中哎垦,else if 可以寫多個(gè),但 if 和 else 只能寫一個(gè)当叭。
語句塊和作用域
下面是 if語句 的一個(gè)簡單例子:
if (true) {
// ......
}
現(xiàn)在熙揍,我們來關(guān)注花括號职祷,花括號代表了語句塊,也就是我們說的 block 届囚。
語句塊表面上看有梆,就是可以讓我們把一些語句放在一起。
當(dāng)然意系,一條語句也可以放到花括號中泥耀,變成語句塊。
但是蛔添,使用花括號對我們的語句進(jìn)行分組并不是最需要理解的概念痰催。
最重要的是,語句塊定義了一個(gè)新的范圍迎瞧,我們稱之為作用域(scope) 夸溶。
作用域 通俗的理解就是代碼的可見范圍:
if (true) {
let score = 1
}
/**
* 當(dāng)我們直接使用score的時(shí)候,將報(bào)錯(cuò)
* 報(bào)錯(cuò):ReferenceError: score is not defined
* console.log() 用于向控制臺打印內(nèi)容。
*/
console.log(score)
上面的例子中夹攒,if語句塊 中定義了的變量 score蜘醋。
但在語句塊外面想使用時(shí),卻告訴我們這個(gè)變量沒有定義的咏尝。
因?yàn)?strong>語句塊劃分了可見范圍压语,語句塊里面和外面,就好像兩個(gè)世界一樣编检。
示例中 if 語句的語句塊所形成的作用域胎食,我們也稱為塊級作用域,而在 ES2015允懂,也就是 ES6 之前厕怜,并不存在塊級作用域。
而 ES5 中的 var 沒有塊級作用域蕾总,是我們放棄使用 var 的重要原因粥航。
數(shù)組
通過前面類型的學(xué)習(xí),我們知道目前 JavaScript 中只有 8 種數(shù)據(jù)類型生百,其中并沒有包括數(shù)組递雀。
也就是說數(shù)組本身不是一種類型。
數(shù)組的作用是可以保存多個(gè)值蚀浆,更官方的說法是缀程,數(shù)組是元素的集合搜吧。
而元素,簡單來說就是值杨凑,反向地說滤奈,元素是組成了某個(gè)東西的一些東西。
之前我們對 8 種類型進(jìn)行了劃分撩满,提到除了原始類型之外蜒程,就都是對象類型。
那么鹦牛,數(shù)組其實(shí)就是對象了搞糕。
初始化
首先我們看看如何初始化數(shù)組一個(gè)空數(shù)組:
const foo = []
const bar = Array()
上面是兩種初始化空數(shù)組的方法勇吊。
第一種是直接使用了中括號語法曼追。
第二種其實(shí)是使用了Array 的函數(shù)。
我們還沒學(xué)到函數(shù)汉规,目前先知道這使用了函數(shù)就可以了礼殊。
我們再看看如何初始化一個(gè)有元素的數(shù)組:
const foo = [1, 2, 3]
const bar = Array().of(1, 2, 3)
數(shù)組其實(shí)可以包含不同類型的值,甚至可以包括另外一個(gè)數(shù)組:
const foo = [1, ’apple', [100]]
現(xiàn)實(shí)中针史,除非有很好的理由晶伦,否則一個(gè)數(shù)組應(yīng)該只放一種類型的元素。
至于多維數(shù)組啄枕,等需要的時(shí)候再去了解吧婚陪。
訪問元素
要訪問數(shù)組中元素,我們需要通過數(shù)組中的索引:
const foo = [1, 2, 3]
foo[0] // 取得1
索引就是數(shù)組中元素的唯一編號频祝,所以通過索引就找到對應(yīng)的元素了泌参。
我們可以看出,數(shù)組的索引其實(shí)是從 0 開始常空,編程的世界大都如此沽一。
數(shù)組的操作
因?yàn)閿?shù)組不在 7 個(gè)原始類型之中,所以我們知道數(shù)組其實(shí)是對象漓糙,也就是說數(shù)組本身應(yīng)該有一些行為(函數(shù))铣缠。
如果完全沒有對象或者函數(shù)的概念,那么可以先學(xué)完相關(guān)的知識后再回到這里昆禽。
函數(shù)很多蝗蛙,希望本手冊隨著更新會(huì)越來越全。
快速填充數(shù)組
使用 fill 可以為我快速初始化一個(gè)數(shù)組:
// 初始化一個(gè)長度為3的數(shù)組醉鳖,里面的元素都設(shè)置為100
const foo = Array(3).fill(100)
獲取數(shù)組長度
要知道數(shù)組的長度捡硅,我們可以使用 length 屬性:
const foo = [0, 1, 3]
foo.length // 3
增添元素
我們可以使用 push() 函數(shù),該方法在數(shù)組的尾部添加元素:
let foo = [1, 2]
foo.push(3) // 1, 2, 3
而使用unshift()方法則在頭部添加元素:
let foo = [1, 2]
foo.unshift(3) // 3, 1, 2
取出元素
和添加類似辐棒,我們可以使用 pop() 和 shift() 函數(shù)病曾。
pop() 用于取出數(shù)組尾部的元素:
let foo = [1, 2, 3]
foo.pop() // foo變成1, 2
shift() 用于取出數(shù)組頭部的元素:
let foo = [1, 2, 3]
foo.shift() // foo變成2, 3
刪除元素
刪除元素牍蜂,我們可以使用 delete。
let foo = [1, 2, 3]
delete foo[1] // 刪除索引為1的元素
console.log(foo) // [1, ,3]
console.log(foo.length) // 3
注意泰涂,使用delete刪除數(shù)組中的元素鲫竞,只是移除了元素的值。
元素原本的位置仍被占據(jù)著逼蒙,數(shù)組的長度不會(huì)變从绘。
拼接數(shù)組
拼接數(shù)組,我們可以使用 concat() 函數(shù):
const foo = [1, 2]
const bar = [3, 4]
const baz = foo.concat(bar) // [1, 2, 3, 4]
除此之外是牢,使用 擴(kuò)展運(yùn)算符 也是可以的:
const foo = [1, 2]
const bar = [3, 4]
const baz = [...a, ...b] // [1, 2, 3, 4]
查找元素
查找元素僵井,我們使用 find() 函數(shù):
let foo = [
{id: 1, score: 80},
{id: 2, score: 90},
{id: 3, score: 100}
];
let bar = foo.find(item => item.id == 1);
console.log(bar.score); // 80
console.log(bar); // { id: 1, score: 80 }
相比起來,find 的用法稍微復(fù)雜驳棱,但是對于存儲(chǔ)對象的數(shù)組來說批什,非常有用。
在上面的例子中社搅,我們通過傳遞一個(gè)只有一個(gè)參數(shù)的函數(shù) item => item.id == 1 來使用 find驻债,這也是現(xiàn)實(shí)中使用較多的用法,其他參數(shù)的一般很少使用形葬。
另外就是如果想找出數(shù)組中元素的索引合呐,我們只需要把 find 改為 findIndex 就可以了。
字符串
字符串也就是 String笙以,在編程語言中通常占據(jù)重要地位淌实。
多個(gè)字符就組成了字符串,更官方準(zhǔn)確的說法就是字符序列猖腕。
聲明字符串
在 JavaScript 中拆祈,定義字符串,使用單引號或雙引號都可以:
'I am string.'
"I am string too."
一般來說谈息,一個(gè)項(xiàng)目除了 JavaScript 文件外缘屹,還有 HTML 文件,所以更多建議 JavaScript 中使用單引號侠仇,而在 HTML 中使用雙引號轻姿。
字符串的操作
既然字符串是一個(gè)字符序列,那么自然就會(huì)有相關(guān)操作了逻炊。
首先看看如何初始化一個(gè)字符串變量:
const foo = 'dog'
獲取字符串長度
使用 length 屬性查看字符串的長度:
const foo = 'dog'
console.log(foo.length) // 3
字符串可以看做是一個(gè)字符數(shù)組互亮,所以注意,字符串的索引也是從 0 開始的余素。
字符串的連接
連接兩個(gè)字符串豹休,我們可以直接使用 "+" 號:
const foo = 'Hello' + ' World!'
console.log(foo) // Hello World!
變量也是可以的:
const foo = 'Hello'
const bar = foo + ' World!'
console.log(bar) // Hello World!
定義多行字符串
有時(shí)候,我們希望做一些模擬數(shù)據(jù)桨吊,需要定義多行字符串威根,我們可以使用反引號:
const string = `Hello
this
is string
World`
循環(huán)
循環(huán)是所有編程語言中必須要有的語句之一凤巨。
通過循環(huán),我們可以重復(fù)執(zhí)行一段代碼洛搀。
通常來說敢茁,while 和 for 是主要的內(nèi)容。
JavaScript 中有很多種循環(huán)的方法留美,我們這里介紹三種:
- while
- for
- for..of
while
while 應(yīng)該是最簡單的一種了:
while (condition) {
statement
}
其中 condition 是條件表達(dá)式彰檬,也就是它需要一個(gè)布爾值,真(true)或假(false)谎砾。
如果是真逢倍,就執(zhí)行語句塊里的語句,每執(zhí)行完一次語句塊景图,就重新執(zhí)行一次條件表達(dá)式较雕,看一下值是真還是假。
如果是真症歇,就繼續(xù)執(zhí)行語句塊郎笆,如果是假谭梗,循環(huán)就停止了忘晤。
使用 while 語句,重要的是需要添加能控制條件表達(dá)式在某次循環(huán)得到一個(gè)假的值激捏,不然循環(huán)一直下去设塔,無法停止的循環(huán),我們稱為死循環(huán)远舅。
除此之外闰蛔,對于第一個(gè)介紹的循環(huán)語句,我還想結(jié)束一些其他的概念图柏,以便更好地學(xué)習(xí)后面的 for 循環(huán)序六。
下面是另外一個(gè)例子:
const foo = ['a', 'b', 'c']
let i = 0
while (i < foo.length) {
console.log(foo[i]) // 將打印 a, b, c
console.log(i) // 將打印 0, 1, 2
i = i + 1 // 每次循環(huán)都需要遞增 1
}
首先,while 語句后面的小括號的內(nèi)容是條件表達(dá)式蚤吹。
條件判斷為真的時(shí)候就執(zhí)行后面語句塊的內(nèi)容例诀。
也就是說當(dāng)通過計(jì)算, i < foo.lenght 得到的值是 true 的時(shí)候裁着,后面的語句塊就會(huì)被執(zhí)行繁涂。
在循環(huán)語句中,語句塊也叫 循環(huán)體 二驰。
循環(huán)語句中還有一個(gè)迭代的概念扔罪,通常,循環(huán)體 被執(zhí)行一次桶雀,我們也叫 一次迭代矿酵,上面的例子就是 三次迭代 了唬复。
如果例子中沒有 i = i + 1 這條語句的話,那么理論上說全肮,循環(huán)體會(huì)一直被執(zhí)行盅抚。
另外,只要最終可以被 JavaScript 轉(zhuǎn)化為一個(gè)布爾值的表達(dá)式倔矾,就都可以放到循環(huán)條件中妄均,而不僅僅是大小運(yùn)算符比較的條件表達(dá)式。
for
當(dāng)使用 for 關(guān)鍵字的時(shí)候哪自,我們一般會(huì)告訴 for 三件事情:
- 初始化變量是什么
- 循環(huán)條件是什么
- 每次循環(huán)后需要執(zhí)行什么
下面是一個(gè)例子
for (let i = 0; i < 3; i++) { // 結(jié)果為 0丰包、1、2
console.log(i);
}
上面的例子中壤巷,初始化變量是 i邑彪,循環(huán)條件是 i < 3,每次循環(huán)過后執(zhí)行 i++胧华。
++ 是自增運(yùn)算符寄症,簡單來說就是讓自己加1。
由于每次循環(huán)后矩动,i 都會(huì)加1有巧,那么,i 將會(huì)在某次循環(huán)過后悲没,它的值將不是小于3的了篮迎。
此時(shí),循環(huán)終止示姿。
如果你是第一次接觸 for 循環(huán)語句甜橱,那么還需要知道的是,三個(gè)表達(dá)式都是可選栈戳,極端的情況是都不寫(但是分號不可省略)岂傲。
另外,在不同的編程語言子檀,或者不同的教程中镊掖,對 for 語句中的三個(gè)表達(dá)式有不一樣的稱呼。
比如 ECMAScript2020 文檔命锄,因?yàn)槭菢?biāo)準(zhǔn)堰乔,所以簡單明了,對這三個(gè)表達(dá)式都稱呼為表達(dá)式脐恩,然后使用 opt 代表都是可選的镐侯,大概像下面這樣:
for(Expression(opt); Expression(opt); Expression(opt) {
Statement
}
而在 MDN 的文檔中,使用的是 initialization,condition苟翻,final-expression韵卤,然后使用中括號代表都是可選的,大概像下面這樣:
for ([initialization]; [condition]; [final-expression]) {
statement
}
其實(shí)除了標(biāo)準(zhǔn)是簡單粗暴地稱為表達(dá)式外崇猫,其他教程幾乎都會(huì)對 for 循環(huán)里的三個(gè)表達(dá)式采用一個(gè)不同的稱呼沈条。
這都是為了讓讀者更好地明白 for 語句是怎么用的。
我們這里把三個(gè)表達(dá)式诅炉,從左到右編號蜡歹,就是表達(dá)式1,表達(dá)式2涕烧,表達(dá)式3:
for (表達(dá)式1; 表達(dá)式2; 表達(dá)式3)
statement
我們要知道 1月而,2,3 各自什么時(shí)候執(zhí)行议纯。
其中父款,記住表達(dá)式1只執(zhí)行一次,然后執(zhí)行表達(dá)式2瞻凤。
如果表達(dá)式2判斷為真憨攒,那么進(jìn)入循環(huán)體,循環(huán)體執(zhí)行完后阀参,執(zhí)行表達(dá)式3肝集。
這里,其實(shí)就是不斷循環(huán)了结笨,表達(dá)式2開始包晰,然后循環(huán)體,然后表達(dá)式3炕吸,之后回到表達(dá)式2,然后循環(huán)體勉痴,然后表達(dá)式3赫模,之后回到表達(dá)式2,如此循環(huán)執(zhí)行蒸矛。
直到有一次瀑罗,表達(dá)式2判斷為假了,整個(gè)循環(huán)也就結(jié)束了雏掠。
for...of
for...of 在 ES2105斩祭,也就是 ES6 中才出現(xiàn),當(dāng)我們想檢查對象的屬性時(shí)乡话,使用它更加方便摧玫,特別是有 key-value 這種數(shù)據(jù)的時(shí)候(比如屬性用作“鍵”),需要檢查其中的任何鍵是否為某值的情況時(shí)绑青,還是推薦用for ... in诬像。
下面是一個(gè)簡單的例子:
const hi = ['h', 'i']
for (const value of hi) {
console.log(value) // hi
}
上面的例子中屋群,我們只是遍歷看看,并不改動(dòng)坏挠,所以使用了 const 來聲明語句塊的變量芍躏。
接下來我們,將會(huì)進(jìn)入函數(shù)的學(xué)習(xí)降狠。
函數(shù)
終于來到函數(shù)了对竣,可以說,函數(shù)是 JavaScript 中最重要同時(shí)也最有意思的部分榜配。
那么柏肪,什么是函數(shù)?
在大部分編程語言中芥牌,一個(gè)函數(shù)通常就是一個(gè)可以被重復(fù)調(diào)用的語句塊烦味。
當(dāng)然,在 JavaScript 中會(huì)有不一樣的概念壁拉。
在 JavaScript 中谬俄,函數(shù)其實(shí)是對象,如果你有對象的概念弃理,那么現(xiàn)在就可以知道溃论,函數(shù)的名稱其實(shí)是指向函數(shù)對象的一個(gè)指針。
同時(shí)痘昌,函數(shù)自己是一個(gè)對象钥勋,這說明函數(shù)本身具有對應(yīng)的屬性和方法。
ok辆苔,對函數(shù)的解釋到這里就可以了算灸。
如果你一頭霧水,那么沒關(guān)系驻啤。
因?yàn)榉坡浚瑢τ诔鯇W(xué)者來說,學(xué)習(xí)函數(shù)就從函數(shù)是一個(gè)可以被重復(fù)調(diào)用的語句塊 這個(gè)概念開始就很好骑冗。
讓我們從如何聲明和調(diào)用一個(gè)簡單的函數(shù)開始赊瞬。
函數(shù)的聲明和調(diào)用
讓我們看一下,我們?nèi)绾温暶饕粋€(gè)函數(shù):
function sayHello() {
console.log('Hello!')
}
其中贼涩,function 是關(guān)鍵字巧涧,用于聲明函數(shù),接著是函數(shù)名 sayHello遥倦。
函數(shù)名后面是圓括號谤绳,圓括號里的內(nèi)容,我們稱呼為形式參數(shù)(通常簡稱形參)。
形參如果出現(xiàn)多個(gè)多個(gè)闷供,我們使用逗號分隔烟央。
在有多個(gè)形參的時(shí)候,我們稱為形參列表歪脏。
最后的花括號疑俭,我們稱之為函數(shù)體。
目前婿失,我們知道一個(gè)函數(shù)包括了以下4個(gè)內(nèi)容:
- function關(guān)鍵字
- 函數(shù)名
- 形參列表
- 函數(shù)體
接下來钞艇,我們來看另外一個(gè)函數(shù):
function sum(num1, num2) {
return num1 + num2;
}
上面的函數(shù)所描述的功能是兩數(shù)相加。
我們會(huì)發(fā)現(xiàn)其中多出了一個(gè)關(guān)鍵字 return豪硅。
return 的作用是返回一個(gè)值(簡稱返回值)哩照。
在 JavaScript 中,每個(gè)函數(shù)其實(shí)都會(huì)返回一個(gè)值懒浮,默認(rèn)情況下飘弧,返回 undefined,及時(shí)沒有寫 return語句也是一樣砚著。
上面的例子中次伶,我們使用 return 語句來明確我們的返回值是兩個(gè)形參之和。
我們目前只是聲明了一個(gè)函數(shù)稽穆。
如果不調(diào)用它們冠王,那么它們并不會(huì)自己執(zhí)行。
要調(diào)用一個(gè)函數(shù)很簡單舌镶,只需要使用函數(shù)名和告訴它形參是什么就可以了柱彻。
下面是對上面兩個(gè)函數(shù)進(jìn)行調(diào)用的例子:
sayHello() // Hello!
console.log(sum(1, 2)) // 調(diào)用,并把函數(shù)返回的結(jié)果打印出來
函數(shù)表達(dá)式
讓我們再看一下實(shí)現(xiàn)兩數(shù)相加的函數(shù):
function sum(num1, num2) {
return num1 + num2;
}
上面的函數(shù)其實(shí)等同于我們使用一個(gè)函數(shù)表達(dá)式餐胀,就像下面這樣:
let sum = function(num1, num2) {
return num1 + num2;
}
如果你忘了什么是表達(dá)式哟楷,那么建議你回看前面關(guān)于表達(dá)式的內(nèi)容。
現(xiàn)在骂澄,我們可以像之前那樣調(diào)用這個(gè)函數(shù):
console.log(sum(1, 2)) // 調(diào)用吓蘑,并把函數(shù)返回的結(jié)果打印出來
在 JavaScript 中,function關(guān)鍵字坟冲,函數(shù)名、形參列表溃蔫、函數(shù)體這些就構(gòu)成了我們的函數(shù)健提。
這種函數(shù)通常也被我們稱呼為普通函數(shù),或者常規(guī)函數(shù)伟叛。
箭頭函數(shù)
箭頭函數(shù)在ES6中出現(xiàn)私痹,目前已經(jīng)變得非常常用了。
有時(shí)候,箭頭函數(shù)和我們上面剛提到的函數(shù)表達(dá)式很類似紊遵。
下面是一個(gè)比較账千,大家感受一下:
// 函數(shù)表達(dá)式
let sum1 = function(num1, num2) {
return num1 + num2;
}
// 箭頭函數(shù)
let sum2 = (num1, num2) => {
return num1 + num2
}
console.log(sum1(1, 2)) // 打印 3
console.log(sum2(1, 2)) // 打印 3
從外表看,箭頭函數(shù)會(huì)更簡單暗膜,它不僅省略了 function 關(guān)鍵字匀奏,還省略了函數(shù)名。
比如普通函數(shù):
function sayHello() {
console.log('Hello!')
}
箭頭函數(shù)是這樣:
() => {
console.log('Hello!')
}
當(dāng)我們想像普通函數(shù)一樣調(diào)用箭頭函數(shù)的時(shí)候学搜,我們會(huì)發(fā)現(xiàn)娃善,由于省略了函數(shù)名,我們沒辦法調(diào)用瑞佩。
所以聚磺,我們需要將它賦值給一個(gè)變量。
let sayHello = () => {
console.log('Hello!')
}
這樣炬丸,我們就可以通過變量名來調(diào)用這個(gè)箭頭函數(shù)了:
sayHello()
如果我們只有一條語句瘫寝,我甚至可以省略花括號:
let sayHello = () => console.log('Hello!')
sayHello() // 將打印 Hello!
可以看到,對于只有一行代碼的函數(shù)來說稠炬,箭頭函數(shù)展示了它的便捷性焕阿。
當(dāng)然,如果函數(shù)體內(nèi)有多行語句酸纲,我們還是要顯示地使用花括號和 return 語句捣鲸。
讓我們看看兩數(shù)之和的箭頭函數(shù)是怎么樣的:
let sum = (num1, num2) => num1 + num2
console.log(sum(1, 2)) // 打印 3
我們可以看到,箭頭函數(shù)允許我們省略 return 關(guān)鍵字闽坡。
如果我們只有一個(gè)參數(shù)栽惶,我們還可以省略圓括號:
const getClassName = className => console.log(className)
getClassName('三年二班') // 打印 三年二班
總的來說,箭頭函數(shù)的要點(diǎn)是:
- 使用函數(shù)表達(dá)式的方式疾嗅,但是省略 function 關(guān)鍵字和函數(shù)名外厂。
- 形參列表和函數(shù)體之間使用 => 。
- 當(dāng)形參列表只有一個(gè)參數(shù)的時(shí)候,我們可以省略圓括號。
- 當(dāng)函數(shù)體內(nèi)只有一條語句的時(shí)候岩榆,我們可以省略花括號刑巧,或者 return 關(guān)鍵字。
更多的用法仍翰,我們可以在需要的時(shí)候再了解。
剛開始接觸箭頭函數(shù)的時(shí)候并不容易,但是多嘗試幾次幔亥,習(xí)慣之后就會(huì)好很多了。
對象
對象是我們的 8 八大類型之一察纯,在 JavaScript 之中帕棉,如果不是原始類型针肥,那么就是對象類型了。
對象的創(chuàng)建
我們先來看看如何創(chuàng)建一個(gè)對象:
const foo = {
}
上面創(chuàng)建對象的方式是目前 JavaScript 里面最好的特性之一香伴。
這種方式我們稱呼為字面量方式慰枕。
直接使用花括號,就好像我們使用單引號或雙引號聲明一個(gè)字符串一樣即纲。
只不過具帮,花括號聲明的是對象,而單引號或雙引號聲明的是字符串崇裁。
除了花括號外匕坯,還有以下兩種方式:
一種是使用對象的構(gòu)造函數(shù),語法是 new Object:
const foo = new Object()
其中拔稳,new 是我們的關(guān)鍵字葛峻。
new 可以用于調(diào)用對象的構(gòu)造函數(shù),并以此來初始化對象巴比。
第三種是使用 Object.create():
const foo = Object.create()
盡管有多種創(chuàng)建對象的方法术奖,但是現(xiàn)實(shí)中,我們都傾向于使用字面量的方式來創(chuàng)建對象轻绞,它更加地可視化采记,以及使用了更少的代碼。
所以政勃,請使用字面量的方式來創(chuàng)建對象唧龄。
對象的屬性
對象里面通常有屬性和方法。
我們先來看看如何聲明對象的屬性
下面我們以字面量的方式創(chuàng)建一個(gè)對象:
let person = {
name: "Jack",
"age": 18,
2: true,
"likes dogs": true,
}
上面的例子奸远,演示了對象中屬性的寫法既棺。
首先是屬性名,后面跟上冒號懒叛,之后是屬性的值丸冕。
多個(gè)屬性之間使用逗號分隔。
最后一個(gè)屬性可以沒有逗號薛窥,因?yàn)樵谝恍├系臑g覽器中胖烛,有逗號會(huì)報(bào)錯(cuò)。
但是現(xiàn)代的瀏覽器都支持最后一個(gè)屬性可以寫逗號诅迷,所以現(xiàn)在建議最后的屬性也加逗號佩番,方便他人也方便自己。
另外罢杉,屬性名會(huì)被自動(dòng)轉(zhuǎn)換為字符串答捕,所以可以加雙引號也可以不加。
但是如果屬性名是多個(gè)單詞的就必須要加上雙引號了屑那。
對象的方法
對象中的方法就是我們之前學(xué)習(xí)的函數(shù)拱镐,只不過和對象聯(lián)系起來的時(shí)候,我們把函數(shù)稱呼為方法持际。
下面是一個(gè)例子:
let person = {
name: "Jack",
age: 18,
sayHello: function() {
console.log('Hello!')
},
}
挺熟悉的是嗎沃琅,我們也是寫了一個(gè)屬性,只不過屬性值是一個(gè)沒有函數(shù)名的函數(shù)而已蜘欲。
我們?yōu)?sayHello 屬性分配了一個(gè)函數(shù)益眉,這種情況下,我們就把函數(shù)稱呼為方法姥份。
還記得郭脂,我們學(xué)過箭頭函數(shù)嗎?
箭頭函數(shù)也是可以的:
let person = {
name: "Hello",
age: 100,
sayHello: () => console.log('Hello!'),
}
訪問對象的屬性和方法
我們?yōu)閷ο笤O(shè)置了屬性和方法后澈歉,怎么調(diào)用呢展鸡?
和大多數(shù)面向?qū)ο?/strong>語言一樣,JavaScript 也是使用點(diǎn)操作符來訪問對象的屬性和方法埃难。
另外莹弊,JavaScript 還提供了方括號來訪問屬性和方法。
下面是一些例子:
let person = {
name: "Jack",
"age": 18,
2: true,
"likes dogs": true,
sayHello: () => console.log('Hello!'),
}
console.log(person.name) // Jack
console.log(person.age) // 18
console.log(person[2]) // true
console.log(person["likes dogs"]) // true
person.sayHello()
一般而言涡尘,使用點(diǎn)操作符和使用中括號在功能上講忍弛,并沒有什么區(qū)別。
但是有時(shí)候考抄,我們必須使用中括號细疚,如上的 2 和 likes dogs。
因?yàn)辄c(diǎn)操作無法使用川梅,原因是點(diǎn)操作符的操作對象需要符合我們標(biāo)識符的命名規(guī)則疯兼。
如果大家忘記了合法標(biāo)識符的那些規(guī)則的話,建議回看前面的內(nèi)容了挑势。
重點(diǎn)是第一個(gè)字符必須是字母镇防、美元符號($) 和 短下劃線(_)。
有了點(diǎn)操作符和中括號的概念后潮饱,我們可以介紹使用 new 關(guān)鍵字來創(chuàng)建對象了:
let person = new Object();
person.name = "Jack"
person.age = 18
person[2] = true
person["like dogs"] = true
這里来氧,我們使用了點(diǎn)操作符來設(shè)置我們的屬性,但是像數(shù)字和多詞屬性名就沒辦法使用點(diǎn)操作符了香拉。我們需要使用中括號啦扬。
再次說明,我們更喜歡使用字面量的方式來創(chuàng)建對象凫碌。
對象中的箭頭函數(shù)和普通函數(shù)
注意扑毡,我們示例中雖然使用了箭頭函數(shù)作為對象的方法,但有時(shí)候我們必須選擇普通函數(shù)來作為對象的方法盛险。
這是因?yàn)樵趯ο笾忻樘^函數(shù)和對象之間根本沒有綁定勋又,這導(dǎo)致在箭頭函數(shù)中的 this 關(guān)鍵字是無效的。
下面是一個(gè)例子:
let person = {
name: "Jack",
sayHello: function() {
console.log(`Hello, ${this.name}`)
},
anotherHello: () => {
console.log(`Hello, ${this.name}`)
}
}
person.sayHello() // Hello, Jack
person.anotherHello() // Hello, undefined
這是箭頭函數(shù)和普通函數(shù)之間的一個(gè)區(qū)別换帜。
類
如果我們理解了面向?qū)ο笮ㄈ溃敲矗覀兒茈y想象只有對象惯驼,而沒有類的世界蹲嚣。
因?yàn)檫@樣的世界里所有的對象都無跡可尋。
那么類到底是什么呢祟牲?
在面向?qū)ο蟮氖澜缋锵缎螅瑢τ陬惖慕忉專鱾髦@么一句話:類是對象的藍(lán)圖说贝。
也就是說议惰,只要我們規(guī)劃好了類,我們就可以根據(jù)類創(chuàng)建出無數(shù)個(gè)我們想要的對象狂丝。
我們也可以把類理解為换淆,一種自定義類型。
我們自己定義了一種類型几颜,然后創(chuàng)建很多相同類型的對象供我們使用倍试。
類的定義、屬性和方法
那么如何定義一個(gè)類呢蛋哭?
和大多數(shù)面向編程語言一樣县习,在 JavaScript 中我們也是使用 class 關(guān)鍵字和花括號來定義類。
下面是一個(gè)例子:
class Person {}
我們雖然定義了一個(gè)類谆趾,但是躁愿,在 JavaScript 中,類其實(shí)只是一種特殊的函數(shù)沪蓬。
所以彤钟,和函數(shù)類似,除了函數(shù)表達(dá)式外跷叉,我們還有類表達(dá)式逸雹。
下面是一個(gè)例子:
const Person = class {}
如果你觀察,你會(huì)發(fā)現(xiàn) 類 的名稱云挟,我們使用大寫字母開頭梆砸。
如有兩個(gè)或兩個(gè)以上的單詞,仍然使用駝峰式园欣,也就是每個(gè)單詞的首字母仍然需要大寫帖世。
下面我們來定義一個(gè)包含屬性的類:
class Person {
name
}
接著我們可以通過 new 關(guān)鍵字初始化一個(gè)該類的對象:
const jack = new Person()
和我們之前介紹的對象類似,我們可以使用點(diǎn)操作符來訪問其屬性:
jack.name = 'Jack'
我們是可以直接在類中初始化屬性的:
class Person {
name = 'Jack'
}
和對象類似沸枯,類中除了可以定義屬性外日矫,還可以定義方法:
class Person {
name = 'Jack'
sayHello() {
console.log('Hello!')
}
}
構(gòu)造方法和 this
類中除了普通的方法外赂弓,還有幾種特殊的方法,其中一個(gè)就是構(gòu)造方法搬男。
構(gòu)造方法通常用于在我們構(gòu)造一個(gè)對象的同時(shí)初始化屬性拣展。
構(gòu)造方法也叫做構(gòu)造器,在 javascript 中通過使用 constructor() 創(chuàng)建:
class Person {
constructor(name) {
this.name = name
}
sayHello() {
console.log(this.name)
}
}
上面的例子中缔逛,我們使用 constructor() 來初始化屬性,除了使用了形參列表外姓惑,我們還是用了另外一個(gè)關(guān)鍵字 this褐奴。
對于 this,我們可以認(rèn)為于毙,誰調(diào)用敦冬,this 就是誰。
比如 我們創(chuàng)建了 jack 對象唯沮,然后 jack 對象調(diào)用了 sayHello() 方法脖旱,那么 this 指的就是 jack 對象。
下面是構(gòu)造方法來初始化 Person 類介蛉,并調(diào)用其方法的例子:
const jack = new Person('Jack')
jack.sayHello() // Jack
靜態(tài)方法
有時(shí)候萌庆,我們可能希望不創(chuàng)建對象,就直接調(diào)用類中的方法币旧。
通過使用關(guān)鍵字 static践险,我們可以這樣做。
使用 static 聲明的方法也叫靜態(tài)方法吹菱。
下面是一個(gè)例子:
class Person {
static sayHello() {
console.log('Hello!')
}
}
類中的方法被 static 修飾后巍虫,可以使用類名直接調(diào)用:
Person.sayHello()
get/set方法
類中除了普通方法、靜態(tài)方法和構(gòu)造方法外鳍刷,還有 get 方法和 set 方法占遥。
下面是 get/set 方法的一個(gè)簡單例子:
class Person {
constructor(name) {
this.name = name; // 將調(diào)用set方法來設(shè)置name屬性
}
get name() {
return this._name;
}
set name(newName) {
this._name = newName
}
}
let jack = new Person();
jack.name = 'Jack'
console.log(jack.name) // Jack
總結(jié)
一個(gè)類中可以包含屬性、普通方法输瓜、構(gòu)造方法和 get/set 方法瓦胎,當(dāng)然,這些都是可選的前痘,就像我們一開始定義一個(gè)空的類一樣凛捏。
繼承
在 JavaScript 中,類的繼承就是一個(gè)類對另外一個(gè)類進(jìn)行擴(kuò)展芹缔。
要使用這種功能坯癣,我們需要使用到關(guān)鍵字 extends。
下面是一個(gè)例子:
class Animal {
eat() {
console.log('eat......')
}
}
class Cat extends Animal {
super()
miao() {
console.log('miao, miao......')
}
}
我們創(chuàng)建的貓類擴(kuò)展了動(dòng)物類最欠。
我們把貓類稱為動(dòng)物類的子類示罗,而動(dòng)物類就是貓類的父類惩猫。
現(xiàn)在,由于貓類繼承(擴(kuò)展)動(dòng)物類蚜点,所以也有動(dòng)物類的行為轧房。
如下,我們可以調(diào)用父類的 eat() 方法:
const cat = new Cat()
cat.eat()
cat.miao()
同時(shí)绍绘,在子類可以通過 super 來調(diào)用父類:
class Cat extends Animal {
miao() {
super.eat()
console.log('miao, miao......')
}
}
const cat = new Cat()
cat.miao()
上面程序?qū)⒋蛴奶镶。篹at......miao,miao......陪拘。
異步編程和回調(diào)
同步和異步
同步和異步其實(shí)是計(jì)算機(jī)世界里的一個(gè)基礎(chǔ)概念厂镇。
同步可以簡單地理解為在完成一條計(jì)算機(jī)指令之前,下一條指令不會(huì)被執(zhí)行左刽。
同步的一個(gè)問題其實(shí)是會(huì)造成程序執(zhí)行被中斷捺信,從而進(jìn)入等待。
比如我們?yōu)g覽器需要調(diào)用網(wǎng)頁里 JavaScript 文件中的一個(gè)函數(shù)欠痴,我們需要先下載這個(gè)文件迄靠。
否則我們?yōu)g覽器將提示找不到這個(gè)函數(shù)。
但在下載這個(gè)文件期間喇辽,網(wǎng)頁中的其他代碼應(yīng)該可以繼續(xù)加載執(zhí)行掌挚,而不是被下載這個(gè) JavaScript 文件的行為所中斷,造成用戶的等待茵臭。
JavaScript 使用 回調(diào) 來解決這個(gè)問題疫诽。
回調(diào)的使用
介紹回調(diào)通常都是以一個(gè)簡單的計(jì)時(shí)器開始。
以下是一個(gè)例子:
setTimeout(() => {
console.log('2 秒后...')
}, 2000)
在 setTimeout() 函數(shù)里有兩個(gè)參數(shù)旦委,一個(gè)是函數(shù)奇徒,另一個(gè)數(shù)字(單位是毫秒)。
語句 console.log('2 秒后...') 將在兩秒后被調(diào)用缨硝。
setTimeout() 并非是 JavaScript 核心語言的一部分摩钙,它并沒有包括在 ECMAScript2020 手冊中。
setTimeout() 一般由瀏覽器或者常見的 Node.js 環(huán)境所提供查辩,所以在這里胖笛,2秒并不一定和你系統(tǒng)上的2秒相同,它取決于當(dāng)時(shí)的運(yùn)行環(huán)境宜岛。
我們再運(yùn)行如下代碼:
console.log('我還沒來')
setTimeout(() => {
console.log('2 秒后...')
}, 2000)
console.log('我來了')
我們看到的結(jié)果可能是這樣的:
我還沒來
我來了
2 秒后...
回調(diào)的模式在處理網(wǎng)絡(luò)长踊、事件或者瀏覽器中的DOM的時(shí)候,都非常常見萍倡。
callback 的使用
處理回調(diào)我們通常會(huì)定義一個(gè) callback 參數(shù):
const foo = callback => {
callback()
}
callback 通常是一個(gè)函數(shù)身弊,下面是我們之前計(jì)算兩數(shù)之和的一個(gè)例子,我們修改為使用回調(diào)函數(shù):
function sum(num1, num2, callback) {
setTimeout(() => callback(num1 + num2), 2000)
}
sum(1, 2, (num3) => console.log(`It\'s ${num3}`))
上面的程序?qū)⒃?秒后輸出 It's 3。
處理回調(diào)的成功和失敗
在回調(diào)的模式中阱佛,其實(shí)是一個(gè)等待處理的過程帖汞,那么,處理的結(jié)果就有可能出現(xiàn)失敗凑术。
以下是回調(diào)時(shí)翩蘸,處理成功和失敗的例子:
function sum(num1, num2, success, failure) {
setTimeout(() => {
try {
if (0 === num2) {
throw '除數(shù)不能為0'
}
success(num1 / num2)
} catch (e) {
failure(e);
}
}, 1000)
}
const successCallback = (num3) => console.log(`成功: ${num3}`)
const failureCallback = (e) => console.log(`失敗: ${e}`)
sum(6, 2, successCallback, failureCallback)
sum(6, 0, successCallback, failureCallback)
輸出的結(jié)果如下:
成功: 3
失敾囱贰: 除數(shù)不能為0
上面處理回調(diào)成功和失敗的方式已經(jīng)比較舊了催首,也不太推薦使用。
回調(diào)地獄
不太推薦使用上一節(jié)中處理回調(diào)的原因之一就是:在我們的回調(diào)需要依賴另一個(gè)回調(diào)時(shí)壮莹,就形成了嵌套的回調(diào)翅帜。
這將使我們的代碼出現(xiàn)著名的回調(diào)地獄。
我們看看把上面兩數(shù)之和的例子變成回調(diào)地獄是什么樣子:
function sum(num1, num2, success, failure) {
setTimeout(() => {
try {
if (0 === num2) {
throw '除數(shù)不能為0'
}
success(num1 / num2)
} catch (e) {
failure(e);
}
}, 1000)
}
// 沒有嵌套回調(diào)的時(shí)候
// const successCallback = (num3) => console.log(`成功: ${num3}`)
// 回調(diào)地獄開始
const successCallback = (num3) => {
sum(8, 4, () => {
sum(10, 5, () => {
sum(12, 6, (anotherNum) => console.log(`Success: ${anotherNum}`))
})
});
}
const failureCallback = (e) => console.log(`失斆: ${e}`)
sum(6, 2, successCallback, failureCallback)
我們輸入 6 和 2 ,但最終绣版,通過回調(diào)地獄胶台,我們得到的是:
Success: 2
一旦代碼中出現(xiàn)了 回調(diào)地獄 ,這份代碼將非常難以維護(hù)杂抽。
為了解決回調(diào)地獄的問題诈唬, ES2015 也就是 ES6 推出了 Promises ,并且推出后缩麸,采用率就非常高铸磅,可以說成為了 JavaScript 中處理異步代碼的標(biāo)準(zhǔn)。
Promises
promise 概述
promises 是處理異步的另外一種方式杭朱。
在 JavaScript 中阅仔,promises 較早期的使用應(yīng)該是在 jQuery 或者 Dojo 的一些 API 中。
promises 規(guī)范有很多版本的實(shí)現(xiàn)弧械,它在 2010 年開始慢慢普及八酒。
對 JavaScript 來說,直到 ECMA2015 也就是 ES6 中才被正式引入刃唐。
現(xiàn)在幾乎所有的現(xiàn)代瀏覽器都完全地支持了 Promises羞迷。
promise 涉及了一些新的概念,就像我們學(xué)習(xí)對象和類一樣画饥。
promise 沒有在我們的原始類型中衔瓮,所以其實(shí)它是對象,更準(zhǔn)確地說是一種引用類型抖甘。
創(chuàng)建 promise 對象
要?jiǎng)?chuàng)建一個(gè) promise 其實(shí)和我們創(chuàng)建一個(gè)對象很相識:
let foo = new Promise(() => {});
// 普通函數(shù)的寫法
// let foo = new Promise(function () {});
promise 中的狀態(tài)
在使用 promise 之前热鞍,我們首先要了解 promise 對象中的一個(gè)屬性叫做 state。
state 用于描述 promise 中的三種狀態(tài):
- pending:這是一種初始狀態(tài),不是成功也不是失敗碍现。
- fulfilled:調(diào)用成功時(shí)的狀態(tài)幅疼。
- rejected:調(diào)用失敗時(shí)的狀態(tài)。
promise 的狀態(tài)指明了 promise 是否開始執(zhí)行了昼接。
pending表示執(zhí)行還沒有開始或者還在執(zhí)行中爽篷。
fulfilled 表示執(zhí)行成功,沒有出現(xiàn)異常慢睡。
rejected 表示沒有執(zhí)行成功逐工,出現(xiàn)了異常。
其實(shí)狀態(tài)在代碼中是不會(huì)直接使用的漂辐,所以無需太在意狀態(tài)的名稱泪喊。
重點(diǎn)是知道有這三種狀態(tài)。
在promise的狀態(tài)中髓涯,還有一個(gè)概念是需要我們知道袒啼。
這個(gè)概念叫 settled(不變的)。
settled 表示的是 fulfilled 和 rejected 這兩個(gè)中狀態(tài)中的任一種纬纪。
當(dāng) promise 對象的狀態(tài)蚓再,從 pending 轉(zhuǎn)為 settled 后,這個(gè) promise 對象的狀態(tài)就不會(huì)再改變了包各。
promise 中的 resolve() 和 reject()
我們知道了 promise 有三種狀態(tài)摘仅,但是 promise 的這三種狀態(tài)其實(shí)是私有的,并沒有公開讓我們訪問问畅。
這樣做的原因是娃属,為了防止使用 promise 的時(shí)候直接根據(jù)狀態(tài)來對對象進(jìn)行同步編程。
雖然我們無法直接訪問這三種狀態(tài)护姆,但 promise 封裝了一個(gè)異步函數(shù)矾端,稱之為 executor,通過它签则,我們可以對 promise 的狀態(tài)進(jìn)行遷移须床。
JavaScript 自身為我們的 executor 提供了兩個(gè)可選參數(shù),一個(gè)是 resolve渐裂,另一個(gè)是 reject豺旬。
所以,創(chuàng)建一個(gè)帶有參數(shù)的參數(shù)的 executor 的例子如下:
let promise = new Promise((resolve, reject) => {
// Do executor things
})
console.log(promise) // 將打悠饬埂:Promise { <pending> }
上面的例子中族阅,用于初始化 promise 對象的參數(shù)是一個(gè)函數(shù),這個(gè)函數(shù)就是我們的 executor膝捞。
如果要進(jìn)行狀態(tài)的轉(zhuǎn)換坦刀,我們需要使用 JavaScript 為我們提供的兩個(gè)回調(diào):
- resolve(value):成功且?guī)в薪Y(jié)果value
- reject(evalue):失敗且出現(xiàn)error
這個(gè)兩個(gè)回到都可以在 executor 直接使用。
現(xiàn)在,讓我們討論一下狀態(tài)是怎么變化的鲤遥。
首先沐寺,executor 一開始處于 pending 狀態(tài),此時(shí)的返回值是 undefined盖奈。
然后可以通過 resolve(value)混坞,將狀態(tài)更改為 fulfilled,此時(shí)返回值是 value钢坦。
而通過 reject(error)究孕,則將狀態(tài)更改為 rejected,此時(shí)返回值是 error爹凹。
下面厨诸,我們來看一個(gè) resolve(value) 的例子:
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('ok'), 1000)
})
下面是一個(gè) reject(error) 的例子:
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('foo')), 1000)
})
通常 executor 會(huì)使用異步執(zhí)行,所以我們例子中用了 setTimeout()禾酱,但是其實(shí)并非必須的微酬,直接寫上執(zhí)行的代碼也可以:
let promise = new Promise((resolve, reject) => {
resolve("ok")
})
另外,promise 的狀態(tài)被轉(zhuǎn)換后是無法再次修改的颤陶,也就是說一個(gè) promise 一次只能改變一種狀態(tài):
let promise = new Promise((resolve, reject) => {
resolve() // Promise <resolved>
reject() // 將不會(huì)產(chǎn)生效果效果
})
promise 中的 .then得封、.catch 和 .finally
executor 用于執(zhí)行我們的內(nèi)容,那么我們?nèi)绾翁幚韴?zhí)行后的結(jié)果呢指郁?
為此,我們可以使用 promise 提供的 .then 拷呆、.catch 和 .finally 方法闲坎。
.then
在 .then 方法中,有兩個(gè)參數(shù)茬斧,兩個(gè)參數(shù)都是函數(shù)腰懂。
一個(gè)用于接收 resolved 的結(jié)果,一個(gè)用于接收 rejected 的結(jié)果项秉。
假設(shè)這兩個(gè)方法為 onFulfilled 和 onRejected绣溜,則 .then 的例子如下:
promise.then(
onFulfilled,
onRejected
);
我們可以選擇只使用其中一個(gè)作為 .then 方法的參數(shù),比如我們只對成功的結(jié)果感興趣::
// 初始化一個(gè) promise 對象
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('ok'), 1000)
})
// 使用 .then 處理結(jié)果
promise.then(function (result) {console.log(result)})
// 在只有一行語句的時(shí)候娄蔼,如果你掌握了箭頭函數(shù)怖喻,選擇使用箭頭函數(shù)會(huì)更好
// promise.then(result => console.log(result))
上面的例子在一秒鐘后,輸出了 ok岁诉。
其實(shí) resolve 方法的參數(shù)并沒有什么特別的規(guī)定锚沸。
但一般我們會(huì)把要傳給回調(diào)函數(shù)的參數(shù)放進(jìn)去,而 then 方法可以接收到這個(gè)參數(shù)值涕癣。
所以這里我們輸出了 ok哗蜈。
.catch
.catch 其實(shí)只是 promise.then(undefined, onRejected) 的別名而已,使用 .catch 就相當(dāng)于我們沒有定義 .then 中的第一個(gè)參數(shù)。
下面是一個(gè)例子:
// 初始化一個(gè) promise 對象
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('foo')), 1000)
})
// 使用 .catch 處理結(jié)果
promise.catch(error => console.error(error))
// 上面的.catch等同于下面的.then
// promise.then(null, error => console.error(error))
.finally
許多編程語言都會(huì)有 finally 子句距潘,JavaScript也一樣炼列。
.finally 通常用于進(jìn)行一些收尾的工作,比如回收資源音比,停止加載指示符等等俭尖。
.finally 是無論結(jié)果如何,都需要執(zhí)行的語句硅确,以下是一個(gè)例子:
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('ok'), 1000)
})
promise.finally(() => console.log('All Done!'))
async 和 await
async 和 await 其實(shí)是 promise 的一種抽象或者說是 JavaScript 提供給我們的特殊語法目溉,它們都是關(guān)鍵字,這使得在 JavaScript 進(jìn)行異步編程更加優(yōu)雅菱农。
async
async 關(guān)鍵字可以用在函數(shù)聲明缭付、函數(shù)表達(dá)式、箭頭函數(shù)和對象的方法中循未。
下面是一個(gè)例子:
async function foo() {
return 'Hello'
}
這樣就可以了陷猫!
接下來,我們就可以像使用 promise 一樣地使用這個(gè)函數(shù)的妖。
測試一下:
async function foo() {
return 'Hello'
}
foo().then(result => console.log(result))
await
await 關(guān)鍵字的作用是讓我們等待異步的動(dòng)作完成并返回結(jié)果绣檬。
下面是一個(gè)例子:
async function foo() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("ok"), 1000)
})
console.log(await promise) // 等 promise resolve 才執(zhí)行
}
foo()
使用 await 需要注意的是,await 不能再普通函數(shù)中使用嫂粟,只能在被 async 聲明的函數(shù)中使用娇未。
我們可以通過一個(gè)匿名的 async 函數(shù)來使用它,如:
(async () => {
let foo = await fetch('test.json');
})
終章
非常感謝您閱讀這本手冊星虹。
如無意外零抬,本手冊仍會(huì)更新。
希望本手冊能幫助大家在學(xué)習(xí) JavaScript 的道路上一路前行宽涌。
文曉歡歡平夜。
2020.03.15
公眾號:文曉歡歡
知識星球:文曉歡歡