1. 程式人生 > 實用技巧 >【JavaWeb】現代 JavaScript 教程

【JavaWeb】現代 JavaScript 教程

js_model_tutorial

!!待更新

前言

現代 JavaScript 教程的學習筆記,它是一份不錯的學習資源,感謝開源。
中文連結

基礎


函式

程式碼示例

  • 函式的宣告方式

    function name(parameters, delimited, by, comma) {
    /* code */
    }
    
  • 作為引數傳遞給函式的值,會被複制到函式的區域性變數。

  • 函式可以訪問外部變數。但它只能從內到外起作用。函式外部的程式碼看不到函式內的區域性變數。

  • 函式可以返回值。如果沒有返回值,則其返回的結果是 undefined。

  • 建議在函式中主要使用區域性變數和引數,而不是外部變數。

  • 函式命名

    • 函式名應該清楚地描述函式的功能。當我們在程式碼中看到一個函式呼叫時,一個好的函式名能夠讓我們馬上知道這個函式的功能是什麼,會返回什麼。
    • 一個函式是一個行為,所以函式名通常是動詞。
    • 目前有許多優秀的函式名字首,如 create…、show…、get…、check… 等等。使用它們來提示函式的作用。

函式表示式

程式碼示例

  • 函式是值。它們可以在程式碼的任何地方被分配,複製或宣告。
  • 如果函式在主程式碼流中被宣告為單獨的語句,則稱為“函式宣告”。
  • 如果該函式是作為表示式的一部分建立的,則稱其“函式表示式”。
  • 在執行程式碼塊之前,內部演算法會先處理函式宣告。所以函式宣告在其被宣告的程式碼塊內的任何位置都是可見的。
  • 函式表示式在執行流程到達時建立。

箭頭函式

程式碼示例

  • 不帶花括號:(...args) => expression — 右側是一個表示式:函式計算表示式並返回其結果。
  • 帶花括號:(...args) => { body } — 花括號允許我們在函式中編寫多個語句,但是我們需要顯式地 return 來返回一些內容。

物件

程式碼示例

  • 物件是具有一些特殊特性的關聯陣列
  • 儲存屬性(鍵值對)
    • 屬性的鍵必須是字串或者symbol
    • 值可以是任何型別
  • 訪問屬性
    • 點符號:obj.property
    • 方括號:obj["property"]
  • 其他操作
    • 刪除屬性:delete obj.property
    • 檢查是否存在給定鍵的屬性:"key" in obj
    • 遍歷物件:for(let key in obj)迴圈
  • 物件是通過引用被賦值或複製的。即變數儲存的不是”物件的值“,而是值的”引用“(記憶體地址)。
  • JS中其他型別的物件
    • Array用於儲存有序資料集合
    • Date用於儲存時間日期
    • Error用於儲存錯誤資訊

垃圾回收

程式碼示例

  • 垃圾回收是自動完成的,我們不能強制執行或是阻止執行。
  • 當物件是可達狀態時,它一定是存在與記憶體中的。
  • 被引用與可訪問(從一個根)不同:一直相互連線的物件可能整體都不可達。

Symbol 型別

程式碼示例

  • Symbol是唯一識別符號的基本型別
  • Symbol是使用帶有可選描述(name)的Symbol()呼叫建立的
  • Symbol總有不同的值,即使它們有相同的名字。如果希望同名的Symbol相等,那麼應該使用全域性登錄檔:Symbol.for(key)返回一個以key作為名字的全域性Symbol。
    使用Symbol.for多次呼叫key相同的Symbol時,返回的就是同一個Symbol
  • Symbol的兩個主要的使用場景:
    • “隱藏”物件屬性。如果想要向“屬於”另一個指令碼或者庫的物件新增一個屬性,可以建立一個Symbol並
      使用它作為屬性的鍵。Symbol屬性不會出現在for .. in中,因此它不會意外地被與其他屬性一起處理。
      並且,它不會被直接訪問,因為另一個指令碼沒有我們地symbol。因此,該屬性將受到保護,防止被意外
      或重寫
    • JS使用了許多系統地Symbol,這些Symbol可以作為Symbol.*訪問
  • 從技術上來講,Symbol不是100%隱藏的

物件方法,"this"

程式碼示例

  • 儲存在物件屬性中的函式被稱為"方法"
  • 方法允許物件進行像object.doSomething()這樣的操作
  • 方法可以講物件引用為this
  • this的值是在程式執行時得到的
  • 一個函式在宣告時,可能就使用了this,但是這個this只有在函式被呼叫時才會有值
  • 可以在物件之間複製函式
  • 以"方法"的語法呼叫函式時:object.method(),呼叫過程中的this值是object

物件-原始值轉換

