从底层来理解Javascript变量的值与引用

date
Apr 12, 2020
slug
从底层来理解Javascript变量的值与引用
status
Published
tags
summary
type
Post

入题

我们学习Javascript,首先学习的就是变量的数据类型,ES6版Javascript内置了7种数据类型:
  1. 布尔(Boolean)
  1. 字符串(String)
  1. 数字(Number)
  1. null
  1. undefined
  1. 对象(Object)
  1. 符号(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的内存,那么内存地址就是从0x00000x8000
内存布局会涉及两个概念:栈和堆
  • ,从内存高位往地位分配,从0x8000开始,假如我们要分配一个4字节的变量,那么变量就是从0x80000x7eec。栈是用来存储函数运行时数据,如局部变量。学过数据结构就知道,栈是后进先出的,这个就是要满足函数调用的顺序,函数是一层层往下调用,然后一层层往上释放。
  • ,从内存低位往高位分配,从0x1000开始(0x1000以下由系统使用),堆用来存储动态变量,需要使用者自己申请和释放(大多高级语言有垃圾回收机制,所以无需管理)。
以上这些就是汇编语言的基本存储和运行逻辑,更多参看参考文档1。

Javascript变量存储和运算

铺垫了这么多,最后我们回来看看Javascript的变量,到底是怎么存储和修改的。

原始类型和高级类型

从存储和访问角度,Javascript的原生数据类型还要分为原始类型和高级类型。
说简单一点,原始类型就是可以按值访问的数据类型;高级类型就是按引用访问的数据类型。存储方式也不一样,基本类型的变量是存放在栈区的(栈区指内存里的栈内存);引用类型的值是同时保存在栈内存和堆内存中的对象。
这里的栈和堆和汇编语言还有区别,假如我们用的是v8引擎,Javascript的内存分配底层是由v8管理的。具体参看参考文档2。

变量的访问

编程语言中的变量名,编译运行时关联的就是变量的地址。
  • 原始类型,这个地址就是内存中存放变量值的地址,直接访问就可以取到变量值;
  • 高级类型,这个地址存放的是引用(理解为指针也可以),也就是这个内存地址存放的是另外一个地址,用这个引用地址才能访问到真正的变量值,这里的变量值存放的是对象结构数据,属性和方法可能还要继续通过引用去访问。

变量的修改

变量的修改也是分原始类型和高级类型。
  • 原始类型,赋值就是修改变量地址存放的数据,也就是直接被修改了。
    • 那么问题来了,函数的原始类型参数会被修改吗?字符串呢?
  • 高级类型,直接赋值修改的其实是引用本身,并非对象的值(引用其实你也可以理解为是一个数据变量)。你想修改对象的属性,必须通过引用访问到对象结构,然后再访问到属性值,再做修改。
    • 函数对象类型参数,可以被修改吗?
以上就是全部内容,最后的题目请仔细思考一下,欢迎与我讨论。

参考

  1. 汇编语言入门
  1. V8 内存浅析

© XieZhichao 2022 - 2024