前言
今天再学习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
这个属性。