程式碼示例

  • 物件到原始值的轉換,是由許多期望以原始值作為值得內建函式和運算子自動呼叫的,包括三種類型:
    • "string" 對於alert和其他需要字串的操作
    • "number" 對於數學運算
    • "default" 少數運算子
  • 規範明確描述了哪些運算子使用哪個hint。很少有運算子"不知道期望什麼"並使用"default"hint。
    通常對於內建物件,"default"hint的處理方式與"number"相同,因此實踐中,最後兩個hint常常結合在一起
    轉換演算法是:
    • 呼叫obj[Symbol.toPrimitive](hint)如果這個方法存在
    • 否則,如果hint是"string"
      • 嘗試obj.toString()和obj.valueOf(),無論那個存在
    • 否則,如果 hint 是 "number" 或者 "default"
      • 嘗試 obj.valueOf() 和 obj.toString(),無論哪個存在。

構造器和操作符"new"

程式碼示例

  • 建構函式,或簡稱構造器,就是常規函式,但有個共同約定,就是其命名首字母要大寫
  • 建構函式只能使用new來呼叫。這樣的呼叫意味著在開始時建立了空的this,並在最後返回填充了值的this
  • 當一個函式被使用new操作符執行時,它按照以下步驟:
    • 一個新的空物件被建立並被分配給this
    • 函式體執行。通常它會修改this,為其新增新的屬性。
    • 返回this的值

數字型別

程式碼示例

  • 要寫很多零的數字
    • 將"e"和0的數量附加到數字後。123e6 <=> 123000000
    • "e"後面的負數將使數字除以1後面接著給定數量的零的數字。123e-6 <=> 0.000123
  • 對於不同的數字系統
    • 可以直接在十六進位制(0x),八進位制(0o)和二進位制(0b)系統中寫入數字
    • parseInt(str, base)將字串str解析為給定的base數字系統中的整數,2 <= base <= 36
    • num.toString(base)將數字轉換為在給定的base數字系統中的字串
  • 要將12pt和100px之類的值轉換為數字
    • 使用parseInt/parseFloat進行"軟"轉換,它從字串中讀取數字,然後返回在發生error前可以讀取到的值
    • 使用Math.floor、Math.ceil、Math.trunc、Math.round或者num.toFixed(precision)進行舍入
    • 請確保記住使用小數時會損失精度

字串

程式碼示例

  • 有3種類型的引號,反引號允許字串跨越多行並可以使用${...}在字串中嵌入表示式
  • JavaScript 中的字串使用的是 UTF-16 編碼
  • 可以使用像 \n 這樣的特殊字元或通過使用 \u... 來操作它們的 unicode 進行字元插入
  • 獲取字元時,使用 []
  • 獲取子字串,使用 slice 或 substring 或 substr
  • 字串的大/小寫轉換,使用:toLowerCase/toUpperCase
  • 查詢子字串時,使用 indexOf 或 includes/startsWith/endsWith 進行簡單檢查
  • 根據語言比較字串時使用 localeCompare,否則將按字元程式碼進行比較
  • 其他幾種有用的字串方法
    • str.trim() — 刪除字串前後的空格 (“trims”)
    • str.repeat(n) — 重複字串 n 次

陣列

程式碼示例

  • 陣列是一種特殊的物件,適用於儲存和管理有序的資料項
  • 宣告:let arr = [item1, item2, item3]; let arr = new Array(item1, item2, item3);
  • 呼叫 new Array(number) 會建立一個給定長度的陣列,但不含有任何項
  • length屬性是陣列的長度,準確地說,它是陣列最後一個數字索引值加一,它由陣列方法自動調整
  • 手動縮短length,那麼陣列就會被截斷,且不可逆
  • 用操作雙端佇列的方式使用陣列
    • push(items)在末端新增items項
    • pop()從末端移除並返回該元素
    • unshift(items)從首端新增items項
    • shift()從首端移除並返回該元素
  • 遍歷陣列的方法
    • for(let i = 0; i < arr.length; i++) 執行最快,可相容舊版本瀏覽器
    • for(let item of arr) 現代語法,只能訪問items
    • for(let i in arr) 永遠不要使用這個

陣列方法

