從規範看ECMAScript(二):資料型別

LarenDorr發表於2019-03-04

本篇來講一下ES裡的資料型別。

資料型別

ES裡的資料型別分兩種。

一種是語言型別,就是我們平常寫的12,'xccurate',undefined等等型別。

一種是規範型別,是用來描述ECMAScript語言結構和ECMAScript語言型別的值,例如引用Reference,列表List等等。

先來看看基本的語言型別吧。

語言型別

ES的語言型別有七種:

  • Undefined型別:只有一個值:undefined
  • Null型別:只有一個值:null
  • Boolean型別:有兩個值:truefalse
  • 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是規範中的演算法要引用到的內建物件。像ArrayStringObject及其原型等等。

詳細見表?,就先不列舉了,等到用到的時候再說。

多說一句,這組內建物件是每個領域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,所以

如果resabrupt completion,返回res

如果resCompletion Recordres = res.[[value]]

你還會看到 ! ToString(key)這樣字首為 !的語句,它的意思如下:

讓res為ToString(key)

斷言res不是abrupt completion

如果resCompletion Recordres.[[value]] = res

這兩個不一樣哦,注意看最後的賦值語句。

在閱讀規範時,可暫時忽略其實際意義。

Reference型別

Reference型別用來解釋諸如deletetypeof,賦值運算子,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
}
複製程式碼

規範還規定了對於它的一些抽象操作,例如GetBaseGetReferencedNameIsStrictReference等等。

簡單說下吧:

  • 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 倉庫?。??

相關文章