国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

JavaScript學習筆記01(包含ES6語法)

這篇具有很好參考價值的文章主要介紹了JavaScript學習筆記01(包含ES6語法)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

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ù)雜,但是基本原理很簡單。

  1. 引擎(如果是瀏覽器,則引擎被嵌入在其中)讀?。ā敖馕觥保┠_本。
  2. 然后,引擎將腳本轉(zhuǎn)化(“編譯”)為機器語言。
  3. 然后,機器代碼快速地執(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、-InfinityNaN。

科學計數(shù)法

  • "e" 和 0 的數(shù)量附加到數(shù)字后。就像:123e6123 后面接 6 個 0 相同。
  • "e" 后面的負數(shù)將使數(shù)字除以 1 后面接著給定數(shù)量的零的數(shù)字。例如 123e-6 表示 0.000123123 的百萬分之一)。

多種進制

  • 可以直接在十六進制(0x),八進制(0oor00)和二進制(0bor0B)系統(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ù)字,則返回 trueNaN/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ù):將 12pt100px 之類的值轉(zhuǎn)換為數(shù)字

全局方法

  • 使用 parseInt/parseFloat 進行“軟”轉(zhuǎn)換,它從字符串中讀取數(shù)字,然后返回在發(fā)生 error 前可以讀取到的值。

定義在Number上的方法 (ES6)

  • ES6 將全局方法parseInt()parseFloat(),移植到Number對象上面,行為完全保持不變。這樣做的目的,是逐步減少全局性方法,使得語言逐步模塊化。

Math 對象的擴展

  • 使用 Math.floorMath.ceil,Math.trunc,Math.roundnum.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。
  • 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ù)的eE前后不能有分隔符。
  • 分隔符不能緊跟著進制的前綴0b、0B、0o、0O0x、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 類型