程式碼示例

  • 新增/刪除元素
    • push(items) 向尾端新增元素
    • pop() 從尾端提取一個元素
    • shift() 從首端提取一個元素
    • unshift(items) 向首端新增元素
    • splice(pos, deleteCount, items) 從index開始刪除deleteCount個元素,並在當前位置插入items
    • slice(start, end) 建立一個新陣列,將從位置start到位置end(但不包括end)的元素複製進行
    • concat(items) 返回一個新陣列:複製當前陣列的新元素,並向其中新增items。如果items中的任意一項是一個數組,那麼就取其元素
  • 搜尋元素
    • indexOf/lastIndexOf(item, pos) 從位置pos開始搜尋item,搜尋到則返回該項的索引,否則返回-1
    • includes(value) 如果陣列有value,則返回true,否則返回false
    • find/filter(func) 通過func過濾元素,返回使func返回true的第一個值/所有值
    • findIndex() 和find()類似,但是返回索引而不是值
  • 遍歷元素
    • forEach(func) 對每個元素都呼叫func,不返回任何內容
  • 轉換陣列
    • map(func) 根據對每個元素呼叫func的結果建立一個新陣列
    • sort(func) 對陣列進行原位排序,然後返回它
    • reverse() 對陣列進行原位反轉,然後返回它
    • split/join 將字串轉換為陣列並返回它
    • reduce(func, initial) 通過對每個元素呼叫func計算陣列的單個值,並在呼叫之間傳遞中間結果
  • 其他
    • Array.isArray(arr) 檢查arr是否是一個數組

可迭代物件

程式碼示例

  • 可以應用 for..of 的物件被稱為可迭代的
  • 技術上來說,可迭代物件必須實現 Symbol.iterator 方法
    • obj[Symbol.iterator] 的結果被稱為迭代器(iterator),由它處理進一步的迭代過程
    • 一個迭代器必須由next()方法,它返回一個{done: Boolean, value: any}物件,
      這裡的done: true表明迭代結束,否則value就是下一個值
  • Symbol.iterator方法會被 for..of 自動呼叫,但是也可以直接呼叫
  • 內建的可迭代物件例如字串和陣列,都實現了Symbol.iterator
  • 字串迭代器能夠識別代理對,即UTF-16拓展字元
  • 有索引屬性和length屬性的物件被稱為類陣列物件,這種物件可能還具有其他屬性和方法,但是沒有陣列的內建方法
  • Array.from(obj[, mapFn, thisArg])將可迭代物件或者類陣列物件obj轉化為真正的陣列Array,然後就可以對它應用陣列的方法,可選引數mapFn和thisArg允許將函式應用到每個元素

對映和集合

程式碼示例

  • Map 是一個帶鍵的資料項的集合
    • new Map() 建立map
    • map.set(key, value) 根據鍵儲存值
    • map.get(key) 根據鍵來返回值,如果map中不存在對應的key,則返回undefined
    • map.has(key) 根據key存在則true,否則返回false
    • map.delete(key) 刪除指定鍵的值
    • map.clear() 清空map
    • map.size 返回當前元素個數
  • Map 與普通物件 Object 的不同點
    • 任何鍵、物件都可以作為鍵
    • 有其他的便捷方法,如 size 屬性
  • Set 是一組唯一值的集合
    • new Set(iterable) 建立一個set,如果提供了一個iterable物件(通常是陣列),將會從數組裡面複製值到set中
    • set.add(value) 新增一個值,返回set本身
    • set.delete(value) 刪除值,如果value在這個方法呼叫的時候存在則返回true,否則返回false
    • set.has(value) 如果value在set中,返回true,否則返回false
    • set.clear() 清空set
    • set.size 返回元素個數
  • 在 Map 和 Set 中迭代總是按照值插入的順序進行的,所以不能說這些集合是無序的,但是不能對元素進行重新排序,也不能直接按其編號來獲取元素。

弱對映和弱集合

程式碼示例

  • WeakMap是類似於Map的集合,它僅允許物件作為鍵,並且一旦通過其他方式無法訪問它們,
    便會將它們與其關聯值一同刪除
  • WeakSet是類似於Set的集合,它僅儲存物件,並且一旦通過其他方式無法訪問他們們,便會將其刪除
  • 均不支援引用所有鍵或其計數的方法和屬性,僅允許單個操作
  • WeakMap和WeakSet被用作“主要”物件儲存之外的“輔助”資料結構,一旦將物件從主儲存器中刪除,
    如果該物件僅被用作WeakMap或WeakSet的鍵,那麼它將被自動清除

解構賦值

程式碼示例

  • 解構賦值可以立即將一個物件或陣列對映到多個變數上
  • 解析物件的完整語法:
    • let {prop: varName = default, ...rest} = object;
    • 這表示屬性prop會被賦值給變數varName,如果沒有這個屬性的話,就會使用default,
      沒有對應對映的物件屬性將被複制到rest物件
  • 解析陣列的完整語法:
    • let [item1 = default, item2, ...rest] = array;
    • 陣列的第一個元素被賦值給item1,第二個元素被賦值給item2,剩下的所有元素被複制到另一個數組rest
  • 從巢狀數物件/陣列中提取資料也是可以的,此時等號左側必須和等號右側有相同的結構

時間和日期

