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这个属性。
相关文章
相关标签/搜索