【JavaWeb】現代 JavaScript 教程
阿新 • • 發佈:2020-12-14
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並
- 從技術上來講,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)
- 但是更常見的是使用“老式”的環境特定(environment-specific)的名字,
- 僅當值對於專案而言確實是全域性時,才應將其儲存在全域性物件中,並保持其數量最少
- 在瀏覽器中,除非使用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
- 提供iterator的物件方法
- 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
- 提供iterator的物件方法
- 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很少被使用,但有時卻非常有用,因為函式在執行過程中與呼叫程式碼交換資料的能力是非常獨特的,而且它們非常適合建立可迭代物件
- 宣告方式
模板簡介
- 一個模組就是一個檔案,瀏覽器需要使用