程式碼示例

  • 在JS中,日期和時間使用Date物件來表示,但是不能只建立日期,或者只建立時間,Date物件總是同時建立兩者
  • 月份從0開始計數,即一月是0
  • 一週的某一天 getDay() 同樣是從0開始計算,0代表星期日
  • 當設定了超出範圍的元件時,Date會自我進行校準,這一點對於日/月/小時的加減很有用
  • 日期可以相減,得到的是以毫秒錶示的兩者的差值。因為當Date被轉換為數字時,Date物件會被轉換為時間戳
  • 使用 Date.now() 可以更快地獲取當前時間的時間戳
  • 和其他系統不同,JS中的時間戳以毫秒為單位,而不是秒

JSON方法,toJSON

程式碼示例

  • JSON是一種資料格式,具有自己的獨立標準和大多數程式語言的庫
  • JSON支援object、array、string、number、boolean、null
  • JS提供序列化成JSON的方法 JSON.stringify 和解析JSON的方法 JSON.parse
  • 這兩種方法都支援用於只能讀/寫的轉換函式
  • 如果一個物件具有toJSON,那麼它會被JSON.stringify呼叫

遞迴和堆疊

程式碼示例

  • 遞迴式程式設計的一個術語,表示從自身呼叫函式。遞迴函式可用於以更優雅的函式解決問題
  • 遞迴定義的資料結構是指可以使用自身來定義的資料結構
  • 任何遞迴函式都可以被重寫為迭代形式,有時這是在優化程式碼時需要做的,但對於大多數任務來說,遞迴方法足夠快,並且容易編寫和維護

Rest引數與Spread語法

程式碼示例

  • 當在程式碼中看到"..."時,它要麼是rest引數,要麼就是spread語法
  • 簡單的區分:
    • 若 ... 出現在函式引數列表的最後,那麼它就是rest引數,它會把引數列表中剩餘的引數收集到一個數組中
    • 若 ... 出現在函式呼叫或類似的表示式中,那它就是spread語法,它會把一個數組展開為列表
  • 使用場景:
    • Rest引數用於建立可接受任意數量引數的函式
    • Spread語法用於將陣列傳遞給通常需要含有許多引數的列表的函式
  • "舊式的" arguments(類陣列物件) 依然能獲取函式呼叫中的所有引數,但不推薦,推薦使用Rest引數

閉包

程式碼示例

  • 這部分需要再多看看

舊時的"var"

程式碼示例

  • var與let/const 有兩個主要的區別
    • var 宣告的變數沒有塊級作用域,它們的最小作用域就是函式級作用域
    • var 變數宣告在函式開頭就會被處理(指令碼啟動對應全域性變數)

全域性物件

程式碼示例

  • 全域性物件包含應該在任何位置都可見的變數
    • 其中包括JS的內建方法
  • 全域性物件有一個通用名稱globalThis
    • 但是更常見的是使用“老式”的環境特定(environment-specific)的名字,
      例如 window(瀏覽器)和 global(Node.js)。
      由於 globalThis 是最近的提議,因此在 non-Chromium Edge 中不受支援(但可以進行 polyfills)
  • 僅當值對於專案而言確實是全域性時,才應將其儲存在全域性物件中,並保持其數量最少
  • 在瀏覽器中,除非使用modules。否則使用var宣告的全域性函式和變數會變成全域性物件的屬性
  • 為了使程式碼面向未來並更易於理解,應該使用直接的方式訪問全域性物件的屬性,如window.x

函式物件,NFE

程式碼示例

  • 函式就是物件
  • 一些屬性
    • name -- 函式的名字。通常取自函式定義,但如果函式定義時沒設定函式名,JS會嘗試通過函式的上下文猜一個函式名
    • length -- 函式定義時入參的個數,Rest引數不參與計數
  • 如果函式是通過函式表示式的形式被宣告的,並且附帶了名字,那麼它就被稱為命名函式表示式,這個名字可以用於在該函式內部進行指呼叫,例如遞迴呼叫等

"new Function" 語法

程式碼示例

  • 使用 new Function 建立的函式,它的 [[Environment]] 指向全域性詞法環境,而不是函式所在的外部詞法環境
  • 不能在 new Function 中直接使用外部變數
  • 這有助於降低我們程式碼出錯的可能
  • 從程式碼架構上講,顯式地使用引數傳值是一種更好的方法,並且避免了與使用壓縮程式而產生衝突的問題

排程:setTimeout 和 setInterval

