Js 簡介
什么是 Js?
Js 最初被創(chuàng)建的目的是“使網(wǎng)頁更生動”。
Js 寫出來的程序被稱為 腳本,Js 是一門腳本語言。
- 被直接寫在網(wǎng)頁的 HTML 中,在頁面加載的時候自動執(zhí)行
- 腳本被以純文本的形式提供和執(zhí)行,不需要特殊的準備或編譯即可運行(JIN compiler)
Js 不僅可以在瀏覽器中執(zhí)行,也可以在服務(wù)端執(zhí)行,本質(zhì)上是它可以在任意搭載了Js 引擎的設(shè)備中執(zhí)行。
瀏覽器中嵌入了 Js 引擎,有時也稱作“JavaScript 虛擬機”,不同的引擎有不同的“代號”,例如:
- V8 —— Chrome、Opera 和 Edge 中的 Js 引擎。
- SpiderMonkey —— Firefox 中的 Js 引擎。
- Chakra —— IE
- JavaScriptCore、Nitro 、 SquirrelFish —— Safari
eg:如果 “V8 支持某個功能” ,那么我們可以認為這個功能大概能在 Chrome、Opera 和 Edge 中正常運行。
引擎是如何工作的?
引擎很復(fù)雜,但是基本原理很簡單。
- 引擎(如果是瀏覽器,則引擎被嵌入在其中)讀?。ā敖馕觥保┠_本。
- 然后,引擎將腳本轉(zhuǎn)化(“編譯”)為機器語言。
- 然后,機器代碼快速地執(zhí)行。
引擎會對流程中的每個階段都進行優(yōu)化。它甚至可以在編譯的腳本運行時監(jiān)視它,分析流經(jīng)該腳本的數(shù)據(jù),并根據(jù)獲得的信息進一步優(yōu)化機器代碼。
瀏覽器中的 Js
能做什么?
現(xiàn)代的 Js 是一種“安全的”編程語言。它不提供對內(nèi)存或 CPU 的底層訪問,因為它最初是為瀏覽器創(chuàng)建的,不需要這些功能。
Js 的能力很大程度上取決于它運行的環(huán)境。例如,Node.js 支持允許 Js 讀取/寫入任意文件,執(zhí)行網(wǎng)絡(luò)請求等。
瀏覽器中的 Js 可以做下面這些事:
- 在網(wǎng)頁中添加新的 HTML,修改網(wǎng)頁已有內(nèi)容和網(wǎng)頁的樣式。
- 響應(yīng)用戶的行為,響應(yīng)鼠標的點擊,按鍵的按動。
- 向遠程服務(wù)器發(fā)送網(wǎng)絡(luò)請求,下載和上傳文件(所謂的 AJAX 和 COMET 技術(shù))。
- 獲取或設(shè)置 cookie,向訪問者提出問題或發(fā)送消息。
- 記住客戶端的數(shù)據(jù)(“本地存儲”)。
不能做什么?
為了用戶的(信息)安全,在瀏覽器中的 Js 的能力是受限的。
目的是防止惡意網(wǎng)頁獲取用戶私人信息或損害用戶數(shù)據(jù)。
此類限制的例子包括:
-
網(wǎng)頁中的 Js 不能讀、寫、復(fù)制和執(zhí)行硬盤上的任意文件。它沒有直接訪問操作系統(tǒng)的功能。
現(xiàn)代瀏覽器允許 Js 做一些文件相關(guān)的操作,但是這個操作是受到限制的。
僅當用戶做出特定的行為,Js 才能操作這個文件。eg:用戶把文件“拖放”到瀏覽器中,或者通過
<input type='file'>
標簽選擇了文件。有很多與相機/麥克風和其它設(shè)備進行交互的方式,但是這些都需要獲得用戶的明確許可。
-
不同的標簽頁/窗口之間通?;ゲ涣私狻?/p>
有時候,也會有一些聯(lián)系,例如一個標簽頁通過 Js 打開的另外一個標簽頁。
但即使在這種情況下,如果兩個標簽頁打開的不是同一個網(wǎng)站(域名、協(xié)議或者端口任一不相同的網(wǎng)站),它們都不能相互通信。這就是所謂的“同源策略”。
為了解決“同源策略”問題,兩個標簽頁必須都包含一些處理這個問題的特定的 Js 代碼,并均允許數(shù)據(jù)交換。
-
Js 可以輕松地通過互聯(lián)網(wǎng)與當前頁面所在的服務(wù)器進行通信。但是從其他網(wǎng)站/域的服務(wù)器中接收數(shù)據(jù)的能力被削弱了。盡管可以,但是需要來自遠程服務(wù)器的明確協(xié)議(在 HTTP header 中)。這也是為了用戶的信息安全。
數(shù)據(jù)類型
在 Js 中有 8 種基本的數(shù)據(jù)類型(7 種原始類型和 1 種引用類型)
Number 類型
代表整數(shù)和浮點數(shù),可以有很多操作,eg:乘法 *
、除法 /
、加法 +
、減法 -
等等。
除了常規(guī)的數(shù)字,還包括所謂的“特殊數(shù)值”:Infinity
、-Infinity
和 NaN
。
科學計數(shù)法
- 將
"e"
和 0 的數(shù)量附加到數(shù)字后。就像:123e6
與123
后面接 6 個 0 相同。 -
"e"
后面的負數(shù)將使數(shù)字除以 1 后面接著給定數(shù)量的零的數(shù)字。例如123e-6
表示0.000123
(123
的百萬分之一)。
多種進制
-
可以直接在十六進制(
0x
),八進制(0o
or00
)和二進制(0b
or0B
)系統(tǒng)中寫入數(shù)字。 -
使用
Number()
方法將含有對應(yīng)前綴的字符串數(shù)值轉(zhuǎn)為十進制Number('0b111') // 7 Number('0o10') // 8
-
parseInt(str, base)
將字符串str
解析為在給定的base
數(shù)字系統(tǒng)中的整數(shù),2 ≤ base ≤ 36
。 -
num.toString(base)
將數(shù)字轉(zhuǎn)換為在給定的base
數(shù)字系統(tǒng)中的字符串。
常規(guī)數(shù)字檢測
全局方法
-
isNaN(value)
—— 將其參數(shù)轉(zhuǎn)換為數(shù)字,然后檢測它是否為NaN
-
isFinite(value)
—— 將其參數(shù)轉(zhuǎn)換為數(shù)字,如果它是常規(guī)數(shù)字,則返回true
(NaN/Infinity/-Infinity
返回false)
定義在Number上的方法 (ES6)
-
Number.isNaN()
——檢查一個值是否為NaN
。如果參數(shù)類型不是NaN
,Number.isNaN
一律返回false
-
Number.isFinite()
—— 檢查一個數(shù)值是否為有限的。如果參數(shù)類型不是數(shù)值,Number.isFinite
一律返回false
。
區(qū)別
傳統(tǒng)的全局方法isFinite()
和isNaN()
先調(diào)用Number()
將非數(shù)值的值轉(zhuǎn)為數(shù)值,再進行判斷,而這兩個新方法只對數(shù)值有效,Number.isFinite()
對于非數(shù)值一律返回false
, Number.isNaN()
只有對于NaN
才返回true
,非NaN
一律返回false
。
isFinite(25) // true
isFinite("25") // true
Number.isFinite(25) // true
Number.isFinite("25") // false
isNaN(NaN) // true
isNaN("NaN") // true
Number.isNaN(NaN) // true
Number.isNaN("NaN") // false
Number.isNaN(1) // false
不規(guī)則字符串轉(zhuǎn)換為數(shù)字
任務(wù):將 12pt
和 100px
之類的值轉(zhuǎn)換為數(shù)字
全局方法
- 使用
parseInt/parseFloat
進行“軟”轉(zhuǎn)換,它從字符串中讀取數(shù)字,然后返回在發(fā)生 error 前可以讀取到的值。
定義在Number上的方法 (ES6)
- ES6 將全局方法
parseInt()
和parseFloat()
,移植到Number
對象上面,行為完全保持不變。這樣做的目的,是逐步減少全局性方法,使得語言逐步模塊化。
Math 對象的擴展
-
使用
Math.floor
,Math.ceil
,Math.trunc
,Math.round
或num.toFixed(precision)
進行舍入。其中Math.trunc() —— 用于去除一個數(shù)的小數(shù)部分,返回整數(shù)部分(內(nèi)部使用Number
方法將其先轉(zhuǎn)為數(shù)值) -
Math.sign() —— 用來判斷一個數(shù)到底是正數(shù)、負數(shù)、還是零。對于非數(shù)值,會先將其轉(zhuǎn)換為數(shù)值。
- 參數(shù)為正數(shù),返回
+1
; - 參數(shù)為負數(shù),返回
-1
; - 參數(shù)為 0,返回
0
; - 參數(shù)為-0,返回
-0
; - 其他值,返回
NaN
。
- 參數(shù)為正數(shù),返回
-
Math.cbrt() —— 計算一個數(shù)的立方根
-
Math.clz32() —— 將參數(shù)轉(zhuǎn)為 32 位無符號整數(shù)的形式,然后返回這個 32 位值里面有多少個前導(dǎo) 0
-
Math.imul() —— 返回兩個數(shù)以 32 位帶符號整數(shù)形式相乘的結(jié)果,返回的也是一個 32 位的帶符號整數(shù)。
-
Math.fround() —— 返回一個數(shù)的32位單精度浮點數(shù)形式
-
Math.hypot() —— 返回所有參數(shù)的平方和的平方根
使用兩個點來調(diào)用一個方法
請注意 123456..toString(36)
中的兩個點不是打錯了。如果我們想直接在一個數(shù)字上調(diào)用一個方法,比如上面例子中的 toString
,那么我們需要在它后面放置兩個點 ..
。
如果我們放置一個點:123456.toString(36)
,那么就會出現(xiàn)一個 error,因為 Js 語法隱含了第一個點之后的部分為小數(shù)部分。如果我們再放一個點,那么 JavaScript 就知道小數(shù)部分為空,現(xiàn)在使用該方法。
也可以寫成 (123456).toString(36)
。
如果是小數(shù):可以直接寫為0.13.toFixed(1)
數(shù)值分隔符
ES2021,允許 Js 的數(shù)值(所有進制)使用下劃線(_
)作為分隔符,這個數(shù)值分隔符沒有指定間隔的位數(shù)。
使用注意點:
- 不能放在數(shù)值的最前面或最后面。
- 不能兩個或兩個以上的分隔符連在一起。
- 小數(shù)點的前后不能有分隔符。
- 科學計數(shù)法里面,表示指數(shù)的
e
或E
前后不能有分隔符。 - 分隔符不能緊跟著進制的前綴
0b
、0B
、0o
、0O
、0x
、0X
(eg:0_b1100,0b_0100)
下面三個將字符串轉(zhuǎn)成數(shù)值的函數(shù),不支持數(shù)值分隔符:
- Number()
- parseInt()
- parseFloat()
BigInt 類型
number 類型無法安全地表示大于 (253-1),或小于 - (253-1)的整數(shù)。
更準確的說:
“number” 類型可以存儲更大的整數(shù),但超出安全整數(shù)范圍 ±(253-1)會出現(xiàn)精度問題,因為并非所有數(shù)字都適合固定的 64 位存儲。因此,可能存儲的是“近似值”。
// 尾部的 "n" 表示這是一個 BigInt 類型
const bigInt = 1234567890123456789012345678901234567890n;
String 類型
三種包含字符串的方式:
-
雙引號:"Hello"
-
單引號:'Hello'
-
反引號:`Hello`
反引號是 功能擴展 引號,稱為模板字符串。
它允許我們通過將變量和表達式包裝在
${…}
中,來將它們嵌入到字符串中,并且可以在里面直接換行。
-
Js 中的字符串使用的是 UTF-16 編碼。
-
Js 中字符串不可以被改變
-
length
屬性表示字符串長度 -
可以使用像
\n
這樣的特殊字符或通過使用\u...
來操作它們的 Unicode 進行字符插入。其中
\uxxxx
是字符的Unicode表示法,這種表示法只限于碼點在\u0000
~\uFFFF
之間的字符。超出這個范圍的字符,必須用兩個雙字節(jié)的形式表示。ES6 對這一點做出了改進,只要將碼點放入大括號,就能正確解讀該字符。"\uD842\uDFB7" // "??" "\u20BB7" // " 7" "\u{20BB7}" // "??" /* 如果直接在`\u`后面跟上超過`0xFFFF`的數(shù)值(比如`\u20BB7`),Js 會理解成`\u20BB+7`。由于`\u20BB`是一個不可打印字符,所以只會顯示一個空格,后面跟著一個`7`。 */
-
獲取字符時,使用
[]
orcharAt
,它們之間的唯一區(qū)別是,如果沒有找到字符,[]
返回undefined
,而charAt
返回一個空字符串 -
獲取子字符串,使用
slice
或substring
。方法 選擇方式 負值參數(shù) slice(start, end)
從 start
到end
(不含end
),start可以比end大允許 substring(start, end)
從 start
到end
(不含end
),start可以比end大負值被視為 0
substr(start, length)
從 start
開始獲取長為length
的字符串允許 start
為負數(shù)let str = "stringify"; // 這些對于 substring 是相同的 alert( str.substring(6, 2) ); // "ring" // ……但對 slice 是不同的: alert( str.slice(6, 2) ); // ""(空字符串)
-
字符串的大/小寫轉(zhuǎn)換,使用:
toLowerCase/toUpperCase
。 -
查找子字符串時,使用
indexOf
或includes/startsWith/endsWith
(ES6)進行簡單檢查。這里檢查是否找到子字符串時使用的一個技巧是
~
運算符。它將數(shù)字轉(zhuǎn)換為 32-bit 整數(shù)(如果存在小數(shù)部分,則刪除小數(shù)部分),然后對其二進制表示形式中的所有位均取反。實際上,這意味著一件很簡單的事兒:對于 32-bit 整數(shù),
~n
等于-(n+1)
。原因:在補碼中,符號位不變,數(shù)值位 取反加1 得 -n ,表示為 -n = 取反 + 1 ,只取反為 ~n = -n - 1 = -(n+1)
人們用它來簡寫
indexOf
檢查:let str = "Widget"; // 找到:返回值>0,不為-1 if (~str.indexOf("Widget")) { alert( 'Found it!' ); // 正常運行 }
-
根據(jù)語言比較字符串時使用
localeCompare
,否則將按字符代碼進行比較。 -
ES6的字符串的遍歷器接口(for...of...)可以識別大于
0xFFFF
的碼點,傳統(tǒng)的for
循環(huán)無法識別這樣的碼點。 -
str.trim()
—— 刪除字符串前后的空格 (“trims”)。 -
str.repeat(n)
—— 重復(fù)字符串n
次。
ES6新增字符串方法
-
String.fromCodePoint() 與 實例方法:codePointAt()
對比:
-
String.fromCharCode()
—— 從Unicode碼點返回對應(yīng)字符串,不能識別大于0xFFFF的字符(charCodeAt) -
String.fromCodePoint()
—— 可以識別大于0xFFFF
的字符 (注意,fromCodePoint
方法定義在String
對象上,而codePointAt
方法定義在字符串的實例對象上。)
// 大于0xFFFF,舍去最高位‘2’ String.fromCharCode(0x20BB7) // "?" String.fromCodePoint(0x20BB7) // "??" // 有多個參數(shù),則合并成一個字符串返回。 String.fromCodePoint(0x78, 0x1f680, 0x79) === 'x\uD83D\uDE80y' // true // 解決 字符a在字符串s的正確位置序號應(yīng)該是 1,但是必須向codePointAt()方法傳入 2。 let s = '??a'; for (let ch of s) { console.log(ch.codePointAt(0).toString(16)); } let arr = [...'??a']; // arr.length === 2 arr.forEach( ch => console.log(ch.codePointAt(0).toString(16)) ); // 20bb7 // 61
-
-
String.raw()
可以作為處理模板字符串的基本方法,它會將所有變量替換,而且對斜杠進行轉(zhuǎn)義,方便下一步作為字符串來使用。
String.raw`Hi\\n` === "Hi\\\\n" // true // 如果寫成正常函數(shù)的形式,它的第一個參數(shù),應(yīng)該是一個具有raw屬性的對象,且raw屬性的值應(yīng)該是一個數(shù)組,對應(yīng)模板字符串解析后的值。 // `foo${1 + 2}bar` // 等同于 String.raw({ raw: ['foo', 'bar'] }, 1 + 2) // "foo3bar"
-
實例方法:normalize()
ES6 提供字符串實例的
normalize()
方法,用來將字符的不同表示方法統(tǒng)一為同樣的形式,這稱為 Unicode 正規(guī)化。不過,
normalize
方法目前不能識別三個或三個以上字符的合成。這種情況下,還是只能使用正則表達式,通過 Unicode 編號區(qū)間判斷。 -
實例方法:repeat()
repeat
方法返回一個新字符串,表示將原字符串重復(fù)n
次。// 參數(shù)如果是小數(shù),會被取整。 'na'.repeat(2.9) // "nana" // 參數(shù)是負數(shù)或者Infinity,會報錯。 'na'.repeat(Infinity) // RangeError 'na'.repeat(-1) // RangeError // 參數(shù)是 0 到-1 之間的小數(shù),則等同于 0 'na'.repeat(-0.9) // "" // 參數(shù)NaN等同于 0。 'na'.repeat(NaN) // "" // 參數(shù)是字符串,則會先轉(zhuǎn)換成數(shù)字。 'na'.repeat('na') // "" 'na'.repeat('3') // "nanana"
-
實例方法:padStart(),padEnd()
ES2017 引入了字符串補全長度的功能。
如果某個字符串不夠指定長度,會在頭部或尾部補全。
padStart()
用于頭部補全,padEnd()
用于尾部補全。padStart()
和padEnd()
一共接受兩個參數(shù),第一個參數(shù)是字符串補全生效的最大長度,第二個參數(shù)是用來補全的字符串(省略則默認填充空格)。// 如果原字符串的長度,等于或大于最大長度,則字符串補全不生效,返回原字符串。 'xxx'.padStart(2, 'ab') // 'xxx' // 如果用來補全的字符串與原字符串,兩者的長度之和超過了最大長度,則會截去超出位數(shù)的補全字符串。 'abc'.padStart(10, '0123456789') // '0123456abc'
常見用途:
- 為數(shù)值補全指定位數(shù)
- 提示字符串格式
-
實例方法:trimStart(),trimEnd()
ES2019 新增,它們的行為與
trim()
一致。trimStart()
消除字符串頭部的空格,trimEnd()
消除尾部的空格。它們返回的都是新字符串,不會修改原始字符串。瀏覽器還部署了額外的兩個方法,
trimLeft()
是trimStart()
的別名,trimRight()
是trimEnd()
的別名。 -
實例方法:matchAll() —— 返回一個正則表式在當前字符串的所有匹配
-
實例方法:at() ——
at()
方法接受一個整數(shù)作為參數(shù),返回參數(shù)指定位置的字符,支持負索引(即倒數(shù)的位置)。 -
實例方法:replaceAll()
如果
searchValue
是一個不帶有g
修飾符的正則表達式(只寫為字符串,默認全局替換),replaceAll()
會報錯。這一點跟replace()
(不寫全局修飾符,只替換找到的第一個)不同。// 不報錯 'aabbcc'.replace(/b/, '_') // 報錯 'aabbcc'.replaceAll(/b/, '_')
上面例子中,
/b/
不帶有g
修飾符,會導(dǎo)致replaceAll()
報錯。replaceAll()
的第二個參數(shù)replacement
是一個字符串,表示替換的文本,其中可以使用一些特殊字符串。
Boolean 類型(邏輯類型)
僅包含兩個值:true
和 false
。
原始類型的方法
-
除
null
和undefined
以外的原始類型都提供了許多有用的方法。 -
從形式上講,這些方法通過臨時對象工作,但 Js 引擎可以很好地調(diào)整,以在內(nèi)部對其進行優(yōu)化,因此調(diào)用它們并不需要太高的成本。
let str = "Hello"; alert( str.toUpperCase() ); // HELLO
以下是
str.toUpperCase()
中實際發(fā)生的情況:- 字符串
str
是一個原始值。因此,在訪問其屬性時,會創(chuàng)建一個包含字符串字面值的特殊對象,并且具有可用的方法,例如toUpperCase()
。 - 該方法運行并返回一個新的字符串(由
alert
顯示)。 - 特殊對象被銷毀,只留下原始值
str
。
重要例子
let str = "Hello"; str.test = 5; // (*) alert(str.test);
根據(jù)你是否開啟了嚴格模式
use strict
,會得到如下結(jié)果:-
undefined
(非嚴格模式) - 報錯(嚴格模式)。
為什么?讓我們看看在
(*)
那一行到底發(fā)生了什么:- 當訪問
str
的屬性時,一個“對象包裝器”被創(chuàng)建了。 - 在嚴格模式下,向其寫入內(nèi)容會報錯。
- 否則,將繼續(xù)執(zhí)行帶有屬性的操作,該對象將獲得
test
屬性,但是此后,“對象包裝器”將消失(對應(yīng)上訴的特殊對象被銷毀),因此在最后一行,str
并沒有該屬性的蹤跡。
這個例子清楚地表明,原始類型不是對象。
它們不能存儲額外的數(shù)據(jù)。
let str = new String("hello"); str.name = "String" alert(str.name);
但是這樣在嚴格or非嚴格模式下可以,原因:真正創(chuàng)建了一個對象
- 字符串
構(gòu)造器 String/Number/Boolean
僅供內(nèi)部使用
像 Java 這樣的一些語言允許我們使用 new Number(1)
或 new Boolean(false)
等語法,明確地為原始類型創(chuàng)建“對象包裝器”。在 Js 中,由于歷史原因,這也是可以的,但極其 不推薦。因為這樣會出問題。
例如:
alert( typeof 0 ); // "number"
alert( typeof new Number(0) ); // "object"!
對象在 if
中始終為真,所以此處的 alert 將顯示:
let zero = new Number(0);
if (zero) { // zero 為 true,因為它是一個對象
alert( "zero is truthy?!?" );
}
另一方面,調(diào)用不帶關(guān)鍵字 new
的 String/Number/Boolean
函數(shù)是可以的且有效的。它們不是用來創(chuàng)建對象包裝器,而是將一個值轉(zhuǎn)換為相應(yīng)的類型:轉(zhuǎn)成字符串、數(shù)字或布爾值(原始類型)。
例如,下面完全是有效的:
let num = Number("123"); // 將字符串轉(zhuǎn)成數(shù)字
null 值
特殊的 null
值不屬于上述任何一種類型,它構(gòu)成了一個獨立的類型,只包含 null
值。
相比較于其他編程語言,Js 中的 null
不是一個 “對不存在的 object
的引用” 或者 “null 指針”。
Js 中的 null
僅僅是一個代表“無”、“空”或“值未知”的特殊值。
typeof null === 'object' 為 Js 的一個遺留錯誤。
undefined 值
undefined
的含義是未被賦值。
如果一個變量已被聲明,但未被賦值,那么它的值就是 undefined
。
Object 類型和 Symbol 類型
object
類型是一個特殊的類型。其他所有的數(shù)據(jù)類型都被稱為“原始類型”,因為它們的值只包含一個單獨的內(nèi)容(字符串、數(shù)字或其他)。相反,object
則用于儲存數(shù)據(jù)集合和更復(fù)雜的實體,它被稱為“引用類型”。
symbol
類型用于創(chuàng)建對象的唯一標識符。
typeof 運算符
typeof
運算符返回參數(shù)的類型。
當我們想要分別處理不同類型值的時候,或者想快速進行數(shù)據(jù)類型檢驗時,非常有用。
對 typeof x
的調(diào)用會以字符串的形式返回數(shù)據(jù)類型。
-
typeof null
的結(jié)果為"object"
。這是官方承認的typeof
的錯誤,這個問題來自于 Js 語言的早期階段,并為了兼容性而保留了下來。null
絕對不是一個object
。null
有自己的類型,它是一個特殊值。typeof
的行為在這里是錯誤的。 -
typeof alert
的結(jié)果是"function"
,因為alert
在 Js 語言中是一個函數(shù)。在 Js 語言中沒有一個特別的 “function” 類型。函數(shù)隸屬于object
類型。但是typeof
會對函數(shù)區(qū)分對待,并返回"function"
。這也是來自于 Js 語言早期的問題。
typeof(x)
語法你可能還會遇到另一種語法:
typeof(x)
。它與typeof x
相同。簡單點說:
typeof
是一個操作符,不是一個函數(shù)。這里的括號不是typeof
的一部分。它是數(shù)學運算分組的括號。通常,這樣的括號里包含的是一個數(shù)學表達式,例如
(2 + 2)
,但這里它只包含一個參數(shù)(x)
。從語法上講,它們允許在typeof
運算符和其參數(shù)之間不打空格。
與瀏覽器的交互
與用戶交互的 3 個瀏覽器的特定函數(shù):
-
alert
顯示信息。
-
prompt
顯示信息要求用戶輸入文本。點擊確定返回文本,點擊取消或按下 Esc 鍵返回
null
。 -
confirm
顯示信息等待用戶點擊確定或取消。點擊確定返回
true
,點擊取消或按下 Esc 鍵返回false
。
這些方法都是模態(tài)的:它們暫停腳本的執(zhí)行,并且不允許用戶與該頁面的其余部分進行交互,直到窗口被解除。
上述所有方法共有兩個限制:
- 模態(tài)窗口的確切位置由瀏覽器決定。通常在頁面中心。
- 窗口的確切外觀也取決于瀏覽器。我們不能修改它。
這就是簡單的代價。還有其他一些方式可以顯示更漂亮的窗口,并與用戶進行更豐富的交互。
類型轉(zhuǎn)換
有三種常用的類型轉(zhuǎn)換:
字符串轉(zhuǎn)換 —— 轉(zhuǎn)換發(fā)生在輸出內(nèi)容的時候,也可以通過 String(value)
進行顯式轉(zhuǎn)換。
數(shù)字型轉(zhuǎn)換 —— 轉(zhuǎn)換發(fā)生在進行算術(shù)操作時,也可以通過 Number(value)
進行顯式轉(zhuǎn)換。
數(shù)字型轉(zhuǎn)換遵循以下規(guī)則:
值 | 變成 |
---|---|
undefined |
NaN |
null |
0 |
true / false |
1 / 0 |
string |
字符串兩端的空白字符(空格、換行符 \n 、制表符 \t 等)會被忽略。空字符串變成 0 。轉(zhuǎn)換出錯則輸出 NaN 。 |
布爾型轉(zhuǎn)換 —— 轉(zhuǎn)換發(fā)生在進行邏輯操作時,也可以通過 Boolean(value)
進行顯式轉(zhuǎn)換。
布爾型轉(zhuǎn)換遵循以下規(guī)則:
值 | 變成 |
---|---|
0 , null , undefined , NaN , ""
|
false |
其他值 | true |
上述的大多數(shù)規(guī)則都容易理解。通常會犯錯誤的例子有以下幾個:
-
對
undefined
進行數(shù)字型轉(zhuǎn)換時,輸出結(jié)果為NaN
,而非0
。 -
對
"0"
和只有空格的字符串(比如:" "
)進行布爾型轉(zhuǎn)換時,輸出結(jié)果為true
。 -
對于什么時候時,"+"是字符串的拼接還是加法:
- 若第一個操作數(shù)為字符串,即為字符串的拼接
- 若第一個操作數(shù)不是字符串,如為 true 這樣的boolean型,即為加法
數(shù)字型轉(zhuǎn)化:一元運算符 +
加號 +
有兩種形式。
一種是二元運算符,另一種是一元運算符。
一元運算符加號 +
應(yīng)用于單個值,對數(shù)字沒有任何作用,但如果運算元不是數(shù)字,加號 +
則會將其轉(zhuǎn)化為數(shù)字。它的效果和 Number(...)
相同,但是更加簡短。
例如:
// 對數(shù)字無效
let y = -2;
alert( +y ); // -2
// 轉(zhuǎn)化非數(shù)字
alert( +true ); // 1
alert( +"" ); // 0
值的比較
奇怪的結(jié)果:null vs 0
通過比較 null
和 0 可得:
alert( null > 0 ); // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true
上面的結(jié)果完全打破了你對數(shù)學的認識。在最后一行代碼顯示 “null
大于等于 0 為 true ” 的情況下,前兩行代碼中一定會有一個是正確的,然而事實表明它們的結(jié)果都是 false。
為什么會出現(xiàn)這種反常結(jié)果,這是因為相等性檢查 ==
和普通比較符 > < >= <=
的代碼邏輯是相互獨立的。
進行值的比較時,null
會被轉(zhuǎn)化為數(shù)字,因此它被轉(zhuǎn)化為了 0
。
這就導(dǎo)致了(3)中 null >= 0
返回值是 true,(1)中 null > 0
返回值是 false。
undefined
和 null
在相等性檢查 ==
中不會進行任何的類型轉(zhuǎn)換,它們有自己獨立的比較規(guī)則,所以除了它們自己與自己相等以及它們之間互等外,不會等于任何其他的值。這就解釋了為什么(2)中 null == 0
會返回 false。
特立獨行的 undefined
undefined
不應(yīng)該被與其他值進行比較:
alert( undefined > 0 ); // false (1)
alert( undefined < 0 ); // false (2)
alert( undefined == 0 ); // false (3)
為何它看起來如此厭惡 0?返回值都是 false!
原因如下:
-
(1)
和(2)
都返回false
是因為undefined
在比較中被轉(zhuǎn)換為了NaN
,而NaN
是一個特殊的數(shù)值型值,它與任何值進行比較都會返回false
。 -
(3)
返回false
是因為這是一個相等性檢查,而undefined
只與null
相等,不會與其他值相等。
連續(xù)比較
1 < 2 < 3 --> true;
// 這里應(yīng)該是1 < 2為true,true < 3的時候true轉(zhuǎn)化為了1所以是true;
3 < 2 < 1 --> true;
// 這里應(yīng)該是3 < 2為false,false < 1的時候false轉(zhuǎn)化為了0所以是true;
總結(jié)
- 比較運算符(>、>=、<、<=、==、!=、===、!==)始終返回布爾值。
- 字符串的比較,會按照“詞典”順序逐字符地比較大小。
- 當對不同類型的值進行比較(不包括
===
與!==
)時,它們會先被轉(zhuǎn)化為數(shù)字再進行比較。 - 在非嚴格相等
==
下,null
和undefined
相等且各自不等于任何其他的值。 - 在使用
>
或<
進行比較時,需要注意變量可能為null/undefined
的情況。比較好的方法是單獨檢查變量是否等于null/undefined
。
邏輯運算符
Js 中有四個邏輯運算符:||
(或),&&
(與),!
(非),??
(空值合并運算符)。
雖然它們被稱為“邏輯”運算符,但這些運算符卻可以被應(yīng)用于任意類型的值,而不僅僅是布爾值。它們的結(jié)果也同樣可以是任意類型。
-
運算優(yōu)先級 : ! > && > ||
-
對于
或||
和與&&
運算:都是短路運算
或運算符做了如下的事情:
- 從左到右依次計算操作數(shù)。
- 處理每一個操作數(shù)時,都將其轉(zhuǎn)化為布爾值。如果結(jié)果是
true
,就停止計算,返回這個操作數(shù)的初始值。 - 如果所有的操作數(shù)都被計算過(也就是,轉(zhuǎn)換結(jié)果都是
false
),則返回最后一個操作數(shù)。
返回的值是操作數(shù)的初始形式,不會做布爾轉(zhuǎn)換。
換句話說,一個或運算
||
的鏈,將返回第一個真值,如果不存在真值,就返回該鏈的最后一個值。例如:
alert( 1 || 0 ); // 1(1 是真值) alert( null || 1 ); // 1(1 是第一個真值) alert( null || 0 || 1 ); // 1(第一個真值) alert( undefined || null || 0 ); // 0(都是假值,返回最后一個值)
-
兩個非運算
!!
有時候用來將某個值轉(zhuǎn)化為布爾類型,功能類似于內(nèi)建函數(shù)Boolean()
重要示例:
alert( alert(1) || 2 || alert(3) );
對 alert
的調(diào)用沒有返回值,或者說返回的是 undefined
。
- 第一個或運算
||
對它的左值alert(1)
進行了計算。這就顯示了第一條信息1
。 - 函數(shù)
alert
返回了undefined
,所以或運算繼續(xù)檢查第二個操作數(shù)以尋找真值。 - 第二個操作數(shù)
2
是真值,所以執(zhí)行就中斷了。2
被返回,并且被外層的 alert 顯示。
這里不會顯示 3
,因為運算沒有抵達 alert(3)
。
空值合并運算符 '??'
-
??
提供了一種從列表中選擇第一個“已定義的”值(值既不是null也不是undefined)的簡便方式。
它被用于為變量分配默認值:
// 當 height 的值為 null 或 undefined 時,將 height 的值設(shè)置為 100
height = height ?? 100;
-
與 || 運算符 比較
它們之間重要的區(qū)別是:
-
||
返回第一個 真 值。 -
??
返回第一個 已定義的 值。
換句話說,
||
無法區(qū)分false
、0
、空字符串""
和null/undefined
。它們都一樣 —— 假值。如果其中任何一個是||
的第一個參數(shù),那么我們將得到第二個參數(shù)作為結(jié)果。 -
-
??
運算符的優(yōu)先級非常低,僅略高于?
和=
,因此在表達式中使用它時請考慮添加括號。 -
如果沒有明確添加括號,不能將其與
||
或&&
一起使用。
循環(huán):While和for
三種循環(huán):
-
while
—— 每次迭代之前都要檢查條件。 -
do...while
—— 每次迭代后都要檢查條件。 -
for (;;)
—— 每次迭代之前都要檢查條件,可以使用其他設(shè)置。
通常使用 while(true)
來構(gòu)造“無限”循環(huán)。
“無限”循環(huán)和其他循環(huán)一樣,都可以通過 break
指令來終止。
如果我們不想在當前迭代中做任何事,并且想要轉(zhuǎn)移至下一次迭代,那么可以使用 continue
指令。
標簽是 break/continue
跳出嵌套循環(huán)以轉(zhuǎn)到外部的唯一方法。
標簽 是在循環(huán)之前帶有冒號的標識符:
labelName: for (...) {
...
}
break <labelName>
語句跳出循環(huán)至標簽處:
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// 如果是空字符串或被取消,則中斷并跳出這兩個循環(huán)。
if (!input) break outer; // (*)
// 用得到的值做些事……
}
}
alert('Done!');
上述代碼中,break outer
向上尋找名為 outer
的標簽并跳出當前循環(huán)。因此,控制權(quán)直接從 (*)
轉(zhuǎn)至 alert('Done!')
。
函數(shù)
函數(shù)聲明
function name(parameters, delimited, by, comma) {
/* code */
}
-
作為參數(shù)傳遞給函數(shù)的值,會被復(fù)制到函數(shù)的局部變量。
-
函數(shù)可以訪問外部變量。但它只能從內(nèi)到外起作用。函數(shù)外部的代碼看不到函數(shù)內(nèi)的局部變量。
-
函數(shù)可以返回值。如果沒有返回值,則其返回的結(jié)果是
undefined
。默認值 (ES6)
如果一個函數(shù)被調(diào)用,但有參數(shù)(argument)未被提供,那么相應(yīng)的值就會變成
undefined
。可以使用
=
為函數(shù)聲明中的參數(shù)指定所謂的“默認”(如果對應(yīng)參數(shù)的值未被傳遞則使用)值:function showMessage(from, text = "no text given") { alert( from + ": " + text ); } showMessage("Ann"); // Ann: no text given // 等同于 showMessage("Ann", undefined)
如果非尾部的參數(shù)設(shè)置默認值,實際上這個參數(shù)是沒法省略的,要顯式設(shè)置為undefined,null沒有這個效果
默認參數(shù)的計算
這里
"no text given"
是一個字符串,但它可以是更復(fù)雜的表達式,并且只會在缺少參數(shù)時才會被計算和分配。所以,這也是可能的:function showMessage(from, text = anotherFunction()) { // anotherFunction() 僅在沒有給定 text 時執(zhí)行,其運行結(jié)果將成為 text 的值 }
在 Js 老代碼中的默認參數(shù)
ES6 前,Js 不支持默認參數(shù)的語法。所以人們使用其他方式來設(shè)置默認參數(shù)。
如今,我們會在舊代碼中看到它們。
例如,顯式地檢查
undefined
:function showMessage(from, text) { if (text === undefined) { text = 'no text given'; } alert( from + ": " + text ); }
或者使用
||
運算符:function showMessage(from, text) { // 如果 text 的值為假值,則分配默認值 // 這樣賦值 text == "" 與 text 無值相同 text = text || 'no text given'; ... }
后備的默認參數(shù)
現(xiàn)代 Js 引擎支持 [空值合并運算符]
??
,它在大多數(shù)假值(例如0
)應(yīng)該被視為“正常值”時更具優(yōu)勢:function showCount(count) { // 如果 count 為 undefined 或 null,則提示 "unknown" alert(count ?? "unknown"); } showCount(0); // 0 showCount(null); // unknown showCount(); // unknown
為了使代碼簡潔易懂,建議在函數(shù)中主要使用局部變量和參數(shù),而不是外部變量。
與不獲取參數(shù)但將修改外部變量作為副作用的函數(shù)相比,獲取參數(shù)、使用參數(shù)并返回結(jié)果的函數(shù)更容易理解。
函數(shù)命名
- 函數(shù)名應(yīng)該清楚地描述函數(shù)的功能。當我們在代碼中看到一個函數(shù)調(diào)用時,一個好的函數(shù)名能夠讓我們馬上知道這個函數(shù)的功能是什么,會返回什么。
- 一個函數(shù)是一個行為,所以函數(shù)名通常是動詞。
- 目前有許多優(yōu)秀的函數(shù)名前綴,如
create…
、show…
、get…
、check…
等等。使用它們來提示函數(shù)的作用吧。
函數(shù)表達式
使用函數(shù)的方法有兩個:
- 使用函數(shù)聲明
- 使用函數(shù)表達式
注意:
-
函數(shù)是值。它們可以在代碼的任何地方被分配,復(fù)制或聲明。
-
如果函數(shù)在主代碼流中被聲明為單獨的語句,則稱為“函數(shù)聲明”。
-
如果該函數(shù)是作為表達式的一部分創(chuàng)建的,則稱其“函數(shù)表達式”。
-
在執(zhí)行代碼塊之前,內(nèi)部算法會先處理函數(shù)聲明。所以函數(shù)聲明在其被聲明的代碼塊內(nèi)的任何位置都是可見的。但嚴格模式下,當一個函數(shù)聲明在一個代碼塊內(nèi)時,它在該代碼塊內(nèi)的任何位置都是可見的。但在代碼塊外不可見。
let age = prompt("What is your age?", 18); // 有條件地聲明一個函數(shù) if (age < 18) { function welcome() { alert("Hello!"); } } else { function welcome() { alert("Greetings!"); } } // ……稍后使用 welcome(); // Error: welcome is not defined
-
函數(shù)表達式在執(zhí)行流程到達時創(chuàng)建。
在大多數(shù)情況下,當我們需要聲明一個函數(shù)時,最好使用函數(shù)聲明,因為函數(shù)在被聲明之前也是可見的。這使我們在代碼組織方面更具靈活性,通常也會使得代碼可讀性更高。
所以,僅當函數(shù)聲明不適合對應(yīng)的任務(wù)時,才應(yīng)使用函數(shù)表達式。
箭頭函數(shù)(ES6)
箭頭函數(shù)對于簡單的操作很方便,特別是對于單行的函數(shù)。它具體有兩種形式:
- 不帶花括號:
(...args) => expression
—— 右側(cè)是一個表達式:函數(shù)計算表達式并返回其結(jié)果。如果只有一個參數(shù),則可以省略括號,例如n => n*2
。 - 帶花括號:
(...args) => { body }
—— 花括號允許我們在函數(shù)中編寫多個語句,但是需要顯式地return
來返回一些內(nèi)容。
使用注意點
箭頭函數(shù)有幾個使用注意點:
(1)以下4個變量在箭頭函數(shù)之中是不存在的,其指向外層函數(shù)的對應(yīng)變量:
-
this
:this
在箭頭函數(shù)之中是不存在的,其指向外層函數(shù)的對應(yīng)變量。所以箭頭函數(shù)體內(nèi)的this
對象,就是定義時所在的對象,而不是使用時所在的對象。 -
arguments
:建議不要在箭頭函數(shù)中使用arguments
,可以用 rest 參數(shù)代替。 super
new.target
(2)因為箭頭函數(shù)沒有this
,所以也就不能用作構(gòu)造函數(shù),也就是說,不可以使用new
命令,否則會拋出一個錯誤。
(3)由于箭頭函數(shù)沒有自己的this
,所以當然也就不能用call()
、apply()
、bind()
這些方法去改變this
的指向。
(4)不可以使用yield
命令,因此箭頭函數(shù)不能用作 Generator 函數(shù)。
不適用場合
由于箭頭函數(shù)使得this
從“動態(tài)”變成“靜態(tài)”,下面2個場合不應(yīng)該使用箭頭函數(shù):
-
第1個場合是定義對象的方法,且該方法內(nèi)部包括
this
: -
第2個場合是需要動態(tài)
this
的時候,也不應(yīng)使用箭頭函數(shù)。var button = document.getElementById('press'); button.addEventListener('click', () => { this.classList.toggle('on'); });
上面代碼運行時,點擊按鈕會報錯,因為
button
的監(jiān)聽函數(shù)是一個箭頭函數(shù),導(dǎo)致里面的this
就是全局對象。如果改成普通函數(shù),this
就會動態(tài)指向被點擊的按鈕對象。
Rest 參數(shù)與 Spread 語法 (ES6)
當我們在函數(shù)中看到 "..."
時,它要么是 rest 參數(shù),要么是 spread 語法。
有一個簡單的方法可以區(qū)分它們:
- 若
...
出現(xiàn)在函數(shù)參數(shù)列表的最后,那么它就是 rest 參數(shù),它會把參數(shù)列表中剩余的參數(shù)收集到一個數(shù)組中。 - 若
...
出現(xiàn)在函數(shù)調(diào)用或類似的表達式中,那它就是 spread 語法,它會把一個數(shù)組(或者可迭代對象)展開為列表。
使用場景:
- Rest 參數(shù)用于創(chuàng)建可接受任意數(shù)量參數(shù)的函數(shù)。
- Spread 語法用于將數(shù)組(或者可迭代對象)傳遞給通常需要含有許多參數(shù)的函數(shù)。
我們可以使用這兩種語法輕松地互相轉(zhuǎn)換列表與參數(shù)數(shù)組。
舊式的 arguments
(類數(shù)組且可迭代的對象)也依然能夠幫助我們獲取函數(shù)調(diào)用中的所有參數(shù)。
變量作用域,閉包 (ES6)
let、const 具有塊級作用域
在 Js 中,閉包(closure)是指一個函數(shù)能夠訪問并記住它被創(chuàng)建時的詞法環(huán)境,即使該函數(shù)在其詞法作用域之外執(zhí)行。
閉包由兩個部分組成:函數(shù)本身和函數(shù)創(chuàng)建時的詞法環(huán)境(隱藏的 [[Environment]]
屬性)。詞法環(huán)境是指在函數(shù)定義時存在的變量集合,包括函數(shù)內(nèi)部聲明的變量、函數(shù)參數(shù)以及外部作用域中的變量。閉包可以捕獲和存儲這些變量的引用,即使函數(shù)在定義后被調(diào)用或者返回出去時,仍然可以訪問這些變量。
閉包的一個常見用途是創(chuàng)建私有變量。通過將變量定義在外部函數(shù)中,并在內(nèi)部函數(shù)中引用這些變量,可以實現(xiàn)對這些變量的私有性保護,防止外部代碼直接訪問或修改這些變量。
例題一
函數(shù) sayHi 使用外部變量。當函數(shù)運行時,將使用哪個值?
let name = "John";
function sayHi() {
alert("Hi, " + name);
}
name = "Pete";
sayHi(); // 會顯示什么:"John" 還是 "Pete"?
答案:Pete。
函數(shù)將從內(nèi)到外依次在對應(yīng)的詞法環(huán)境中尋找目標變量,它使用最新的值。
舊變量值不會保存在任何地方。當一個函數(shù)想要一個變量時,它會從自己的詞法環(huán)境或外部詞法環(huán)境中獲取當前值。
例題二
用相同的 makeCounter
函數(shù)創(chuàng)建了兩個計數(shù)器(counters):counter
和 counter2
。
它們是獨立的嗎?第二個 counter 會顯示什么?0,1
或 2,3
還是其他?
function makeCounter() {
let count = 0;
return function() {
return count++;
};
}
let counter = makeCounter();
let counter2 = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
alert( counter2() ); // ?
alert( counter2() ); // ?
答案是:0,1。
函數(shù) counter
和 counter2
是通過 makeCounter
的不同調(diào)用創(chuàng)建的。
因此,它們具有獨立的外部詞法環(huán)境,每一個都有自己的 count
。
例題三
let x = 1;
function func() {
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 2;
}
func();
在這個例子中,可以觀察到“不存在”的變量和“未初始化”的變量之間的特殊差異。
從程序執(zhí)行進入代碼塊(或函數(shù))的那一刻起,變量就開始進入“未初始化”狀態(tài)。它一直保持未初始化狀態(tài),直至程序執(zhí)行到相應(yīng)的 let
語句。
換句話說,一個變量從技術(shù)的角度來講是存在的,但是在 let
之前還不能使用。
function func() {
// 引擎從函數(shù)開始就知道局部變量 x,
// 但是變量 x 一直處于“未初始化”(無法使用)的狀態(tài),直到結(jié)束 let(“死區(qū)”)
// 因此答案是 error
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 2;
// 如果聲明改為var x = 2; 由于變量提升,不會報錯,而是會打印出undefined
}
變量暫時無法使用的區(qū)域(從代碼塊的開始到 let
)有時被稱為“死區(qū)”。
Let 和 Const 命令
let 命令
基本用法
let
用來聲明變量,用法類似于var,但let
只在其代碼塊內(nèi)有效。
for
循環(huán)還有一個特別之處,就是設(shè)置循環(huán)變量的那部分是一個父作用域,而循環(huán)體內(nèi)部是一個單獨的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
上面代碼正確運行,輸出了 3 次abc
。這表明函數(shù)內(nèi)部的變量i
與循環(huán)變量i
不在同一個作用域,有各自單獨的作用域(同一個作用域不可使用 let
重復(fù)聲明同一個變量)。
不存在變量提升
var
命令會發(fā)生“變量提升”現(xiàn)象,即變量可以在聲明之前使用,值為undefined
。為了糾正這種現(xiàn)象,let
命令改變了語法行為,它所聲明的變量一定要在聲明后使用,否則報錯。
暫時性死區(qū)
暫時性死區(qū)的本質(zhì)就是,只要一進入當前作用域,所要使用的變量就已經(jīng)存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量。
只要塊級作用域內(nèi)存在let
命令,它所聲明的變量就“綁定”(binding)這個區(qū)域,不再受外部的影響。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代碼中,存在全局變量tmp
,但是塊級作用域內(nèi)let
又聲明了一個局部變量tmp
,導(dǎo)致后者綁定這個塊級作用域,所以在let
聲明變量前,對tmp
賦值會報錯。
例子一
“暫時性死區(qū)”也意味著typeof
不再是一個百分之百安全的操作。
typeof x; // ReferenceError
let x;
上面代碼中,變量x
使用let
命令聲明,所以在聲明之前,都屬于x
的“死區(qū)”,只要用到該變量就會報錯。因此,typeof
運行時就會拋出一個ReferenceError
。
作為比較,如果一個變量根本沒有被聲明,使用typeof
反而不會報錯。
typeof undeclared_variable // "undefined"
例子二
// 不報錯
var x = x;
// 報錯
let x = x;
// ReferenceError: x is not defined
上面代碼報錯,也是因為暫時性死區(qū)。使用let
聲明變量時,只要變量在還沒有聲明完成前使用,就會報錯。上面這行就屬于這個情況,在變量x
的聲明語句還沒有執(zhí)行完成前,就去取x
的值,導(dǎo)致報錯”x 未定義“。
不允許重復(fù)聲明
let
不允許在相同作用域內(nèi),重復(fù)聲明同一個變量。
function func(arg) {
{
let arg;
}
}
func() // 不報錯
塊級作用域
ES6 引入了塊級作用域,明確允許在塊級作用域之中聲明函數(shù)。ES6 規(guī)定,塊級作用域之中,函數(shù)聲明語句的行為類似于let
,在塊級作用域之外不可引用。
如果改變了塊級作用域內(nèi)聲明的函數(shù)的處理規(guī)則,顯然會對老代碼產(chǎn)生很大影響。為了減輕因此產(chǎn)生的不兼容問題,ES6 在附錄 B里面規(guī)定,瀏覽器的實現(xiàn)可以不遵守上面的規(guī)定,有自己的行為方式。
- 允許在塊級作用域內(nèi)聲明函數(shù)。
- 函數(shù)聲明類似于
var
,即會提升到全局作用域或函數(shù)作用域的頭部。 - 同時,函數(shù)聲明還會提升到所在的塊級作用域的頭部。
注意,上面三條規(guī)則只對 ES6 的瀏覽器實現(xiàn)有效,其他環(huán)境的實現(xiàn)不用遵守,還是將塊級作用域的函數(shù)聲明當作let
處理。
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());
/*
根據(jù)這三條規(guī)則,瀏覽器的 ES6 環(huán)境中,塊級作用域內(nèi)聲明的函數(shù),行為類似于`var`聲明的變量。上面的例子實際運行的代碼如下。
*/
// 瀏覽器的 ES6 環(huán)境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function
考慮到環(huán)境導(dǎo)致的行為差異太大,應(yīng)該避免在塊級作用域內(nèi)聲明函數(shù)。如果確實需要,也應(yīng)該寫成函數(shù)表達式,而不是函數(shù)聲明語句。
還有一個需要注意的地方。ES6 的塊級作用域必須有大括號,如果沒有大括號,Js 引擎就認為不存在塊級作用域。
const 命令
const
聲明一個只讀的常量。一旦聲明,常量的值就不能改變。
- 一旦聲明變量,就必須立即初始化,不能留到以后賦值。
- 聲明的常量不提升,存在暫時性死區(qū),只能在聲明的位置后面使用。
- 聲明的常量,與
let
一樣不可重復(fù)聲明。
本質(zhì)
const
實際上保證的,并不是變量的值不得改動,而是變量指向的那個內(nèi)存地址所保存的數(shù)據(jù)不得改動。對于簡單類型的數(shù)據(jù)(數(shù)值、字符串、布爾值),值就保存在變量指向的那個內(nèi)存地址,因此等同于常量。但對于復(fù)合類型的數(shù)據(jù)(主要是對象和數(shù)組),變量指向的內(nèi)存地址,保存的只是一個指向?qū)嶋H數(shù)據(jù)的指針,const
只能保證這個指針是固定的(即總是指向另一個固定的地址),至于它指向的數(shù)據(jù)結(jié)構(gòu)是不是可變的,就完全不能控制了。因此,將一個對象聲明為常量必須非常小心。
可以通過將對象凍結(jié)來實現(xiàn)對象屬性的不可修改操作,除了將對象本身凍結(jié),對象的屬性也應(yīng)該凍結(jié)。下面是一個將對象徹底凍結(jié)的函數(shù)。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
ES6 聲明變量的六種方法
ES5 :兩種 —— var
命令和function
命令。
ES6 :六種 —— var
命令和function
命令,let
和const
命令,import
命令和class
命令。
老舊的 "var"
var
與 let/const
有兩個主要的區(qū)別:
-
var
聲明的變量沒有塊級作用域,它們僅在當前函數(shù)內(nèi)可見,或者全局可見(如果變量是在函數(shù)外聲明的)。 -
var
變量聲明在函數(shù)開頭就會被處理(腳本啟動對應(yīng)全局變量),變量提升。
函數(shù)對象,NFE
函數(shù)的類型是對象。
屬性
-
name
—— 函數(shù)的名字。通常取自函數(shù)定義,但如果函數(shù)定義時沒設(shè)定函數(shù)名,Js 會嘗試通過函數(shù)的上下文猜一個函數(shù)名(例如把賦值的變量名取為函數(shù)名)。 -
length
—— 函數(shù)定義時的入?yún)⒌膫€數(shù)。Rest 參數(shù)不參與計數(shù)。
如果函數(shù)是通過函數(shù)表達式的形式被聲明的(不是在主代碼流里),并且附帶了名字,那么它被稱為命名函數(shù)表達式(Named Function Expression)。這個名字可以用于在該函數(shù)內(nèi)部進行自調(diào)用,例如遞歸調(diào)用等。
此外,函數(shù)可以帶有額外的屬性。很多知名的 Js 庫都充分利用了這個功能。
它們創(chuàng)建一個“主”函數(shù),然后給它附加很多其它“輔助”函數(shù)。例如:
-
jQuery 庫創(chuàng)建了一個名為
$
的函數(shù)。 -
lodash 庫創(chuàng)建一個
_
函數(shù),然后為其添加了_.add
、_.keyBy
以及其它屬性 - 實際上,它們這么做是為了減少對全局空間的污染,這樣一個庫就只會有一個全局變量。這樣就降低了命名沖突的可能性。
尾調(diào)用優(yōu)化
什么是尾調(diào)用?
尾調(diào)用(Tail Call)是函數(shù)式編程的一個重要概念,本身非常簡單,一句話就能說清楚,就是指某個函數(shù)的最后一步是調(diào)用另一個函數(shù)。
function f(x){
return g(x);
}
尾調(diào)用優(yōu)化
尾調(diào)用之所以與其他調(diào)用不同,就在于它的特殊的調(diào)用位置。
我們知道,函數(shù)調(diào)用會在內(nèi)存形成一個“調(diào)用記錄”,又稱“調(diào)用幀”(call frame),保存調(diào)用位置和內(nèi)部變量等信息。如果在函數(shù)A
的內(nèi)部調(diào)用函數(shù)B
,那么在A
的調(diào)用幀上方,還會形成一個B
的調(diào)用幀。等到B
運行結(jié)束,將結(jié)果返回到A
,B
的調(diào)用幀才會消失。如果函數(shù)B
內(nèi)部還調(diào)用函數(shù)C
,那就還有一個C
的調(diào)用幀,以此類推。所有的調(diào)用幀,就形成一個“調(diào)用?!保╟all stack)。
尾調(diào)用由于是函數(shù)的最后一步操作,所以不需要保留外層函數(shù)的調(diào)用幀,因為調(diào)用位置、內(nèi)部變量等信息都不會再用到了,只要直接用內(nèi)層函數(shù)的調(diào)用幀,取代外層函數(shù)的調(diào)用幀就可以了。
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
上面代碼中,如果函數(shù)g
不是尾調(diào)用,函數(shù)f
就需要保存內(nèi)部變量m
和n
的值、g
的調(diào)用位置等信息。但由于調(diào)用g
之后,函數(shù)f
就結(jié)束了,所以執(zhí)行到最后一步,完全可以刪除f(x)
的調(diào)用幀,只保留g(3)
的調(diào)用幀。
這就叫做“尾調(diào)用優(yōu)化”(Tail call optimization),即只保留內(nèi)層函數(shù)的調(diào)用幀。如果所有函數(shù)都是尾調(diào)用,那么完全可以做到每次執(zhí)行時,調(diào)用幀只有一項,這將大大節(jié)省內(nèi)存。這就是“尾調(diào)用優(yōu)化”的意義。注意,只有不再用到外層函數(shù)的內(nèi)部變量,內(nèi)層函數(shù)的調(diào)用幀才會取代外層函數(shù)的調(diào)用幀,否則就無法進行“尾調(diào)用優(yōu)化”。
嚴格模式
ES6 的尾調(diào)用優(yōu)化只在嚴格模式下開啟,正常模式是無效的。
這是因為在正常模式下,函數(shù)內(nèi)部有兩個變量,可以跟蹤函數(shù)的調(diào)用棧。
-
func.arguments
:返回調(diào)用時函數(shù)的參數(shù)。 -
func.caller
:返回調(diào)用當前函數(shù)的那個函數(shù)。
函數(shù)參數(shù)的尾逗號 (ES2017)
允許函數(shù)的最后一個參數(shù)有尾逗號(trailing comma)
此前,函數(shù)定義和調(diào)用時,都不允許最后一個參數(shù)后面出現(xiàn)逗號。
Function.prototype.toString() (ES2019)
對函數(shù)實例的toString()
方法做出了修改,以前會省略注釋和空格,現(xiàn)在明確要求返回一模一樣的原始代碼。
"new Function" 語法
語法:
let func = new Function ([arg1, arg2, ...argN], functionBody);
由于歷史原因,參數(shù)也可以按逗號分隔符的形式給出。
以下三種聲明的含義相同:
new Function('a', 'b', 'return a + b'); // 基礎(chǔ)語法
new Function('a,b', 'return a + b'); // 逗號分隔
new Function('a , b', 'return a + b'); // 逗號和空格分隔
使用 new Function
創(chuàng)建的函數(shù),它的 [[Environment]]
指向全局詞法環(huán)境,而不是函數(shù)所在的外部詞法環(huán)境。因此,我們不能在 new Function
中直接使用外部變量。這有助于降低我們代碼出錯的可能。并且,從代碼架構(gòu)上講,顯式地使用參數(shù)傳值是一種更好的方法,并且避免了與使用壓縮程序而產(chǎn)生沖突的問題。
Object 對象
對象
對象存儲屬性(鍵值對),其中:
- 屬性的鍵必須是字符串或者 symbol(通常是字符串)。
- 值可以是任何類型。
使用下面的方法訪問屬性:
- 點符號:
obj.property
。 - 方括號
obj["property"]
,方括號允許從變量中獲取鍵,例如obj[varWithKey]
。
其他操作:
- 刪除屬性:
delete obj.prop
。 - 檢查是否存在給定鍵的屬性:
"key" in obj
。 - 遍歷對象:
for(let key in obj)
循環(huán)。
Js 中還有很多其他類型的對象:
-
Array
用于存儲有序數(shù)據(jù)集合 -
Date
用于存儲時間日期 -
Error
用于存儲錯誤信息
它們有著各自特別的特性,有時候大家會說“Array 類型”或“Date 類型”,但其實它們并不是自身所屬的類型,而是屬于一個對象類型即 “object”。
對象引用
對象通過引用被賦值和拷貝。換句話說,一個變量存儲的不是“對象的值”,而是一個對值的“引用”(內(nèi)存地址)。
因此,拷貝此類變量或?qū)⑵渥鳛楹瘮?shù)參數(shù)傳遞時,所拷貝的是引用,而不是對象本身。
所有通過被拷貝的引用的操作(如添加、刪除屬性)都作用在同一個對象上。
為了創(chuàng)建“真正的拷貝”(一個克?。褂?Object.assign
來做“淺拷貝”(嵌套對象被通過引用進行拷貝)或者使用“深拷貝”函數(shù)_.cloneDeep(obj) (存在于Lodash模塊中)。
語法是:
Object.assign(dest, [src1, src2, src3...])
- 第一個參數(shù)
dest
是指目標對象。 - 后面的參數(shù)
src1, ..., srcN
(可按需傳遞多個參數(shù))是源對象。 - 該方法將所有源對象的屬性拷貝到目標對象
dest
中。換句話說,從第二個開始的所有參數(shù)的屬性都被拷貝到第一個參數(shù)的對象中。(如果對象中的屬性出現(xiàn)重復(fù),以最重復(fù)后面的屬性的值作為最終的值) - 調(diào)用結(jié)果返回
dest
。
垃圾回收
- 垃圾回收是自動完成的,我們不能強制執(zhí)行或是阻止執(zhí)行。
- 當對象是可達狀態(tài)時,它一定是存在于內(nèi)存中的。
- 被引用與可訪問(從一個根)不同:一組相互連接的對象可能整體都不可達
- 對外引用不重要,只有傳入引用才可以使對象可達。
- 幾個對象相互引用,但外部沒有對其任意對象的引用,這些對象也可能是不可達的,并被從內(nèi)存中刪除。
對象方法,"this"
-
存儲在對象屬性中的函數(shù)被稱為“方法”。
-
方法允許對象進行像
object.doSomething()
這樣的“操作”。 -
方法可以將對象引用看為
this
,this 的值是在程序運行時得到的。一個函數(shù)在聲明時,可能就使用了this
,但是這個this
只有在函數(shù)被調(diào)用時才會有值。 -
可以在對象之間復(fù)制函數(shù)。
-
以“方法”的語法調(diào)用函數(shù)時:
object.method()
,調(diào)用過程中的this
值是object
。
請注意箭頭函數(shù)有些特別:它們沒有 this
。在箭頭函數(shù)內(nèi)部訪問到的 this
都是從外部獲取的。
function makeUser() {
return {
name: "John",
ref: this
};
}
let user = makeUser();
alert( user.ref.name ); // Error: Cannot read property 'name' of undefined
這是因為設(shè)置 this
的規(guī)則不考慮對象定義。只有調(diào)用那一刻才重要。
個人理解如下:函數(shù)調(diào)用makeUser()
就已經(jīng)決定了該函數(shù)中的this的值,因為它是直接調(diào)用,不是通過點符號作為方法調(diào)用,故此時函數(shù)中的this
為 undefined
,故其實返回的對象中的ref屬性就已經(jīng)為undefined
了。
function makeUser() {
return {
name: "John",
ref() {
return this;
}
};
}
let user = makeUser();
alert( user.ref().name ); // John
現(xiàn)在正常了,因為 user.ref()
是一個方法。this
的值為點符號 .
前的這個對象。
構(gòu)造器和操作符 "new"
-
構(gòu)造函數(shù),或簡稱構(gòu)造器,就是常規(guī)函數(shù),但大家對于構(gòu)造器有個共同的約定,就是其命名首字母要大寫。
-
構(gòu)造函數(shù)只能使用
new
來調(diào)用。- 一個新的空對象被創(chuàng)建并分配給
this
。 - 函數(shù)體執(zhí)行。通常它會修改
this
,為其添加新的屬性。 - 返回
this
的值。
- 一個新的空對象被創(chuàng)建并分配給
-
構(gòu)造器模式測試:new.target
在一個函數(shù)內(nèi)部,我們可以使用
new.target
屬性來檢查它是否被使用new
進行調(diào)用了。對于常規(guī)調(diào)用,它為 undefined,對于使用
new
的調(diào)用,則等于該構(gòu)造函數(shù):function User() { alert(new.target); } // 不帶 "new": User(); // undefined // 帶 "new": new User(); // function User { ... }
我們可以使用構(gòu)造函數(shù)來創(chuàng)建多個類似的對象。
對象屬性配置
屬性標志和屬性描述符
屬性標志
對象屬性(properties),除 value
外,還有三個特殊的特性(attributes),即所謂的“標志”:
-
writable
— 如果為true
,則值可以被修改,否則只可讀的。 -
enumerable
— 如果為true
,則會被在循環(huán)中列出,否則不會被列出。 -
configurable
— 如果為true
,則此屬性可以被刪除,這些特性也可以被修改,否則不可以。當
configurable
設(shè)置為false
,唯一可行的特性更改:writable true → false
對于不可配置的屬性,我們可以將
writable: true
更改為false
,從而防止其值被修改(以添加另一層保護)。但無法反向行之。
當用“常用的方式”創(chuàng)建一個屬性時(user.age = 18),標志都為 true
。但也可以隨時更改它們。
獲得標志
let user = {
name: "John"
};
// 語法:let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
// 返回值是一個所謂的“屬性描述符”對象:它包含值和所有的標志。
// 要一次獲取所有屬性描述符,我們可以使用 Object.getOwnPropertyDescriptors(obj) 方法。
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* 屬性描述符:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
修改標志:
let user = {};
Object.defineProperty(user, "name", {
value: "John"
});
// 語法:Object.defineProperty(obj, propertyName, descriptor)
// 如果該屬性存在,defineProperty 會更新其標志。否則,它會使用給定的值和標志創(chuàng)建屬性;在這種情況下,如果沒有提供標志,則會假定它是 false。
// 一次配置多個屬性
/* Object.defineProperties(obj, {
prop1: descriptor1,
prop2: descriptor2
// ...
});
*/
克隆對象
通常,當克隆一個對象時,使用賦值的方式來復(fù)制屬性
for (let key in user) {
clone[key] = user[key]
}
新方法:
// 克隆 obj 這個對象
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
區(qū)別:
- 舊方法并不能復(fù)制標志。所以如果想要一個“更好”的克隆,那么
Object.defineProperties
是首選。 -
for..in
會忽略 symbol 類型的和不可枚舉的屬性,但是Object.getOwnPropertyDescriptors
返回包含 symbol 類型的和不可枚舉的屬性在內(nèi)的 所有 屬性描述符。
設(shè)定一個全局的密封對象
屬性描述符在單個屬性的級別上工作。
還有一些限制訪問 整個 對象的方法:
-
Object.preventExtensions(obj) —— 禁止向?qū)ο筇砑有聦傩浴?/p>
-
Object.seal(obj) —— 禁止添加/刪除屬性。為所有現(xiàn)有的屬性設(shè)置
configurable: false
。 -
Object.freeze(obj) —— 禁止添加/刪除/更改屬性。為所有現(xiàn)有的屬性設(shè)置
configurable: false, writable: false
。
還有針對它們的測試:
-
Object.isExtensible(obj) —— 如果添加屬性被禁止,則返回
false
,否則返回true
。 -
Object.isSealed(obj) —— 如果添加/刪除屬性被禁止,并且所有現(xiàn)有的屬性都具有
configurable: false
則返回true
。 -
Object.isFrozen(obj) —— 如果添加/刪除/更改屬性被禁止,并且所有當前屬性都是
configurable: false, writable: false
,則返回true
。
屬性的 getter 和 setter
對象屬性分為兩類:
- 數(shù)據(jù)屬性。到目前為止,我們使用過的所有屬性都是數(shù)據(jù)屬性。
- 訪問器屬性(accessor property)。本質(zhì)上是用于獲取和設(shè)置值的函數(shù),但從外部代碼來看就像常規(guī)屬性。
訪問器描述符
訪問器屬性的描述符與數(shù)據(jù)屬性的不同。
對于訪問器屬性,沒有 value
和 writable
,但是有 get
和 set
函數(shù)。
訪問器描述符:
-
get
—— 一個沒有參數(shù)的函數(shù),在讀取屬性時工作, -
set
—— 帶有一個參數(shù)的函數(shù),當屬性被設(shè)置時調(diào)用, -
enumerable
—— 與數(shù)據(jù)屬性的相同, -
configurable
—— 與數(shù)據(jù)屬性的相同。
let user = {
name: "John",
surname: "Smith"
};
// 請注意,一個屬性要么是訪問器屬性(具有 get/set 方法),要么是數(shù)據(jù)屬性(具有 value),但不能兩者都是。如果我們試圖在同一個描述符中同時提供 get 和 value,則會出現(xiàn)錯誤。
Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
alert(user.fullName); // John Smith
for(let key in user) alert(key); // name, surname
可選鏈 "?."
可選鏈 ?.
語法有三種形式:
-
obj?.prop
—— 如果obj
存在則返回obj.prop
,否則返回undefined
。 -
obj?.[prop]
—— 如果obj
存在則返回obj[prop]
,否則返回undefined
。 -
obj.method?.()
—— 如果obj.method
存在則調(diào)用obj.method()
,否則返回undefined
。
正如我們所看到的,這些語法形式用起來都很簡單直接。?.
檢查左邊部分是否為 null/undefined
,如果不是則繼續(xù)運算。
?.
鏈使我們能夠安全地訪問嵌套屬性。
Symbol 類型
“symbol” 值表示唯一的標識符。
可以使用 Symbol()
來創(chuàng)建這種類型的值:
let id = Symbol();
創(chuàng)建時,我們可以給 symbol 一個描述(也稱為 symbol 名),這在代碼調(diào)試時非常有用:
// id 是描述為 "id" 的 symbol
let id = Symbol("id");
symbol 保證是唯一的。即使我們創(chuàng)建了許多具有相同描述的 symbol,它們的值也是不同。描述只是一個標簽,不影響任何東西。
例如,這里有兩個描述相同的 symbol —— 它們不相等:
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
symbol 不會被自動轉(zhuǎn)換為字符串
Js 中的大多數(shù)值都支持字符串的隱式轉(zhuǎn)換。
例如,我們可以 alert
任何值,都可以生效。symbol 比較特殊,它不會被自動轉(zhuǎn)換。但下面這個 alert
將會提示出錯:
let id = Symbol("id");
alert(id); // 類型錯誤:無法將 symbol 值轉(zhuǎn)換為字符串。
這是一種防止混亂的“語言保護”,因為字符串和 symbol 有本質(zhì)上的不同,不應(yīng)該意外地將它們轉(zhuǎn)換成另一個。
如果我們真的想顯示一個 symbol,我們需要在它上面調(diào)用 .toString()
,如下所示:
let id = Symbol("id");
alert(id.toString()); // Symbol(id),現(xiàn)在它有效了
或者獲取 symbol.description
屬性,只顯示描述(description):
let id = Symbol("id");
alert(id.description); // id
如果我們要在對象字面量 {...}
中使用 symbol,則需要使用方括號把它括起來。
let id = Symbol("id");
let user = {
name: "John",
[id]: 123 // 而不是 "id":123
};
如果我們希望同名的 symbol 相等,那么我們應(yīng)該使用全局注冊表。
要從注冊表中讀?。ú淮嬖趧t創(chuàng)建)symbol,請使用 Symbol.for(key)
。
該調(diào)用會檢查全局注冊表,如果有一個描述為 key
的 symbol,則返回該 symbol,否則將創(chuàng)建一個新 symbol(Symbol(key)
),并通過給定的 key
將其存儲在注冊表中。
Symbol.keyFor
內(nèi)部使用全局 symbol 注冊表來查找 symbol 的鍵。所以它不適用于非全局 symbol。如果 symbol 不是全局的,它將無法找到它并返回 undefined
。
symbol 有兩個主要的使用場景:
-
“隱藏” 對象屬性。
如果我們想要向“屬于”另一個腳本或者庫的對象添加一個屬性,我們可以創(chuàng)建一個 symbol 并使用它作為屬性的鍵。symbol 屬性不會出現(xiàn)在
for..in
中,因此它不會意外地被與其他屬性一起處理。并且,它不會被直接訪問,因為另一個腳本沒有我們的 symbol。因此,該屬性將受到保護,防止被意外使用或重寫。因此我們可以使用 symbol 屬性“秘密地”將一些東西隱藏到我們需要的對象中,但其他地方看不到它。
相反,Object.assign 會同時復(fù)制字符串和 symbol 屬性:
-
JavaScript 使用了許多系統(tǒng) symbol,這些 symbol 可以作為
Symbol.*
訪問。我們可以使用它們來改變一些內(nèi)建行為。例如,在本教程的后面部分,我們將使用Symbol.iterator
來進行 迭代 操作,使用Symbol.toPrimitive
來設(shè)置 對象原始值的轉(zhuǎn)換 等等。
從技術(shù)上說,symbol 不是 100% 隱藏的。有一個內(nèi)建方法 Object.getOwnPropertySymbols(obj) 允許我們獲取所有的 symbol。還有一個名為 Reflect.ownKeys(obj) 的方法可以返回一個對象的 所有 鍵,包括 symbol。但大多數(shù)庫、內(nèi)建方法和語法結(jié)構(gòu)都沒有使用這些方法。
對象 —— 原始值轉(zhuǎn)換
對象到原始值的轉(zhuǎn)換,是由許多期望以原始值作為值的內(nèi)建函數(shù)和運算符自動調(diào)用的。
這里有三種類型(hint):
-
"string"
(對于alert
和其他需要字符串的操作) -
"number"
(對于數(shù)學運算) -
"default"
(少數(shù)運算符,通常對象以和"number"
相同的方式實現(xiàn)"default"
轉(zhuǎn)換)
規(guī)范明確描述了哪個運算符使用哪個 hint。
轉(zhuǎn)換算法是:
-
調(diào)用
obj[Symbol.toPrimitive](hint)
如果這個方法存在,let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // 轉(zhuǎn)換演示: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500
-
否則,如果 hint 是 "string"
-
嘗試調(diào)用
obj.toString()
或obj.valueOf()
,無論哪個存在。 -
默認情況下:
/* 默認情況下,普通對象具有 toString 和 valueOf 方法: toString 方法返回一個字符串 "[object Object]"。 valueOf 方法返回對象自身。 */ let user = {name: "John"}; alert(user); // [object Object] alert(user.valueOf() === user); // true
-
-
否則,如果 hint 是 "number"或者"default"
- 嘗試調(diào)用
obj.valueOf()
或obj.toString()
,無論哪個存在。
- 嘗試調(diào)用
所有這些方法都必須返回一個原始值才能工作(如果已定義)。文章來源:http://www.zghlxwxcb.cn/news/detail-594351.html
在實際使用中,通常只實現(xiàn) obj.toString()
作為字符串轉(zhuǎn)換的“全能”方法就足夠了,該方法應(yīng)該返回對象的“人類可讀”表示,用于日志記錄或調(diào)試。文章來源地址http://www.zghlxwxcb.cn/news/detail-594351.html
到了這里,關(guān)于JavaScript學習筆記01(包含ES6語法)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!