数组是很常见的数据类型。得益于相当友好于开发者们对数据进行操作, 数组成了套路之王了!
当然,友好于开发者,肯定会失去很多的。它就不是很友好于性能优化了,当然,当今的硬件技术发展下,一般的小项目根本就跟前端层面的性能优化无瓜的。
数组的优点:
根据下标,赋值,取值,很方便,存储型的操作!查找,赋值,取值,性能强!
缺点:
扩容,很消耗性能!
数组在低层创建时是要声明长度的!JS视角只是表层,面向内存,数组声明时,内存就把相邻连续的一块区域划分给数组了,区域大小跟声明的数组长度挂钩。
问题来了,数组可以随意地push,shift,,,就需要扩容了,扩容等同于新创建一个数组,长度是扩容时设置的长度,然后把原本的数据依次拷贝过来,然后删除原数组的内存区块。太消耗性能了!
另外,
中间插入,中间删除,首部插入,首部删除,会导致操作位置后面的所有的数据在原内存地址上依次顺序改动。
我对数组的理解:
数组就是人心中的理想的数据类型,人的暴力美学,数组操作多简单!就算是扩容,就算是中间插入删除,又咋了?堂堂正正的阳谋!不就是性能差么?又不是不能用.jif!
针对优缺点,既可以思考:数组的使用场景!比如前端,,,数据量不大时,从来就没怂过!JSON.parse(JSON.stringify(xxx))都无所畏惧,,,
当然,理性地思考,数组适合于多查找,多编辑,少中间首部插入或删除的操作。偏向于读和写的操作场景了!
对数组不适用于中间,首部的插入删除,还有扩容问题,另一种链式数据结构算是跟它完全反过来了!
链表
完全是针对数组的么!
不需要扩容!随意任何位置的元素进行插入,删除!牛皮!偏向于频繁地增删的操作场景!
这么友好于性能,,,终究是无法打破必须不完美的铁律了。它的读写性能不高,查找不如数组快。
优点
- 因为不必像数组一样是内存里的一块连续的内存区域,相对于内存很友好,充分榨取内存的剩余价值。源于链表的数据结构,存储的有下一个节点的地址。
 - 插入和删除操作性能快。
 - 理论上只要内存够,想存多少数据就存多少,扩啥容?
 
缺点
读取一个节点,就要遍历一遍链表,因为它的内存地址不连续。所以,性能不如数组直接靠下标来得快!
数据结构

很典型的因果论哲学模型!比如一个人的成长历史,从小学,到初中,高中,大学。讲究因果联系!
单链表的封装
//链表的封装class LinkedList {constructor(list) {this.header = list || null;this.length = list ? 1 : 0;}append(data) {const list = new List(data);if (this.length === 0) {return this.header = list;}let current = this.header;while (current.next) {current = current.next;}current.next = list;this.length += 1;}insert(data, index) {if (index < 0 || index > this.length) {throw new Error("index参数不合理");}const list = new List(data);let oldList = this.header;if (index === 0) {oldList && (list.next = oldList);this.header = list;} else {let i = 0;while (i < index - 1) { //index-1是找的index-1的那个点,index点可以通过它找到oldList = oldList.next;i++;}let next = oldList.next;oldList.next = list;list.next = next;}this.length++;}get(position) {if (position > this.length - 1 || position < 0) {throw new Error("位置参数超出范围了!");}let current = this.header;if (position === 0) return current;let i = 0;while (i < position) {current = current.next;i++;}return current;}update(position, data) {this.get(position).data = data;return true;}indexOf(data,key) {let current = this.header;if (!current) return -1;let index;let i = 0;while (!index && current) {//这里是扩展了一下如果data是复杂类型的时候,凭关键字段匹配的情况://在用哈希表的时候,我不得不扩展if ( (key ? current.data[key]:current.data) === data) {index = i;} else {current = current.next;i++;}}return index >= 0 ? index : -1;}removeAt(position) {if (position < 0 || position > this.length - 1 || this.length === 0) {throw new Error("参数超出范围!");}let current = this.header;if (position === 0) {this.length -= 1;this.header = current.next;return current;} else {let i = 0;while (i < position - 1) {current = current.next;i++;}const target = current.next;if (target) {current.next = target.next;this.length -= 1;return target;}}}remove(data) {const index = this.indexOf(data);return this.removeAt(index);}size() {return this.length;}isEmpty() {return this.length === 0;}toString() {let current = this.header;let res = "";while (current) {res += (res ? " -> " : "") + current.data;current = current.next;}return res;}}class List {constructor(data, next) {this.data = data;this.next = next ? next : null;}}const list = new List(1);const linkedList = new LinkedList(list);console.log(linkedList);linkedList.append(30);linkedList.insert("2", 1);linkedList.insert("0", 0);linkedList.insert("6", 3);linkedList.insert("20", 4);linkedList.insert("89", 6);console.log(linkedList, linkedList.toString(), 888, linkedList.get(3),);console.log(linkedList.indexOf(3), linkedList.update(3, 10), linkedList.get(3));console.log(linkedList.removeAt(3), linkedList.toString());console.log(linkedList.remove("20"), linkedList.toString(),linkedList.size());
单链表的数据模型很清晰,像舞龙队。舞龙一堆人,其实,隔了很远,如何心意相通,互相完美配合,就是后面的人只关心前面的那一个人。一条链下来。还有一个游戏,就是传话,几个人一个次序方向传话。
由此,看出局限性了,节点只知道下一个节点是谁,只能一个方向次序操作,,,如果遇到一个节点,想要反方向操作上一个节点,怎么办?
比如文本编辑器的光标一行一行地向上向下移动。
需求是,一个节点既可以拿到下一个节点,单链表实现了。又可以拿到它的上一个节点,双链表可以的!
双向链表
其实就是在单链表的优点缺点的基础上,增加了点增删复杂的操作,增加了上一个节点的操作。增加了内存的消耗,其实也就是添加了一个prev的字段,跟功能比起来,可以看不见。
性能一样的。魔改版的单链表。
当然,双链表在性能跟单链表相同的情况下,功能更加完善了。它成了主流了!

