前言
今天再学习ts的枚举类型的时候,对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这个属性。