从底层来理解Javascript变量的值与引用
date
Apr 12, 2020
slug
从底层来理解Javascript变量的值与引用
status
Published
tags
summary
type
Post
入题
我们学习Javascript,首先学习的就是变量的数据类型,ES6版Javascript内置了7种数据类型:
- 布尔(Boolean)
- 字符串(String)
- 数字(Number)
- null
- undefined
- 对象(Object)
- 符号(Symbol)
最新规范中,还增加了整数(BigInt)类型,这里暂且不论。
这里有几个问题:
- a的值变了吗?为什么?
let a = 1
let b = a
b = 2
- a的值变了吗?为什么?
let a = 1
let b = a
function foo(c, d) {
c = c + d
}
foo(a, b)
- a的值变了吗?为什么?
let a = {
value: 1
}
let b = a
b.value = 2
- a的值变了吗?为什么?
let a = {
value: 1
}
let b = a
function foo(c, d) {
c = c + d
}
foo(a.value, b.value)
答案可以自己跑一下,为什么可以在后文中寻找。
变量是怎么存储和修改的?
为了更好的理解,我们从最底层的汇编语言说起。
汇编语言
为什么需要汇编语言,大家都知道机器语言是二进制的,为了容易读写,汇编语言在二进制的基础上加上了指令名称,比如加法指令
00000011
,汇编里面就是add
,这样人类才好阅读和编写机器能读懂的指令程序。执行时转换成对应的二进制码即可。再到后来,高级语言发展越来越快,它们的语法更接近人类的思维方式,更受程序员欢迎。它们怎么执行呢?运行之前,它们的编译器会把高级语言代码编译成机器能读懂的二进制编码。
寄存器和内存
二进制编码怎么运行呢,我们需要再了解两个概念:寄存器和内存。
寄存器
,cpu只能运算,寄存器是辅助执行运算的存储器,存储内容诸如:参与运算的数据的内存地址、当前所需运行的指令的地址
内存
,内存分为两种:cpu缓存和内存。- cpu缓存,比如i5,i7,它们都有三级缓存,通常是几兆,它们的访问速度比内存条快很多,用来缓存经常使用的数据,加快访问速度。
- 内存,就是我们电脑的内存条提供的存储。大部分运行中的程序的数据,都会存储在这里。
简单描述一下运算的过程:根据寄存器的地址,到内存读取到运算指令,运算数据,然后在运算单元执行运算。
内存布局
从上面可以了解到,程序运行数据是存放在内存中,下面我们来看看数据在内存中是怎么布局的。
内存是按地址访问的,比如,我们有512Kb的内存,那么内存地址就是从
0x0000
到0x8000
。内存布局会涉及两个概念:
栈和堆
。栈
,从内存高位往地位分配,从0x8000
开始,假如我们要分配一个4字节的变量,那么变量就是从0x8000
到0x7eec
。栈是用来存储函数运行时数据,如局部变量。学过数据结构就知道,栈是后进先出的,这个就是要满足函数调用的顺序,函数是一层层往下调用,然后一层层往上释放。
堆
,从内存低位往高位分配,从0x1000
开始(0x1000
以下由系统使用),堆用来存储动态变量,需要使用者自己申请和释放(大多高级语言有垃圾回收机制,所以无需管理)。
以上这些就是汇编语言的基本存储和运行逻辑,更多参看参考文档1。
Javascript变量存储和运算
铺垫了这么多,最后我们回来看看Javascript的变量,到底是怎么存储和修改的。
原始类型和高级类型
从存储和访问角度,Javascript的原生数据类型还要分为原始类型和高级类型。
说简单一点,原始类型就是可以按值访问的数据类型;高级类型就是按引用访问的数据类型。存储方式也不一样,基本类型的变量是存放在栈区的(栈区指内存里的栈内存);引用类型的值是同时保存在栈内存和堆内存中的对象。
这里的栈和堆和汇编语言还有区别,假如我们用的是v8引擎,Javascript的内存分配底层是由v8管理的。具体参看参考文档2。
变量的访问
编程语言中的变量名,编译运行时关联的就是变量的地址。
- 原始类型,这个地址就是内存中存放变量值的地址,直接访问就可以取到变量值;
- 高级类型,这个地址存放的是引用(理解为指针也可以),也就是这个内存地址存放的是另外一个地址,用这个引用地址才能访问到真正的变量值,这里的变量值存放的是对象结构数据,属性和方法可能还要继续通过引用去访问。
变量的修改
变量的修改也是分原始类型和高级类型。
- 原始类型,赋值就是修改变量地址存放的数据,也就是直接被修改了。
那么问题来了,函数的原始类型参数会被修改吗?字符串呢?
- 高级类型,直接赋值修改的其实是引用本身,并非对象的值(引用其实你也可以理解为是一个数据变量)。你想修改对象的属性,必须通过引用访问到对象结构,然后再访问到属性值,再做修改。
函数对象类型参数,可以被修改吗?
以上就是全部内容,最后的题目请仔细思考一下,欢迎与我讨论。