前言
对于初次学习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本身就已经做好啦
原型链,“链表”的本质一下就清晰明啦!