程式碼示例

  • setTimeout(func, delay, ...args)和setInterval(func, delay, ...args)方法允許在delay毫秒之後允許func一次或者以delay毫秒為時間間隔週期性執行func
  • 要取消函式的執行,應該呼叫clearInterval/clearTimeout,並將setInterval/setTimeout返回的值作為入參傳入
  • 巢狀的setTimeout比setInterval用起來更加靈活,允許更精確地設定兩次執行之間的時間
  • 零延時排程setTimeout(func, 0)(或者setTimeout(func))用來排程需要儘快執行的呼叫,但是會在當前指令碼執行完成後進行排程
  • 瀏覽器會將setTimeout或setInterval的五層或者更多層巢狀呼叫(呼叫五次之後)的最小延時限制在4ms,這是一個歷史遺留問題
  • 注意,所有的排程方法都不能保證確切的延時,例如,瀏覽器內的計時器可能由於許多原因而變慢
    • CPU過載
    • 瀏覽器頁籤處於後臺模式
    • 膝上型電腦用的電池供電,使用電池供電會以降低效能為代價提升續航

裝飾者模式和轉發,call/apply

程式碼示例

  • 裝飾器是一個圍繞改變函式行為的包裝器,其主要工作任由該函式來完成
  • 裝飾器可以被看作是可以新增到函式的"features"或"aspects",可以新增一個或新增多個,而這一切都無需更改其程式碼
  • 為了實現cachingDecorator,可以運用以下方法
    • func.call(context, arg1, arg2) -- 給定的上下文和引數呼叫func
    • func.apply(context, args) -- 呼叫func將context作為this和類陣列的args傳遞給引數列表
  • 通用的來電轉駁(call forwarding)通常是使用apply完成的
  • 一個方法借用(method borrowing),就是從一個物件中獲取一個方法,並在另一個物件的上下文中"呼叫"它。
    採用陣列方法並將它們應用於引數arguments是很常見的。另一種方法是使用Rest引數物件,該物件是一個真正的陣列。

函式繫結

程式碼示例

  • 方法func.bind(context, ...args)返回函式func的"繫結的變體",它綁定了上下文this和第一個引數
  • 通常應用bind來繫結物件方法的this,這樣就可以把它們傳遞到其他地方使用
  • 當繫結一個現有的函式的某些引數時,繫結後的函式被稱為partial
  • 當不想一遍又一遍重複相同的引數時,partial非常有用

深入理解箭頭函式

程式碼示例

  • 箭頭函式沒有this、arguments,這對裝飾器來說非常有用
  • 箭頭函式也不能使用new進行呼叫
  • 箭頭函式也沒有super
  • 箭頭函式時針對那些沒有自己的"上下文",但在當前上下文中起作用的短程式碼的

屬性標誌和屬性描述符

程式碼示例

  • 物件屬性,除value外,還有三個特殊的特性,也就是所謂的"標誌"
    • writable -- 如果為true,則值可以被修改,否則它只是可讀的
    • enumerable -- 如果為true,則會被在迴圈中列出,否則不會被列出
    • configurable -- 如果為true,則此特性可以被刪除,這些屬性也可以被修改,否則不可以
    • 以上,預設都為true

屬性的getter和setter

程式碼示例

  • 有兩種型別的屬性
    • 資料屬性,目前使用的所有屬性都是資料屬性
    • 訪問器屬性,本質上是用於獲取和設定值的函式,但從外部程式碼來看就像常規屬性
  • 訪問器描述符與資料屬性不同,對於它,沒有value和writable,但是有get 和 set 函式

原型繼承

程式碼示例

  • 在JS中,所有的物件都有一個隱藏的[[Prototype]]屬性,它要麼是另一個物件,要麼就是null
  • 可以使用obj.__proto__訪問它
  • 通過[[Prototype]]引用的物件被稱為"原型"
  • 想要讀取obj的一個屬性或者呼叫一個方法,並且它不存在,那麼JS就會嘗試在原型中查詢它
  • 寫/刪除操作直接在物件上進行,它們不使用原型(假設它是資料型別,不是setter)
  • 如果想呼叫obj.method(),而且method是從原型中獲取的,this仍然會引用obj,因此,方法始終是與當前物件一起使用,即使方法是繼承的
  • for .. in 迴圈在其自身和繼承的屬性上進行迭代,所有其他的鍵/值獲取方法僅對物件本身起作用

F.prototype

程式碼示例

  • F.prototype屬性(不要把它與[[Prototype]]弄混了)在new F被呼叫時為新物件的[[Prototype]]賦值
  • F.prototype的值要麼是一個物件,要麼就是null,其他值都不起作用
  • "prototype"屬性僅在設定了一個建構函式,並通過new呼叫時,才具有這種特殊的影響

原生的型別