三種包含字符串的方式:

  1. 雙引號:"Hello"

  2. 單引號:'Hello'

  3. 反引號:`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 返回一個空字符串

  • 獲取子字符串,使用 slicesubstring。

    方法 選擇方式 負值參數(shù)
    slice(start, end) startend(不含 end),start可以比end大 允許
    substring(start, end) startend(不含 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。

  • 查找子字符串時,使用 indexOfincludes/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'
    

    常見用途:

    1. 為數(shù)值補全指定位數(shù)
    2. 提示字符串格式
  • 實例方法: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 類型(邏輯類型)

僅包含兩個值:truefalse。

原始類型的方法

  • nullundefined 以外的原始類型都提供了許多有用的方法。

  • 從形式上講,這些方法通過臨時對象工作,但 Js 引擎可以很好地調(diào)整,以在內(nèi)部對其進行優(yōu)化,因此調(diào)用它們并不需要太高的成本。

    let str = "Hello";
    
    alert( str.toUpperCase() ); // HELLO
    

    以下是 str.toUpperCase() 中實際發(fā)生的情況:

    1. 字符串 str 是一個原始值。因此,在訪問其屬性時,會創(chuàng)建一個包含字符串字面值的特殊對象,并且具有可用的方法,例如 toUpperCase()
    2. 該方法運行并返回一個新的字符串(由 alert 顯示)。
    3. 特殊對象被銷毀,只留下原始值 str。

    重要例子

    let str = "Hello";
    
    str.test = 5; // (*)
    
    alert(str.test);
    

    根據(jù)你是否開啟了嚴格模式 use strict,會得到如下結(jié)果:

    1. undefined(非嚴格模式)
    2. 報錯(嚴格模式)。

    為什么?讓我們看看在 (*) 那一行到底發(fā)生了什么:

    1. 當訪問 str 的屬性時,一個“對象包裝器”被創(chuàng)建了。
    2. 在嚴格模式下,向其寫入內(nèi)容會報錯。
    3. 否則,將繼續(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)鍵字 newString/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ù)類型。

  1. typeof null 的結(jié)果為 "object"這是官方承認的 typeof 的錯誤,這個問題來自于 Js 語言的早期階段,并為了兼容性而保留了下來。null 絕對不是一個 objectnull 有自己的類型,它是一個特殊值。typeof 的行為在這里是錯誤的。
  2. 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í)行,并且不允許用戶與該頁面的其余部分進行交互,直到窗口被解除。

上述所有方法共有兩個限制:

  1. 模態(tài)窗口的確切位置由瀏覽器決定。通常在頁面中心。
  2. 窗口的確切外觀也取決于瀏覽器。我們不能修改它。

這就是簡單的代價。還有其他一些方式可以顯示更漂亮的窗口,并與用戶進行更豐富的交互。

類型轉(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

  • 對于什么時候時,"+"是字符串的拼接還是加法:

    1. 若第一個操作數(shù)為字符串,即為字符串的拼接
    2. 若第一個操作數(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。

undefinednull 在相等性檢查 == 中不會進行任何的類型轉(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ù)字再進行比較。
  • 在非嚴格相等 == 下,nullundefined 相等且各自不等于任何其他的值。
  • 在使用 >< 進行比較時,需要注意變量可能為 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。

  1. 第一個或運算 || 對它的左值 alert(1) 進行了計算。這就顯示了第一條信息 1。
  2. 函數(shù) alert 返回了 undefined,所以或運算繼續(xù)檢查第二個操作數(shù)以尋找真值。
  3. 第二個操作數(shù) 2 是真值,所以執(zhí)行就中斷了。2 被返回,并且被外層的 alert 顯示。

這里不會顯示 3,因為運算沒有抵達 alert(3)。

空值合并運算符 '??'

  • ?? 提供了一種從列表中選擇第一個“已定義的”值(值既不是null也不是undefined)的簡便方式。

它被用于為變量分配默認值:

// 當 height 的值為 null 或 undefined 時,將 height 的值設(shè)置為 100
height = height ?? 100;
  • 與 || 運算符 比較

    它們之間重要的區(qū)別是:

    • || 返回第一個 值。
    • ?? 返回第一個 已定義的 值。

    換句話說,|| 無法區(qū)分 false0、空字符串 ""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ù)。它具體有兩種形式:

  1. 不帶花括號:(...args) => expression —— 右側(cè)是一個表達式:函數(shù)計算表達式并返回其結(jié)果。如果只有一個參數(shù),則可以省略括號,例如 n => n*2
  2. 帶花括號:(...args) => { body } —— 花括號允許我們在函數(shù)中編寫多個語句,但是需要顯式地 return 來返回一些內(nèi)容。

使用注意點

箭頭函數(shù)有幾個使用注意點:

(1)以下4個變量在箭頭函數(shù)之中是不存在的,其指向外層函數(shù)的對應(yīng)變量:

  • thisthis在箭頭函數(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):countercounter2

它們是獨立的嗎?第二個 counter 會顯示什么?0,12,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ù) countercounter2 是通過 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命令,letconst命令,import命令和class命令。

老舊的 "var"

varlet/const 有兩個主要的區(qū)別:

  1. var 聲明的變量沒有塊級作用域,它們僅在當前函數(shù)內(nèi)可見,或者全局可見(如果變量是在函數(shù)外聲明的)。
  2. 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é)果返回到AB的調(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)部變量mn的值、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ù)中的thisundefined,故其實返回的對象中的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)用。

    1. 一個新的空對象被創(chuàng)建并分配給 this
    2. 函數(shù)體執(zhí)行。通常它會修改 this,為其添加新的屬性。
    3. 返回 this 的值。
  • 構(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

對象屬性分為兩類:

  1. 數(shù)據(jù)屬性。到目前為止,我們使用過的所有屬性都是數(shù)據(jù)屬性。
  2. 訪問器屬性(accessor property)。本質(zhì)上是用于獲取和設(shè)置值的函數(shù),但從外部代碼來看就像常規(guī)屬性。

訪問器描述符

訪問器屬性的描述符與數(shù)據(jù)屬性的不同。

對于訪問器屬性,沒有 valuewritable,但是有 getset 函數(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

可選鏈 "?."

可選鏈 ?. 語法有三種形式:

  1. obj?.prop —— 如果 obj 存在則返回 obj.prop,否則返回 undefined。
  2. obj?.[prop] —— 如果 obj 存在則返回 obj[prop],否則返回 undefined。
  3. 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 有兩個主要的使用場景:

  1. “隱藏” 對象屬性。

    如果我們想要向“屬于”另一個腳本或者庫的對象添加一個屬性,我們可以創(chuàng)建一個 symbol 并使用它作為屬性的鍵。symbol 屬性不會出現(xiàn)在 for..in 中,因此它不會意外地被與其他屬性一起處理。并且,它不會被直接訪問,因為另一個腳本沒有我們的 symbol。因此,該屬性將受到保護,防止被意外使用或重寫。

    因此我們可以使用 symbol 屬性“秘密地”將一些東西隱藏到我們需要的對象中,但其他地方看不到它。

    相反,Object.assign 會同時復(fù)制字符串和 symbol 屬性:

  2. 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)換算法是:

  1. 調(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
    
  2. 否則,如果 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
      
  3. 否則,如果 hint 是 "number"或者"default"

    • 嘗試調(diào)用 obj.valueOf()obj.toString(),無論哪個存在。

所有這些方法都必須返回一個原始值才能工作(如果已定義)。

在實際使用中,通常只實現(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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • JavaScript筆記——快速了解 ES6 新增數(shù)組方法,開箱即用(含案例)

    JavaScript筆記——快速了解 ES6 新增數(shù)組方法,開箱即用(含案例)

    數(shù)組是 JavaScript 以及多數(shù)編程其他編程語言的一種基礎(chǔ)數(shù)據(jù)類型。 ES6 提供了許多新的數(shù)組方法,這篇文章將介紹其中一些常用的數(shù)組方法及其使用示例。 Array.from() 方法從一個類似數(shù)組或可迭代對象中創(chuàng)建一個新的,淺拷貝的數(shù)組實例。例如,將字符串轉(zhuǎn)換為字符數(shù)組。 A

    2024年02月10日
    瀏覽(27)
  • 軟件工程師,學習下JavaScript ES6新特性吧

    軟件工程師,學習下JavaScript ES6新特性吧

    概述 ????????作為一名軟件工程師,不管你是不是前端開發(fā)的崗位,工作中或多或少都會用到一點JavaScript。JavaScript是大家所了解的語言名稱,但是這個語言名稱是Oracle公司注冊的商標。JavaScript的正式名稱是ECMAScript。1996年11月,JavaScript的創(chuàng)造者網(wǎng)景公司將JS提交給國際化

    2024年02月13日
    瀏覽(21)
  • 手把手帶你學習 JavaScript 的 ES6 ~ ESn

    手把手帶你學習 JavaScript 的 ES6 ~ ESn

    JavaScript 是一種廣泛使用的網(wǎng)絡(luò)編程語言,它在前端開發(fā)中扮演著重要角色。隨著時間的推移,JavaScript 的版本不斷更新,新的功能和語法不斷涌現(xiàn),使得這門語言更加豐富和強大。ES6~ESn 是指 JavaScript 的版本6到版本n(例如ES7、ES8等),這些新版本為我們帶來了許多新的特性

    2024年02月05日
    瀏覽(25)
  • ES6 ~ ES11 學習筆記

    ES6 ~ ES11 學習筆記

    ·課程地址 let 不能重復(fù)聲明變量(var 可以) let 具有塊級作用域,內(nèi)層變量外層無法訪問 let 不存在變量提升(運行前收集變量和函數(shù),提前聲明),但是 var 存在變量提升: 不影響作用域鏈: 案例: 如果在 for 循環(huán)中使用了 var 聲明 i,那么它會被提升到全局作用域 window

    2024年02月21日
    瀏覽(63)
  • 【ECMAScript】ES6-ES11學習筆記

    注意事項 代碼中的注釋有筆記如 有一些錯誤示范代碼,為了代碼整體可運行,將其注釋如 當代碼有輸出是,通常將輸出放在對應(yīng)代碼下一行,并注釋如下 1.聲明變量 2.定義常量 3.解構(gòu)賦值 4.模板字符串 5.簡化對象寫法 6.箭頭函數(shù) 箭頭函數(shù)適合與this無關(guān)的回調(diào),定時器,數(shù)

    2024年02月13日
    瀏覽(52)
  • 前端框架學習-ES6新特性(尚硅谷web筆記)

    ECMASript 是由 Ecma 國際通過 ECMA-262 標準化的腳本程序設(shè)計語言。javaScript也是該規(guī)范的一種實現(xiàn)。 筆記出處:b站 尚硅谷Web前端ES6教程,涵蓋ES6-ES11 阮一峰大佬的:ECMAScript 6 入門 ES6 let 使用let聲明變量的特點: 不允許重復(fù)聲 塊兒級別作用域 不存在變量提升 不影

    2024年02月12日
    瀏覽(45)
  • es6 ...展開語法

    ES6中的...(展開)語法是一種可以將數(shù)組或?qū)ο笳归_為函數(shù)參數(shù)或數(shù)組字面量的語法。它通常用于函數(shù)調(diào)用或數(shù)組字面量的展開。 在函數(shù)調(diào)用中,...可以將一個數(shù)組展開為函數(shù)的參數(shù)列表。例如: js 復(fù)制代碼 ? function sum( a, b, c) { ? return a + b + c; ? } ? ? ? const numbers = [ 1,

    2024年02月05日
    瀏覽(18)
  • ES6核心語法

    ES6核心語法

    主要記錄學習ES6的語法 同es5中的var來聲明變量。三者的區(qū)別分別是: var聲明的變量存在變量提升,先聲明未賦值,值為undefined。且變量聲明可在函數(shù)塊內(nèi)使用。變量聲明之后可以重復(fù)聲明 let聲明的變量無變量提升。作用域是塊級(例如if或者for等等)。不能被重復(fù)聲明。聲

    2024年02月10日
    瀏覽(17)
  • ES6基礎(chǔ)語法

    ES6基礎(chǔ)語法

    目錄 解構(gòu) 數(shù)組解構(gòu) 對象解構(gòu) ?基本數(shù)據(jù)解構(gòu) 對象 對象簡寫 箭頭函數(shù) 擴展運算符 函數(shù)參數(shù)解構(gòu) ?對象API拓展 Object.is() Object.assign() Object.getPrototypeOf() Object.setPrototypeOf() Object.keys() Object.values() Object.entries() Object.fromEntries() ?數(shù)組方法拓展 String類型方法 String.prototype.trimStart()/

    2024年02月16日
    瀏覽(17)
  • 學習筆記 JavaScript基礎(chǔ)語法(全)

    學習筆記 JavaScript基礎(chǔ)語法(全)

    1.1 瀏覽器執(zhí)行 JS 簡介 瀏覽器分成兩部分:渲染引擎和 JS 引擎 渲染引擎 :用來解析HTML與CSS,俗稱內(nèi)核,比如 chrome 瀏覽器的 blink ,老版本的 webkit JS 引擎 :也稱為 JS 解釋器。 用來讀取網(wǎng)頁中的JavaScript代碼,對其處理后運行,比如 chrome 瀏覽器的 V8 1.2 JS的組成 1.2.1 ECMAScr

    2024年02月05日
    瀏覽(29)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包