前言
今天再學習ts的列舉型別的時候,對ts解釋成js後的程式碼有疑問。帶著疑問,一步步追根溯源,最終有了這篇文章。
問題起源
ts的列舉型別解析成js
這是一段簡單的ts程式碼解析成js程式碼的例子。
左邊是ts程式碼,簡單的宣告一個列舉型別。 右邊是解析出來的js程式碼。 Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Left"] = 3] = "Left";
Direction[Direction["Right"] = 4] = "Right";
複製程式碼
這幾句程式碼,引起了我的注意。 因為,這四句程式碼中,有8個賦值操作。
賦值操作1:Direction["Up"] = 1
賦值操作2:Direction[1] = "Up"
賦值操作3:Direction["Down"] = 2
賦值操作4:Direction[2] = "Down"
賦值操作5:Direction["Left"] = 3
賦值操作6:Direction[3] = "Left"
賦值操作7:Direction["Right"] = 4
賦值操作8:Direction[4] = "Right"
為什麼會有Direction[1] = "Up"
這類賦值呢?
經查閱資料發現,原來每個賦值語句都有返回值的(叫返回值可能不太準確,先這麼叫著吧...?)
具體是這樣的:
可以看到,宣告變數的時候,返回值是undefined
,
aaa=2
的時候,返回值是2
.
所以賦值的時候,是有返回值。並且這個返回值是賦值號=
右邊的值。如下圖:
所以,上面的ts解析出來的js程式碼就可以讀懂了。
Direction[Direction["Up"] = 1] = "Up"
// 相當於
Direction["Up"] = 1
Direction[1] = "Up"
複製程式碼
連續賦值問題
有了上面的認識後,引申出了一個新的問題。 連續賦值的時候,第二個賦值的值來源是什麼? 例如:
b = a = 10
複製程式碼
以前的認知(可能是學過C和C++的原因,留下了印象。),賦值語句是從右往左執行的。
上面的語句是預設是 10賦值給a,a賦值給b。很簡單,我們知道a
的值是10
,b
的值也是10
。
但是,因為a = 10
有返回值(10)
,所以b
的值是從a
來的還是從這個(10)
來的呢?
我們來看一個例子:
var a = { name: 'HoTao' }
a.myName = a = { name: '你好' }
console.log(a) // { name: '你好' }
console.log(a.myName) // undefined
複製程式碼
按我們正常的理解,連續賦值語句,從右往左賦值,那麼應該是 a = { name: '你好' }
, a.myName = a。所以,輸出的結果應該都是{ name: '你好' }
,但是,事與願違,並不是這個結果。
這是怎麼回事啊?
於是做了以下測試:
前置知識:JS中,物件和陣列屬於引用型別,在將它們賦值給別人時,傳的是記憶體地址
var a = { name: 'HoTao' }
var b = a
b.name = '你好'
console.log(a.name) // 你好
console.log(b.name) // 你好
複製程式碼
上述程式碼解析:
- 宣告瞭一個變數a,並且指向了一值為
{ name: 'HoTao' }
的記憶體地址(a1
) - 宣告瞭一個變數b,並且指向了變數a的地址,即(
a1
) b.name = '你好'
這句程式碼,修改了記憶體地址a1
所指向的物件的name
的值。所以a和b同時受到了影響。
再看一個例子:
var a = { name: 'HoTao' }
var b = a
b = { name: '你好' }
console.log(a.name) // HoTao
console.log(b.name) // 你好
複製程式碼
當執行b= { name: '你好' }
給b賦了一個新的記憶體地址(a2
),所以,變數a和變數b已經指向不同的地址,他們兩個現在毫無瓜葛了。
我們再反過來看一下連續賦值的問題:
var a = { name: 'HoTao' }
var b = a
a.myName = a = { name: '你好' }
console.log(a) // { name: '你好'}
console.log(a.myName) // undefined
console.log(b) // { name: 'Hotao', myName: { name: '你好' } }
console.log(b.myName) // { name: '你好' }
複製程式碼
程式碼解析:
- 宣告瞭兩個變數a、b。都指向值為
{ name: 'HoTao' }
的記憶體地址(叫a1
) - 執行連續賦值語句,從右往左執行:
- 先執行
a = { name: '你好' }
。這個時候變數a指向的記憶體地址變成了值為{ name: '你好' }
的記憶體地址(叫a2
),並且這句賦值語句有返回值,返回值為{ name: '你好' }
- 接著執行
a.myName = { name: '你好' }
,這個時候a.myName
中的a還是指向a1
。(因為js是解釋執行的語言,在直譯器對程式碼進行解釋的階段,就已經確定了a.myName
的指向) - 所以再執行執行
a.myName = { name: '你好' }
之前,就已經對a.myName
再記憶體地址a1所指向的物件中建立了一個屬性名為myName
的屬性,預設值就是我們常見的undefined
。所以,執行a.myName = { name: '你好' }
的結果,就是往記憶體地址為a1
所指向的物件中的myName
屬性賦值。 - 輸出結果中
a.myName
為undefined
,是因為此時變數a
指向的地址是(a2)
,a2
中沒有myName
這個屬性 - 輸出結果中
b.myName
為{ name: '你好' }
,是因為b指向的記憶體地址是(a1),而,a1存在myName
這個屬性,並且還成功賦值了。所以,正常輸出
- 先執行
總結一下(雖然有點繞~)
- JS是先解釋,再執行。所有的變數宣告,再解釋階段的時候,就已經宣告瞭這篇文章解釋得很好。
- 當例子中
a.myName = a = { name: '你好' }
時,由於連續賦值語句是從右自左,先執行a = { name: '你好' }
,執行後 a 在記憶體中的地址已經改變了 - 執行下一句
a.myName = { name: '你好' }
時,由於解析時已經確定了a.myName
所指向的地址為變數a原來的記憶體地址(a1
) ,所以a.myName = { name: '你好' }
是給變數a
原來的記憶體地址(a1
)指向的變數賦了值。 - 最後輸出
console.log(a.myName)
,由於 a 現在是指向新地址(a2
),而我們只給變數a的舊地址(a1
)的a.myName
賦了值,新地址a2
中沒有a.myName
這個屬性。