JS 中賦值語句的祕密

前言

今天再學習ts的枚舉類型的時候,對ts解釋成js後的代碼有疑問。帶着疑問,一步步追根溯源,最終有了這篇文章。segmentfault

問題起源

ts的枚舉類型解析成js

這是一段簡單的ts代碼解析成js代碼的例子。數組

image.png
左邊是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"ui

賦值操做3:Direction["Down"] = 2spa

賦值操做4:Direction[2] = "Down"3d

賦值操做5:Direction["Left"] = 3code

賦值操做6:Direction[3] = "Left"cdn

賦值操做7:Direction["Right"] = 4對象

賦值操做8:Direction[4] = "Right"

爲何會有Direction[1] = "Up"這類賦值呢?

經查閱資料發現,原來每一個賦值語句都有返回值的(叫返回值可能不太準確,先這麼叫着吧...😄)

具體是這樣的:

image.png

能夠看到,聲明變量的時候,返回值是undefined, aaa=2的時候,返回值是2.

因此賦值的時候,是有返回值。而且這個返回值是賦值號=右邊的值。以下圖:

image.png

因此,上面的ts解析出來的js代碼就能夠讀懂了。

Direction[Direction["Up"] = 1] = "Up"
// 至關於
Direction["Up"] = 1
Direction[1] = "Up"
複製代碼

連續賦值問題

有了上面的認識後,引伸出了一個新的問題。 連續賦值的時候,第二個賦值的值來源是什麼? 例如:

b = a = 10
複製代碼

之前的認知(多是學過C和C++的緣由,留下了印象。),賦值語句是從右往左執行的。 上面的語句是默認是 10賦值給a,a賦值給b。很簡單,咱們知道a的值是10b的值也是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.myNameundefined,是由於此時變量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這個屬性。
相關文章
相關標籤/搜索