Python 与 JavaScript 变量内存存储机制

一、核心底层本质差异(最关键)

  • Python:万物皆对象
    • Python 中所有数据都是堆中的对象(数字、字符串、布尔、 元组、列表、函数全部是对象)。
    • 变量没有类型、不存数据,只是对象的引用标签(内存地址指针)
    • 真正的数据、类型、内存地址全部保存在堆对象中
    • 核心区分:可变对象、不可变对象
  • JavaScript:原始值 + 引用对象 双模型
    • JS 采用两套完全独立的存储规则:
    • 基本原始(简单数据)类型(Number、String、Boolean、Null、Undefined、Symbol、BigInt):值直接存在栈内存
    • 引用(复杂)数据类型(Object、Array、Function):数据存在堆内存,栈中仅存堆的内存地址
    • 变量直接存储「值」或「地址」,变量本身有明确存储内容

二、内存分区与基础赋值逻辑对比

  • 内存分区通用规则
    • 所有高级语言内存分为两块:
    • 栈内存:空间小、读写快、自动释放,存放变量、基础值、引用地址
    • 堆内存:空间大、持久存储,需要垃圾回收,存放复杂对象数据
  • Python 赋值底层逻辑
    • Python 没有任何数据存在栈中,栈永远只存指针:
    • a = 10:堆内存创建整数对象 10
    • 栈内存变量 a 存储该对象的内存地址
    • b = a:b 和 a 指向同一个堆对象
    • 核心特点:赋值永远是「引用拷贝」,不拷贝数据本身。
  • JavaScript 赋值底层逻辑
    • 原始(简单)类型
      栈内存直接存储数值 10,赋值是完整的值拷贝,两个变量完全独立。
      let a = 10;
      let b = a;
      a = 20;
      // b 仍然是 10
      
    • 引用(复杂)类型
      栈中存储堆数组的地址,赋值拷贝地址,多个变量共享堆数据。
      let a = [1,2];
      let b = a;
      a.push(3);
      // b 同步变为 [1,2,3]
      

三、Python可变 / 不可变数据

  • 不可变数据(整数、浮点数、字符串、布尔、元组、复数、冰冻集合)
    • 不可变对象无法修改原有堆数据,重新赋值会新建对象、更换引用。
      a = 100
      b = a
      a = 200  # 新建200对象,a更换指向,b不变
      print(b) # 100
      
  • 可变数据(列表、字典、 普通集合)
    • Python List、Dict 为堆中可变对象:
      • 修改数据是直接修改堆内存原数据
      • 所有指向该对象的变量,数据会同步变化

四、函数参数传递机制

  • Python:统一「传引用」
    • 传不可变对象:函数内重新赋值会新建对象,外部数据不变
    • 传可变对象:函数内修改原堆数据,外部同步改变
  • JavaScript:统一「按值传递」
    • 原始类型:拷贝真实数值,内外完全隔离
    • 引用类型:拷贝堆地址,可通过地址修改堆原数据

五、Python 专属内存优化机制(重点)

Python 为了减少频繁创建、销毁对象的性能开销,设计了两大缓存机制:小整数池、字符串驻留,这是 JS 不具备的显式机制。

  • 小整数池(整数缓存机制)

    • 原理
      Python 解释器启动时,提前在堆中预创建 -5 ~ 256 所有整数对象,常驻内存、永不销毁
      开发中最常用的小整数全部复用缓存对象,无需重复开辟内存,极致优化性能。
    • 存储位置
      Small Integer Cache
    • 代码验证:
      # 在交互环境测试
      a = 100
      b = 100
      print(id(a) == id(b))  # True,共用同一个对象
      
      c = 300
      d = 300
      print(id(c) == id(d))  # False,超出缓存池,新建对象
      
    • 补充说明
      脚本文件运行时会触发常量折叠编译优化,大于 256 的相同整数也可能地址一致,这不是小整数池机制,属于编译优化,和运行时缓存无关
  • 字符串驻留(String Interning)

    • 原理
      字符串是不可变对象,Python 将高频重复的规则化字符串缓存到全局驻留池,相同内容的字符串只存储一份,实现内存复用、加速比对。
    • 自动驻留规则
      只有满足以下条件,才会自动进入驻留池:
      仅包含:字母、数字、下划线(合法标识符格式)
      无空格、无特殊符号、无中文
    • 存储位置
      Python维护一张全局表,intern table
    • 代码验证
      # 自动驻留
      s1 = "user_123"
      s2 = "user_123"
      print(id(s1) == id(s2)) # True
      
      # 不满足规则,不驻留
      s3 = "hello world"
      s4 = "hello world"
      print(id(s3) == id(s4)) # False
      
    • 手动强制驻留
      通过 sys.intern() 可强制任意字符串加入缓存池。
      sys.intern(s) 会把字符串放入全局字符串驻留池,返回驻留后的字符串对象,保证后续用 intern 获取同一字面量时拿到同一个 id
      不会自动驻留普通字面量创建的长字符串
      import sys
      a = sys.intern("hello world")
      b = sys.intern("hello world")
      print(id(a) == id(b)) # True
      
  • 常量池

    • 概述
      常量池就是编译器提前把程序中出现的常量保存起来,运行时直接引用,而不是每次都重新创建。
    • 存储位置
      它保存在 Code Object(代码对象) 中
    • 哪些对象可以放进常量池
      整数、浮点数、字符串、元组
    • 判断标准
      • 编译期可确定的不可变常量
      • 必须同时满足:
        ✅ 不可变(Immutable)
        ✅ 编译期可确定(Compile-time Constant)
    • 作用
      避免重复创建不可变对象
    • 常量池总结
      对象 不可变 编译期可确定 能否进入 co_consts
      123
      3.14
      "hello"
      (1, 2, 3)
      (x,)
      tuple([1,2,3])
      "ab" * 3(CPython 会常量折叠)
      "ab" * n
      [1,2,3]
  • 小整数池、字符串驻留、常量池 总结