一看,就可以知道,增删的方法时,要修改的属性会相当的多的。
双链表的实现封装:
class DoublyLinkedList extends LinkedList {constructor(list) {super(list);this.tail = list || null;}append(data) {const list = new DoublyList(data);if (this.length === 0) {list.prev = null;this.header = list;this.tail = list;} else {const last = this.tail;last.next = list;list.prev = last;this.tail = list;}this.length += 1;}insert(data, index) {if (index < 0 || index > this.length) {throw new Error("插入的下标超出范围!");}const list = new DoublyList(data);if (index === 0) {const current = this.header;if (current) {this.header = list;list.next = current;current.prev = list;} else {this.tail = list;this.header = list;}} else if (index === this.length) {const last = this.tail;last.next = list;list.prev = last;this.tail = list;} else {const test = index * 2 > this.length - 1;let i = test ? this.length - 1 : 0;let current = test ? this.tail : this.header;let key = test ? "prev" : "next";if (test) {while (i > index) {current = current[key];i--;}} else {while (i < index) {current = current[key];i++;}}const last = current.prev;list.prev = last;last.next = list;list.next = current;current.prev = list;}this.length++;}get(index) {if (index < 0 || this.length === 0 || index > this.length - 1) {throw new Error("超出链表的长度范围");}const test = index * 2 > this.length - 1;let current = test ? this.tail : this.header;let i = test ? this.length - 1 : 0;const key = test ? "prev" : "next";if (test) {while (i > index) {current = current[key];i--;}} else {while (i < index) {current = current[key];i++;}}return current;}removeAt(index) {if (index < 0 || this.length === 0 || index > this.length - 1) {throw new Error("操作的下标超出范围!");}const current = this.get(index);const last = current.prev;const next = current.next;if (last) {if (next) {last.next = next;next.prev = last;} else {this.tail = last;last.next = null;}} else {if (next) {this.header = next;next.prev = null;} else {this.header = null;this.tail = null;}}this.length -= 1;return current;}remove(data) {// const index = this.indexOf(data);// return this.removeAt(index);//这里可以简单套用上面的逻辑,但是我又重温了一下,各种逻辑的套路:let current = this.header;let ind, i = 0;while (ind === undefined && current) {if (current.data === data) {ind = i;}current = current.next;i++;}if (ind === undefined) {throw new Error("暂无此节点,无法删除!");}const test = ind * 2 > this.length - 1;let cur = test ? this.tail : this.header;const key = test ? "prev" : "next";let Ind = test ? this.length - 1 : 0;if (test) {while (Ind > ind) {cur = cur[key];Ind -= 1;}} else {while (Ind < ind) {cur = cur[key];Ind += 1;}}const last = cur.prev;const next = cur.next;if (last) {if (next) {last.next = next;next.prev = last;} else {this.tail = last;last.next = null;}} else {if (next) {this.header = next;next.prev = null;} else {this.header = null;this.tail = null;}}this.length--;return cur;}update(index, data) {const list = this.get(index);list.data = data;return list;}backwardString() {return this.toString();}forwardString() {let current = this.tail;let res = "";while (current) {res += (res ? " <- " : "") + current.data;current = current.prev;}return res;}}const doublyList = new DoublyList(1);const doublyLinkedList = new DoublyLinkedList(doublyList);doublyLinkedList.append(2);doublyLinkedList.append(3);doublyLinkedList.append(4);doublyLinkedList.insert("哈哈", 2);doublyLinkedList.update(3, "test");console.log(doublyLinkedList,doublyLinkedList.backwardString(),doublyLinkedList.forwardString(),doublyLinkedList.get(3),doublyLinkedList.indexOf("哈哈"));console.log(doublyLinkedList.removeAt(3),doublyLinkedList.remove(4),doublyLinkedList.toString(),);
