【JavaScript】一文从“链表”轻松理解“原型链”

251 阅读3分钟

前言

对于初次学习JavaScript的同学来说,原型链是JS中比较难理解的

没有学过“链表”数据结构,并以此理解原型链,很可能只是记住了原型链相关知识结论

原型链的本质是“链表”,那就从“链表”理解原型链吧


一、链表

(1)什么是链表

链表是一种有序元素集合,其中的元素在内存中存储不连续

每一个元素,由节点数据部分和一个指向下一个元素的指针组成,通常是next指针

(2)示例

JavaScript中没有链表这种数据结构,可以用对象Object去模拟链表

const a = { val: 'a' };
const b = { val: 'b' };
const c = { val: 'c' };
const d = { val: 'd' };
a.next = b;
b.next = c;
c.next = d;

(3)链表遍历

let pointer = a;
while (pointer) {
   console.log(pointer.val);
   pointer = pointer.next;
}
// 控制台按序输出 a b c d

(4)添加元素:修改相邻节点的指针来实现

const e = { val: 'e' };
b.next = e;
e.next = c;
// 在b,c元素中间,添加一个e元素

(5)删除元素:修改相邻节点的指针来实现

b.next = c;
e.next = null
// 删除b,c元素中的e元素

二、链表和数组的区别

元素存储插入和删除操作访问速度优势
数组连续存储需要移动元素(甚至大量元素)通过索引直接访问访问元素速度更快
链表不连续存储仅需修改相邻节点的指针从头遍历的方式访问插入和删除元素更快

三、原型链

(1)规则

当访问对象的属性或者方法时,首先查找这个对象自身有没有该属性;

如果没有,就会按照原型链依次查找。

(2)简要概括

构造函数上有一个prototype属性,用来存放构造函数的公共方法,其值本身也是一个对象

每次用构造环函数,new一个新的对象,对象上有一个__proto__属性指向构造函数的原型

(3)示例

function Animal(name, age) {
    this.name = name
    this.age = age
}

Animal.prototype.eat = function() {
    console.log('吃鱼')
}

const cat = new Animal('小花', 3)

console.log(cat.__proto__ === Animal.prototype) // ture

Animal构造函数有一个prototype属性,存放Animal的公共方法,它是Animal的原型

new一个cat对象,它身上有一个__proto__属性指向Animal构造函数的原型

console.log(cat.__proto__ === Animal.prototype) // ture

同理,Animal.prototype本身也是一个对象,它是Object构造函数new出来的

Animal.prototype上有一个__proto__属性指向Object构造函数的原型

console.log(Animal.prototype.__proto__ === Object.prototype) // true

四、用链表来理解原型链

(1)关系

原型链本质是一个“链表”

除链头外,其值是一个个构造函数的原型prototype,

链头是一个构造函数的实例对象

next指针是用__proto__代替

(2)示例

上方cat实例的原型链,如果给链表每个节点,一个简单的名称,就更好理解了

// cat -> Animal.prototype -> Object.prototype -> null (next指针为__proto__)

const a = cat;
const b = Animal.prototype;
const c = Object.prototype;
const d = null;
//  a -> b -> c -> d  (next指针为__proto__)

// a.__proto__ = b;
// b.__proto__ = c;
// c.__proto__ = d;
// 上面链表的串联,JS本身就已经做好啦

原型链,“链表”的本质一下就清晰明啦!