程式碼示例

  • 所有的內建物件收遵循相同的模式(pattern)
    • 方法都儲存在prototype中(Array.prototype、Object.prototype、Date.prototype等)
    • 物件只儲存資料本身(陣列元素、物件屬性、日期)
  • 原始資料型別也將方法儲存在包裝器物件的prototype中:Number.prototype、String.prototype、Boolean.prototype。只有undefined和null沒有包裝器物件
  • 內建原型可以被修改或被用新的方法填充,但是不建議更改它們,唯一允許的情況可能是,當新增一個還沒有被JS引擎支援,但已經被加入JS規範的新標準中,才可能允許這樣做

原生方法,沒有__proto__的物件

程式碼示例

  • 設定和直接訪問原型的現代方法:
    • Object.create(proto, [description]) -- 利用給定的proto作為 [[Prototype]] (可以是null)和可選的屬性描述符來建立一個任務
    • Object.getPrototypeOf(obj) -- 返回物件obj的 [[Prototype]] (與__proto__的getter相同)
    • Object.setPrototypeOf(obj. proto) -- 將物件obj的 [[Prototype]] 設定為proto(與__proto__的setter相同)
  • 如果要將一個使用者生成的鍵放入一個物件,那麼內建的__proto__ getter/setter 是不安全的
  • 可以使用Object.create(null)建立一個沒有__proto__的"very plain"物件,或者對此場景堅持使用Map物件即可

Class基本語法

程式碼示例

  • 基本的類語法
class MyClass {
  prop = value; // 屬性

  constructor(...) { // 構造器
    // ...
  }

  method(...) {} // method

  get something(...) {} // getter 方法
  set something(...) {} // setter 方法

  [Symbol.iterator]() {} // 有計算名稱(computed name)的方法(此處為 symbol)
  // ...
}
  • 技術上來說,MyClass 是一個函式(提供作為 constructor 的那個),而 methods、getters 和 settors 都被寫入了 MyClass.prototype。

類繼承

程式碼示例

  • 想要擴充套件一個類:class Child extends Parent:
    • 則意味著Child.prototype.__proto__將是Parent.prototype,所有方法會被繼承
  • 重寫一個constructor
    • 在使用this之前,必須在Child的constructor中將父constructor呼叫super()
  • 重寫一個方法
    • 可以在一個Child方法中使用super.method()來呼叫Parent方法
  • 內部
    • 方法在內部的[[HomeObject]]屬性中記住了它們的類/物件,這就是super如何解析父方法的
    • 將一個帶有super的方法從一個物件複製到另一個物件是不安全的
  • 箭頭函式是沒有自己的this或者super,所以它們能融入到就近的上下文中,像透明似的

靜態屬性和靜態方法

程式碼示例

  • 靜態方法被用於實現整個類的功能,與具體的類示例無關
  • 在類生命中,它們都被用關鍵字static進行了標記
  • 靜態屬性被用於但想要儲存類級別的資料時,而不是繫結到例項
  • 從技術上講,靜態宣告與直接給類本身賦值相同
  • 靜態屬性和方法都是可以被繼承的

私有的和受保護的屬性和方法

程式碼示例

  • OOP而言,內部介面和外部介面的劃分被稱為封裝
  • 封裝的優點
    • 保護使用者,使他們不會誤傷自己
    • 可支援性
    • 嚴格界定內部介面,class的開發人員可以自由地更改其內部屬性和方法,甚至無需通知使用者
    • 隱藏複雜性
    • 當實施細節被隱藏,並提供了簡單且有據可查地外部介面時,總是方便的
  • 為了隱藏內部介面,使用了受保護或私有的屬性
    • 受保護的欄位以_開頭,這是一個約定
    • 私有欄位以#開頭,確保只能從類的內部訪問它們
  • 目前,在各瀏覽器中不支援私有欄位,但可以用 polyfill 解決,即自行解決

擴充套件內建類

程式碼示例

  • 內建的類,例如Array、Map等也都是可以擴充套件的
  • 如果想要map或filter這樣的內建方法返回常規陣列,可以在Symbol.species中返回Array
  • 內建物件有它們自己的靜態方法,例如Object.keys、Array.isArray等,原生的類互相擴充套件,通常,當一個類擴充套件另一個類時,靜態方法和非靜態方法都會被繼承,但內建類是一個例外,它們相互間不繼承靜態方法

類檢查:"instanceof"

程式碼示例

  • typeof用於原始資料型別,返回值為string
  • {}.toString用於原始資料型別和內建物件,包括Symbol.toStringTag屬性的物件,返回string
  • instanceof用於物件,返回true/false
  • 從技術上講,{}.toString 是一種"更高階的"typeof
  • 當使用類的層次結構,並想要對該類進行檢查,同時考慮繼承時,這種場景下instanceof操作確實很出色

Mixin模式

