Consider a LinkedList class which mimics the Linkedlist data structure as below:
class LinkedList {
constructor(value) {
this.head = {
value: value,
next: null
};
this.tail = this.head;
this.length = 1;
}
append(value) {
const newNode = {
value: value,
next: null
}
this.tail.next = newNode; // why does this change head.next ?
this.tail = newNode;
this.length++;
return this;
}
}
let myLinkedList = new LinkedList(10);
myLinkedList.append(5);
log output
LinkedList {
head: { value: 10, next: { value: 5, next: null } },
tail: { value: 5, next: null },
length: 2
}
I see that this.tail.next
will change the next property of tail as well (then this.tail = newNode
will reassign tail to newNode). What I don't understand here is why this.tail.next
would also change the next property of this.head?
Also, when appending another number to the list myLinkedList.append(16)
, it keeps updating the next property of head as below:
LinkedList {
head: { value: 10, next: { value: 5, next: [Object] } },
tail: { value: 16, next: null },
length: 3
}
Maybe a possible reason relates to the constructor where I define this.tail = this.head
? But I'm not really sure since this one only assigns value of head to tail.
To sum up, my question is why does this.tail.next = newNode
change the next property of the head? Also, when appending another value, why does it change head.next.next and so on?
When the constructor has run, this.tail
and this.head
reference the same object, so any assignment you make to this.tail.next
is visible in this.head
, since that really is a reference to the same object that is being mutated.
It may help to visualise this. Once the constructor has run, we have this situation:
this.head
↓
┌───────────┐
│ value: 10 │
│ next: null│
└───────────┘
↑
this.tail
Then append(5)
will first create a new node:
this.head newNode
↓ ↓
┌───────────┐ ┌───────────┐
│ value: 10 │ │ value: 5 │
│ next:null │ │ next:null │
└───────────┘ └───────────┘
↑
this.tail
Then this.tail.next = newNode;
is executed, which is a modification of that next
property in the first object:
this.head newNode
↓ ↓
┌───────────┐ ┌───────────┐
│ value: 10 │ │ value: 5 │
│ next: ———————→ │ next:null │
└───────────┘ └───────────┘
↑
this.tail
So indeed, this also changes this.head.next
... because it is just the very same property.
Then this.tail = newNode;
is executed:
this.head newNode
↓ ↓
┌───────────┐ ┌───────────┐
│ value: 10 │ │ value: 5 │
│ next: ———————→ │ next:null │
└───────────┘ └───────────┘
↑
this.tail
The next time append
is called, the next
property of the second object will be updated, and so we get:
this.head newNode
↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ value: 10 │ │ value: 5 │ │ value: 16 │
│ next: ———————→ │ next: ———————→ │ next:null │
└───────────┘ └───────────┘ └───────────┘
↑
this.tail
And yes, this change is also traceable from this.head
, because... it is a linked list... so it should be traceable. As each next
property refers to the next node, you can find your way from head
to any of the nodes.