机制 是否编译期 作用对象 生命周期
常量池(co_consts 编译期常量(数字、字符串、元组等) 属于某个代码对象(函数或模块)
小整数缓存 否(解释器启动时建立) 通常是 -5256 的整数 整个解释器进程
字符串驻留(intern) 否(可自动或手动) 字符串 整个解释器进程

六、JS 与 Python 核心差异总结表

对比维度 Python JavaScript
存储核心 万物皆堆对象,变量只存引用地址 原始值存栈,对象存堆,双存储模型
赋值逻辑 永远拷贝对象引用 原始值拷贝值,引用类型拷贝地址
整数优化 拥有 -5~256 小整数缓存池 无整数缓存机制
字符串优化 严格规则的自动驻留池 + 手动驻留 V8 隐式驻留,无公开固定规则
参数传递 传对象引用 统一按栈内值传递
垃圾回收 引用计数 + 分代 GC 标记清除算法
数据区分 可变 / 不可变对象 原始类型 / 引用类型

七、总结

  • Python:没有变量存数据,所有数据都是堆对象,一切赋值都是引用传递,靠小整数池和字符串驻留极致优化内存性能;
  • JavaScript:基础值栈上直接存储,复杂数据堆存储,分层管理内存,无显式常量缓存池机制。

Python与JavaScript 赋值、深浅拷贝

一、Python

  • 可变对象和不可变类型划分

    不可变类型:
    数值、字符串、元组.
    可变类型:
    列表、字典、集合
    总结:
    在不改变地址值的情况下,内容可以改就是可变类型,不可以改就是不可变类型

  • 地址赋值、浅拷贝、深拷贝规则

    纯不可变类型:
    普通赋值、浅拷贝、深拷贝, 地址都不变
    原值修改不影响拷贝后的值

    可变类型:
    普通赋值, 地址不变; 浅拷贝、深拷贝, 地址会变
    普通赋值、浅拷贝, 原值修改 影响 拷贝后的值; 深拷贝,原值修改不影响拷贝后的值

    深拷贝内部逻辑:
    遍历要拷贝对象的每一层;
    若当前层是可变对象(list / dict / 自定义实例):开辟新堆内存,完整复制一份;
    若当前层是纯不可变对象(tuple / int / str):为了性能优化,直接返回原对象引用,不新建内存。

  • 注意点

    # 不可变类型 值相同 内存中只存一份
    a = 3
    b = 3
    print(id(a)) # 4545460504
    print(id(b)) # 4545460504
    print(id(3)) # 4545460504
    
    # 如果深拷贝遇到纯不可变对象(tuple/int/str/float),不会新建内存,直接复用原对象地址
    a_tuple = ((1, 2, 3),)
    b_tuple = copy.deepcopy(a_tuple)
    print(id(a_tuple) == id(b_tuple)) # True
    
    # 深拷贝拷贝非纯不可以变类型地址会变
    a_tuple = ([[1,2,3],[4,5,6]],)
    b_tuple = copy.deepcopy(a_tuple)
    print(id(a_tuple) == id(b_tuple)) # False
    print(id(a_tuple[0]) == id(b_tuple[0])) # False
    print(id(a_tuple[0][0]) == id(b_tuple[0][0])) # False
    print(id(a_tuple[0][0][0]) == id(b_tuple[0][0][0])) # True deepcopy对不可变对象的复用策略导致的
    

二、JavaScript

  • 浅拷贝方式

    • 扩展运算符 …(最常用)
    • Object.assign()
  • 深拷贝方式

    • Lodash 深拷贝 _.cloneDeep
  • JS中主要是针对引用(复杂)数据类型

    方式 新外层对象 第一层数据独立 嵌套对象独立 底层原理
    直接赋值 ❌ 无新对象 ❌ 共享引用 ❌ 共享引用 复制内存地址,共用堆对象
    浅拷贝 ✅ 新建外层 ✅ 基本类型独立 ❌ 嵌套共用引用 只递归一层,深层仅复制地址
    深拷贝 ✅ 全新外层 ✅ 全部层级独立 ✅ 所有嵌套全新 递归创建每一层对象,完全隔离

更多推荐