程式碼示例

  • Mixin是一個通用的面向物件程式設計術語:一個包含其他類的方法的類
  • JS不支援多重繼承,但是可以通過將方法拷貝到原型來實現mixin
  • 可以使用mixin作為一種通過新增多種行為來擴充類的方法
  • 如果Mixins意外覆蓋了現有類的方法,那麼它們可能會成為一個衝突點。因此,通常應該仔細考慮mixin的命名方法,以最大程度地降低發生這種衝突的可能性

錯誤處理,"try...catch"

程式碼示例

  • try...catch結構允許處理執行過程中出現的error,從字面上看,它允許"嘗試"允許程式碼並"捕獲"其中可能發生的錯誤
try {
  // 執行此處程式碼
} catch(err) {
  // 如果發生錯誤,跳轉至此處
  // err 是一個 error 物件
} finally {
  // 無論怎樣都會在 try/catch 之後執行
}
  • 可能沒有catch部分或者沒有finally,所以try...catch或者try...finally都是可用的
  • Error物件包括下列屬性:
    • message -- 人類可讀的error資訊
    • name -- 具有error名稱的字串(Error構造器的名稱)
    • stack(沒有標準化,但是得到了很好的支援),即Error發生時的呼叫棧
  • 可以使用throw操作符來生成自定義的error,從技術上講,throw的引數可以是任何東西,但通常是繼承自內建的Error類的error物件
  • 再次丟擲是一中錯誤處理的重要模式:catch塊通常期望並知道如何處理特定的error型別,因此它應該再次丟擲它不知道的error

自定義Error,擴充套件Error

程式碼示例

  • 可以正常地從Error和其他內建的error類進行繼承,但需要注意部分屬性以及不忘了呼叫super
  • 可以用instanceof來檢查特定的error,但是對於一些第三方庫的error,由於沒有簡單的方法獲取它的類,可以將name屬性用於這一類的檢查
  • 包裝異常是一項廣泛應用的技術:用於出入低級別異常並建立高級別error而不是各種低級別error的函式

回撥介紹

程式碼示例

  • 一些函式支援非同步行為,即現在開始執行的行為,但是它們在稍後才會完成
  • 由於指令碼是"非同步"呼叫,所以不會等到指令碼載入完成才執行
  • 非同步執行某項功能的函式應該提供一個callback引數用於在相應事件完成時呼叫
  • 避免"回撥地獄"式的編碼方式

Promise

程式碼示例

  • "生產者程式碼":會做一些事兒,並且會需要一些時間,例如,通過網路載入資料的程式碼
  • "消費者程式碼":想要在"生產者程式碼"完成哦你工作的第一時間就能獲得其工作成果,許多函式可能都需要這個結果
  • Promise是將"生產者程式碼"和"消費者程式碼"連線在一起的特殊的JS的物件
  • 由new Promise構造器返回的promise物件具有以下內部屬性:
    • state -- 最初是"pending",然後再resolve被呼叫時變為"fulfilled",或者在reject被呼叫時變成"rejected"
    • result -- 最初是undefined,然後在resolve(value)被呼叫時變為value,或者在reject(error)被呼叫時變成error
  • Promises和Callbacks對比:
    • Promises允許按照自然順序進行編碼,允許loadScript和.then來處理結果;
    • 而Callbacks在呼叫loadScript(script, callback)時,在處理的地方必須有一個callback函式,即在呼叫loadScript之前,必須知道如何處理結果
    • Promises可以根據需要在promise上多次呼叫.then,每次呼叫,都會在"訂閱列表"中新增一個新的"分析",一個新的訂閱函式,即Promise鏈
    • 而Callbacks只能有一個回撥

Promise鏈

程式碼示例

  • 如果.then(或catch/finally都可以)處理程式(handler)返回一個promise,那麼鏈的其餘部分將會等待,直到它狀態變為settled。當它被settled後,其result(或error)將被進一步傳遞下去

使用promise進行錯誤處理

程式碼示例

  • .catch處理promise中的各種error:在reject()呼叫中的,或者在處理程式(handler)中丟擲的(throw)error
  • 應該將.catch準確地放在想要處理的error上,並知道如何處理這些error的地方。處理程式應該分析error(也可以自定義error類來幫助分析)並再次丟擲未知的error
  • 如果沒有辦法從error中恢復的話,不使用.catch也可以
  • 在任何情況下都應該有unhandled rejection事件處理程式(用於瀏覽器,以及其他環境的模擬),以跟蹤未處理的error並告知使用者/伺服器有關資訊,以使應用程式永遠不會死掉

Promise API

