本篇來講一下ES裡的資料型別。
資料型別
ES裡的資料型別分兩種。
一種是語言型別,就是我們平常寫的12,'xccurate',undefined
等等型別。
一種是規範型別,是用來描述ECMAScript語言結構和ECMAScript語言型別的值,例如引用Reference
,列表List
等等。
先來看看基本的語言型別吧。
語言型別
ES的語言型別有七種:
Undefined
型別:只有一個值:undefined
。Null
型別:只有一個值:null
。Boolean
型別:有兩個值:true
和false
。String
型別:是零個或多個16位無符號整數值的所有有序序列的集合,最大長度為2^53 -1個元素。它的嚴格定義就不深究了,想了解的看?。Symbol
型別:每個值都是獨一無二的,例如Symbol('s1mple')
。規範中定義了一些內建的Symbol值?。Number
型別:一共18437736874454810627個值,包括NaN
、+Infinity
、-Infinity
。詳細定義看?。Object
型別:例如:let a = {}
。這就得好好說說了,見下方。
Object型別
物件是屬性property
的集合。
屬性使用鍵值對來標識,屬性的鍵的值可以為String
型別(可以為空''
)或Symbol
型別。
屬性分為兩種:
- 資料屬性
data property
:將鍵值與一個ES語言值和一組布林屬性相關聯。 - 訪問器屬性
accessor property
:將鍵值與一個或兩個訪問器函式和一組布林屬性相關聯。訪問器函式用於儲存或檢索與屬性關聯的ECMAScript語言值。
所有物件都是屬性的邏輯集合。但是有很多種物件在訪問和操作其屬性的語義上有所不同。
普通物件(Ordinary objects)是最常見的物件形式,具有預設物件語義。
怪異物件(exotic object)是其屬性語義與預設語義不同的任何形式的物件。
屬性的特性
特性attribute
用來定義和解釋物件屬性的狀態。
其中資料屬性的特性有:[[Value]]
、[[Writable]]
、[[Enumerable]]
、[[Configurable]]
。
訪問器屬性的特性有:[[Get]]
、[[Set]]
、[[Enumerable]]
、[[Configurable]]
。
它們的意義也不多說了,隨便一查都有,不是重點。
物件的內部方法與內部插槽
內部方法 Object Internal
:其演算法制定了物件的實際語義。不是ES語言的一部分,由規範定義,僅用於說明。內部方法是多型的,不同的物件可能會執行不同的演算法。
內部插槽 Internal Slots
:是給演算法使用的內部狀態。不是物件屬性,不繼承。其值可能為規範型別或者語言型別。(為了方便理解,其實可以把它看作內部屬性,不對開發者可見。而且內部插槽這個名字實在太彆扭了?,下文就叫做內部屬性了。)
這兩個都是規範定義的用來描述ES物件行為的東西,可以簡單的理解為物件的函式和屬性。
**內部方法和內部屬性均用雙方括號標識:[[xxxxx]]**
這個表格?總結了物件的必要的內部方法,每個物件都必須有其演算法,然而卻不一定相同。
簡略說一下有什麼:
[[GetPrototypeOf]]
、[[SetPrototypeOf]]
:獲取和設定物件的原型prototype
。
[[IsExtensible]]
:決定是否可以向此物件新增其他屬性。
[[PreventExtensions]]
:決定是否可以將新屬性新增到此物件。// 這個跟上面那個有什麼區別還不太清楚
[[GetOwnProperty]]
、[[DefineOwnProperty]]
:獲取和設定物件(名稱為引數的)屬性的屬性描述符。
[[HasProperty]]
:判斷物件是否有自己的或繼承的(名稱為引數的)屬性。
[[Get]]
、[[Set]]
:獲取和設定(名稱為引數的)屬性的值。
[[Delete]]
:刪除物件(名稱為引數的)屬性。
[[OwnPropertyKeys]]
:返回物件自身的屬性的列表。
上篇說過,函式是可呼叫物件。所以其具有額外的內部方法:[[Call]]
。而建構函式具有額外的內部方法:[[Construct]]
。
也就是說函式執行時最終呼叫的就是函式物件的[[Call]]
方法,建構函式構造物件時,執行的是建構函式的[[Construct]]
方法。
它們的的實際語義到時候再仔細分析。
接下來一小節?講的是這些基本內部方法不變的行為。例如返回型別的規定,等等。這些規則是所有物件必須遵守的,儘管其演算法有可能不同。這些邊邊角角不用開發者操心,就不詳述了。
內在物件
內在物件 Intrinsic Objects
是規範中的演算法要引用到的內建物件。像Array
、String
、Object
及其原型等等。
詳細見表?,就先不列舉了,等到用到的時候再說。
多說一句,這組內建物件是每個領域Realm
中有一組。什麼是領域呢,例如一個頁面中的iframe
中的Array
物件與該頁面中的Array
物件就不一樣,它們就是兩個領域的。
規範型別
規範型別是在規範中使用的,用來描述ES語義的、無法在規範外使用型別。
List和Record型別
List型別用來解釋函式引數列表的執行。List型別的值是包含單個值的列表元素的簡單有序序列。
其實可以簡單理解為ES的陣列,但是寫規範的時候還不存在ES的陣列型別,只好用List來描述像陣列這樣的型別。例如,«1,2»定義了一個List值,它具有兩個元素,每個元素被初始化為一個特定的值。 一個新的空列表可以表示為«»。
(在上篇提到的ES直譯器engine262中List實現就是用陣列來實現的。)
Record型別用於描述規範演算法中的資料聚合。Record型別值由一個或多個命名欄位組成。 每個欄位的值都是ECMAScript值或由與Record型別相關聯的名稱表示的抽象值。 欄位名稱始終用雙括號括起來,例如[[value]]
。
這個就像是ES裡的物件了,使用也很像:R.[[Field2]]
是名為[[Field2]]
的R的欄位的縮寫。
(在engine262裡Record就是用物件來實現的。)
Set 和 Relation型別
Set型別用於解釋記憶體模型中使用的無序元素集合,其中沒有元素出現超過一次。元素可以新增到集合中,也可以從集合中移除。集合可以合併、交叉或從彼此中減去。
Relation型別用於解釋Set上的約束。
// 這兩個我還沒遇到過,不太懂就不說了?。
Completion Record 型別
Completion Record型別用來解釋值和控制流的執行時傳播。
ECMAScript規範中的每個執行時語義都顯式或隱式返回一個報告其結果的完成Completion Record。
(這個定義讀起來就很拗口,不過不是很重要,簡單理解一下就行。)
Completion Record是一個Record,所以就可以用物件來描述它,它有三個欄位:
Completion Record = {
[[type]] // Completion的型別,有 normal, break, continue, return, throw 5種型別
[[value]] // 返回的值為ES語言值或空,僅噹噹[[type]] 為 normal,return, throw時有值
[[target]] // 定向控制轉移的目標label,為string或空,僅當[[type]] 為break, continue時有值
}
複製程式碼
[[type]]
為normal
時返回的Completion Record稱作normal completion。其餘的都稱為abrupt completion。
在規範的各種抽象操作(也叫演算法)中會有這樣的返回 Return Infinity
,這就是隱式返回了一個Completion Record,相當於 Return NormalCompletion("Infinity")
。
規範在演算法中經常有 ? Call(exoticToPrim, input, « hint »)
這樣以 ?
開頭的語句,它的意思如下:
讓res
為執行Call(exoticToPrim, input, « hint »)
抽象操作的結果。
上面說過,每個執行時語義都顯示或隱式的返回一個Completion Record,所以
如果res
為abrupt completion
,返回res
。
如果res
為Completion Record
, res = res.[[value]]
。
你還會看到 ! ToString(key)
這樣字首為 !
的語句,它的意思如下:
讓res為ToString(key)
斷言res不是abrupt completion
如果res
為Completion Record
, res.[[value]] = res
。
這兩個不一樣哦,注意看最後的賦值語句。
在閱讀規範時,可暫時忽略其實際意義。
Reference型別
Reference型別用來解釋諸如delete
,typeof
,賦值運算子,super
關鍵字和其他語言特徵等運算子的行為。
(在我看來,就是LHS,為了解決誰在哪裡這樣的問題)
一個Reference是解析的名稱或屬性繫結。Reference由三個元件組成:
{
BaseValue // 值為:undefined,Object,Boolean,String,Symbol,Number, Environment Record.
ReferencedName // String或Symbol值。
StrictReference // 布林值,標識是否為嚴格模式
}
複製程式碼
base
值為undefined
時表示無法解析該引用。
還有Super Reference, 用於表示使用super關鍵字表達的名稱繫結。有個額外的 thisValue
元件,其base
值永遠不會為Environment Record
。
舉個栗子:
let a = {
b: 'test'
}
複製程式碼
其中,查詢b時就會得到一個Reference型別,其值為:
{
BaseValue: a,
ReferencedName: b,
StrictReference: false
}
複製程式碼
規範還規定了對於它的一些抽象操作,例如GetBase
、GetReferencedName
、IsStrictReference
等等。
簡單說下吧:
-
GetBase ( V )
:V是一個Reference。獲取V的base
元件。 -
GetReferencedName ( V )
:V是一個Reference。獲取V的name
元件。 -
IsStrictReference ( V )
:V是一個Reference。獲取V的strict
元件。 -
HasPrimitiveBase ( V )
:V是一個Reference。用來判斷V的base元件的值是否是這幾個原始值Boolean,String,Symbol,Number
。 -
IsPropertyReference ( V )
:V是一個Reference。用來判斷V是否是屬性Reference,即其base值為Object或者HasPrimitiveBase ( V )
為true。 -
IsUnresolvableReference ( V )
:V是一個Reference。用來判斷是否是無法解析的Reference,即base
值為undefined
。 -
IsSuperReference ( V )
:V是一個Reference。就是判斷V是不是Super Reference,即有沒有thisValue
元件。 -
GetValue ( V )
:如果V不是Reference,就直接返回V
是Reference,
是無法解析的Reference,就丟擲ReferenceError 錯誤。(就是平常遇到的變數未定義錯誤啦
Uncaught ReferenceError: a is not defined
) 是屬性Reference,
如果是base原始值
Boolean,String,Symbol,Number
, 就讓base = ToObject(base)。⭕1(以後有紅圈的地方就是有批註的地方,圈起來,重點?)
返回
base.[[Get]](GetReferencedName(V), GetThisValue(V))
。 那麼Reference一定為
Environment Record
, 返回
base.GetBindingValue(GetReferencedName(V), IsStrictReference(V))
。⭕1:這個地方就是為什麼
string、number
不是物件卻能夠使用String、Number
物件上的方法的原因了。比如使用
'SOMEBODY'.toLocaleLowerCase()
時要先進行LHS查詢找到'SOMEBODY'.toLocaleLowerCase
是什麼,才能執行函式。此時的Reference就是:{ BaseValue: 'SOMEBODY', ReferencedName: toLocaleLowerCase, StrictReference: false } 複製程式碼
判斷
'SOMEBODY'
是原始值,就進行ToObject
,這個操作會將其變成一個String物件,再獲取toLocaleLowerCase
方法。當Reference為
Environment Record
時,由於Environment Record
還沒講,不說太多,大概就是獲取Environment Record繫結值。 -
PutValue ( V, W )
:如果V不是Reference,丟擲ReferenceError 。
如果無法解析Reference,
如果是嚴格模式,丟擲ReferenceError 。
否則,⭕2
獲取全域性物件
GlobalObject
, 執行
Set(globalObj, GetReferencedName(V), W, false)
操作,返回其結果。如果是屬性Reference,
如果是
base
原始值Boolean,String,Symbol,Number
,就ToObjet
。 執行
base.[[Set]](GetReferencedName(V), W, GetThisValue(V))
操作, 如果返回值為
false
,且為嚴格模式,丟擲ReferenceError 。 那麼一定是Environment Record,
執行
base.SetMutableBinding(GetReferencedName(V), W, IsStrictReference(V))
。返回其結果。⭕2:這個地方就是平常我們沒宣告變數直接進行賦值操作
a = 1
時的演算法,會在全域性物件上新建一個屬性,並賦給其值。當Reference為 Environment Record時,大概進行的操作就是在型別為Environment Record 的
base
上建立一個可變繫結,並賦值。 -
GetThisValue ( V )
:當為SuperReference時返回thisValue
元件的值。 -
InitializeReferencedBinding ( V, W )
:此時base
為Environment Record,執行InitializeBinding(GetReferencedName(V), W)
操作,就是初始化繫結。
總算講完了,Reference型別算是比較重要的一種規範型別了,理解ES執行時語義繞不開它,所以講了很多,不像Completion Record型別,沒講太多,一方面對於理解語義無關緊要,一方面是我也不太懂?。
Property Descriptor型別
屬性描述符型別,用來解釋物件屬性的特性的操作,其值為Record型別,分為資料屬性描述符和訪問器屬性描述符。
它的抽象操作我就不多說了,自己瞭解下就行了?。
Lexical Environment 型別 Environment Record型別
這兩個是大部頭,按下不表,下面有一節專門介紹,主要是用來描述ES裡作用域,識別符號繫結等等。
額外說一句,我看到現在很多教程裡都還在用活動物件(Activation Object)來解釋作用域、閉包時,還納悶怎麼沒在規範裡看到,後來一搜才發現,AO、VO是ES3規範裡的東西,打ES5後就再也沒這倆詞了。。
Data Blocks
用來描述位元組大小(8位)數值的不同且可變的序列。暫時我也不懂,就先不說了?。
這篇就先結束到這吧,本來打算把下章抽象操作也講完的,一寫下來發現有點長,就下篇講吧。下篇主要是型別測試、型別轉換,還有物件上的基本操作。
最後如果對大家有用的話,求關注、star,github 倉庫?。??