链表append()方法覆盖this.tail时仍可更新嵌套节点的原理疑问
核心前提:JS 基础类型与引用类型的赋值逻辑差异
你观察到的两种不同表现,本质是JS中两类数据类型的赋值规则不同:
- 基础类型(Number、String、Boolean等):赋值时直接拷贝值,两个变量存储在栈内存中完全独立,修改一个不会影响另一个。
- 引用类型(对象、数组等):赋值时拷贝的是堆内存的存储地址,多个变量可以指向同一个堆内存中的对象,修改对象的属性会同步到所有指向该对象的变量上。
第一个示例的运行逻辑
你写的potatoes和fish操作的是基础类型,所以修改不联动是正常表现:
let potatoes = 5 // 栈内存中存入值5,变量potatoes指向该值 let fish = potatoes // 拷贝potatoes的*值*给fish,fish在栈中独立存了一份5 potatoes = 2 // 修改potatoes指向的栈值为2,和fish完全无关 console.log(fish) // 输出5
链表append方法的逐行拆解
链表中的所有节点都是引用类型的对象,我们以第一次调用append(2)的完整流程为例:
- 构造函数执行完成时:
堆内存中生成地址为0x001的节点对象:{value:5, next:null}this.head存储的地址是0x001,this.tail = this.head表示this.tail也存储地址0x001,两个变量指向同一个堆对象。 - 调用
append(2)生成新节点:
堆内存中生成地址为0x002的新节点:{value:2, next:null},赋值给变量newNode - 执行
this.tail.next = newNode:
此时this.tail存的是0x001,这行代码等价于直接修改地址为0x001的堆对象的next属性,赋值为0x002,此时head节点的next已经指向新节点,该修改是直接作用在堆对象上的,和后续this.tail的变化无关。 - 执行
this.tail = newNode:
这步只是修改this.tail这个变量本身存储的地址,从0x001改成0x002,完全不会触碰0x001这个堆对象的内容,也不会修改this.head存储的地址,所以不可能覆盖head。 - 后续调用
append的逻辑完全一致:每次都是先通过this.tail找到当前尾节点的堆地址,修改它的next属性指向新节点,再把this.tail的地址改成新节点的地址即可。
常见疑问解答
this.tail.next是如何同时更新this.tail和它所指向的对象的?
并没有同时更新两者,this.tail.next修改的是this.tail当前指向的堆对象的属性,this.tail本身存储的地址没有变化,直到下一行代码才会修改this.tail本身的指向。- 为什么把
this.tail覆盖为newNode不会把this.head整个覆盖?this.tail和this.head是两个独立的变量,只是初始阶段指向同一个堆对象,修改this.tail本身存储的地址,完全不会影响this.head存储的地址,也不会修改之前指向的堆对象的内容。 - 我们只设置了
this.tail.next,没有直接操作原节点引用,为什么原引用的next属性会被更新?
因为this.tail存储的就是原节点的堆地址,this.tail.next就是直接操作原节点的属性,你所谓的“原引用”和this.tail指向同一个堆对象,所以修改会同步生效。
完整可运行代码
class LinkedList { constructor(value) { this.head = { value, next: null } this.tail = this.head this.length = 1 } append(value) { const newNode = { value, next: null } this.tail.next = newNode this.tail = newNode this.length++ return this } } const linkedList = new LinkedList(5) linkedList.append(2) linkedList.append(8) console.log(linkedList)
内容的提问来源于stack exchange,提问作者InspectorDanno
相关产品推荐
相关产品推荐