程式碼示例

  • Promise類有5種靜態方法
    • Promise.all(promises) —— 等待所有 promise 都 resolve 時,返回存放它們結果的陣列。如果給定的任意一個 promise 為 reject,那麼它就會變成 Promise.all 的 error,所有其他 promise 的結果都會被忽略
    • Promise.allSettled(promises)(ES2020 新增方法)—— 等待所有 promise 都 settle 時,並以包含以下內容的物件陣列的形式返回它們的結果:
      • state: "fulfilled" 或 "rejected"
      • value(如果 fulfilled)或 reason(如果 rejected)
    • Promise.race(promises) —— 等待第一個 settle 的 promise,並將其 result/error 作為結果
    • Promise.resolve(value) —— 使用給定 value 建立一個 resolved 的 promise
    • Promise.reject(error) —— 使用給定 error 建立一個 rejected 的 promise

Promisify

程式碼示例

  • "Promisification"是用於一個簡單轉換的一個長單詞,它指將一個接受回撥的函式轉換為一個返回promise的函式
  • 由於許多函式和庫都是基於回撥的,因此,在實際開發中經常會需要這種轉換,因為使用promise更加方便
  • 在實際開發中,可能需要promisify很多函式,使用一個helper很有意義,將其稱為promisify(f): 它接受一個需要被promisify的函式f,並返回一個包裝(wrapper)
  • 注意:Promisification是一種很好的方法,特別是在使用async/await的時候,但是不是回撥的完全替代,一個promise可能只有一個結果,但從技術上將,一個回撥將函式可能被呼叫多次,因此Promisification僅適用於呼叫一次回撥的函式,進一步呼叫將被忽略

微任務(Microtask)

程式碼示例

  • Promise處理始終時非同步的,因為所有promise行為都會通過內部的"promise jobs"佇列,即微任務佇列
    • 佇列(queue)是先進先出的:首先進入佇列的任務會首先執行
    • 只有在JS引擎中沒有其他任務在執行時,才開始執行任務佇列中的任務
  • .then/catch/finally 處理程式(handler)總是在當前程式碼完成後才會被呼叫
  • 如果需要確保一段程式碼在 .then/catch/finally 之後被執行,可以將它新增到鏈式呼叫的 .then 中
  • 在大多數 JavaScript 引擎中(包括瀏覽器和 Node.js),微任務(microtask)的概念與“事件迴圈(event loop)”和“巨集任務(macrotasks)”緊密相關

Async/await

程式碼示例

  • 函式前面的關鍵字async有兩個作用
    • 讓這個函式總是返回一個promise
    • 允許在該函式內使用await
  • Promise前的關鍵字await使JS引擎等待該promise settle,然後:
    • 如果有error,就會丟擲異常 -- 就像那裡呼叫了throw error 一樣
    • 否則,就返回結果
  • 關鍵字async和await在一起能很好的編寫非同步程式碼的框架,且程式碼易於閱讀和編寫
  • 有了async/await之後,幾乎就不需要使用promise.then/catch,但是不要忘了它們本質上是promise的,有時候(例如在函式最外層作用域)不得不使用它們
  • 當需要同時等待多個任務時,別忘了Promise.all

Generator

程式碼示例

  • Generator是通過generator函式function* f(...) {...}建立的
  • 在generator(僅在)內部,存在yield操作
  • 外部程式碼和generator可能會通過next/yield呼叫交換結果
  • 在現代JS中,generator很少被使用,但有時卻非常有用,因為函式在執行過程中與呼叫程式碼交換資料的能力是非常獨特的,而且它們非常適合建立可迭代物件

Async iterator 和 generator

程式碼示例

  • Iterator 和 Async Iterator 的 區別
    • 提供iterator的物件方法
      • Iterator -- Symbol.iterator
      • Async Iterator -- Symbol.asyncIterator
    • next() 返回的值
      • Iterator -- 任意值
      • Async Iterator -- Promise
    • 進行迴圈使用時
      • Iterator -- for .. of
      • Async Iterator -- for await .. of
  • Iterable 和 Async Iterable 的 區別
    • 提供iterator的物件方法
      • Iterable -- Symbol.iterator
      • Async Iterable -- Symbol.asyncIterator
    • next() 返回的值
      • Iterable -- {value: ..., done: true/false}
      • Async Iterable -- resolve成{value: ..., done: true/false}的Promise
  • Generator 和 Async Generator 的 區別
    • 宣告方式
      • Generator -- function*
      • Async Generator -- async function*
    • next() 返回的值
      • Generator -- {value: ..., done: true/false}
      • Async Generator -- resolve成{value: ..., done: true/false}的Promise
        在現代JS中,generator很少被使用,但有時卻非常有用,因為函式在執行過程中與呼叫程式碼交換資料的能力是非常獨特的,而且它們非常適合建立可迭代物件

模板簡介

程式碼示例

  • 一個模組就是一個檔案,瀏覽器需要使用