JS 中赋值语句的秘密

前言

今天再学习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的值是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这个属性。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值