详解链表在前端的应用,顺便再弄懂原型和原型链!

简介: 该文章深入解析了链表在前端开发中的应用,并详细阐述了JavaScript中的原型和原型链的概念及其工作原理。

链表 在前端中的应用常用于原型和原型链当中。在接下来的这篇文章中,将讲解关于 链表 在前端中的应用。

一、链表VS数组

  • 数组:增删非首尾元素时往往需要移动元素
  • 链表:增删非首尾元素,不需要移动元素,只需要更改 next 的指向即可。

二、JS中的链表

  • Javascript中没有链表;
  • 可以用Object模拟链表。

三、前端与链表:JS中的原型链

1、原型是什么?

  • Javascript 中,每个对象都会在其内部初始化一个属性,这个属性就是原型对象(简称原型)。

2、原型链是什么?

  • 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去 prototype 里找这个属性,这个 prototype 又会有自己的 prototype ,于是就这样一直找下去,这样逐级查找形似一个链条,且通过 [[prototype]] 属性连接,这个连接的过程被称为原型链
  • 原型链的本质是链表,且原型链上的节点是各种原型对象,如: Function.prototypeObject.prototype ……。
  • 原型链通过 __proto__ 属性连接各种原型对象。

3、原型链长啥样?

(1)arr → Array.prototype → Object.prototype → null

  • arr.__ proto __ = Array.prototype;
  • Array.__ proto __= Object.prototype;
  • Object.__ proto __= null。

先用代码来演示这段关系:

let arr = [];
console.log(arr.__proto__ === Array.prototype);    //  true
console.log(arr.__proto__.__proto__ === Object.prototype);    //  true
console.log(arr.__proto__.__proto__.__proto__);  //null

解释说明:

假设我们定义了一个对象,名字叫 arr ,那么 arr.__proto__ 表示的是arr这个对象的原型,在这个例子中 let arr = [] 间接调用了 new Array ,所以我们通过 Array.prototype 来表示 Array 这个构造函数的原型对象,通过对 arr.__proto__Array.prototype 进行比较,发现两者相等,所以说, arr 的原型属性就是构造函数 Array 的原型对象。

与上述类似的,我们发现 arr.__proto__Array.prototype 相等,那么继续往源头查找下去, Array 又有它自己的原型属性,那么这个时候 Array 的原型属性 arr.__proto__.__proto__ 又会等于什么呢?

其实,在 js 当中, Object 是所有对象的父对象,也就是说绝大多数的对象都有一个共同的原型 Object.prototype 。所以,这个时候 Array 的原型属性 arr.__proto__.__proto__ 就等于 Object.prototype ,到此为止,找到最原始的对象 Object 的原型之后,基本就快结束了。我们最后再检验 Object 的原型属性 arr.__proto__.__proto__.__proto__ ,发现是 null 空值,也就意味着原型链已经走到了最源头的位置。

总结:

  • Object 是所有对象的父对象
  • 从上面例子中可以看到,所有原型对象都会先指向自己的 __proto__ 属性,之后再指向自己的原型,最后指向父对象 Object 的原型。

下面再给出两个例子,大家可以依据(1)的方法进行检验。

(2)obj → Object.prototype → null

  • obj.__ proto __ = Object.prototype;
  • Object.__ proto __= null。

用代码来演示这段关系:

let obj = {
   };
console.log(obj.__proto__ === Object.prototype);    //  true
console.log(obj.__proto__.__proto__);  //null

(3)func → Function.prototype → Object.prototype → null

  • func.__ proto __ = Function.prototype;
  • Function.__ proto __= Object.prototype;
  • Object.__ proto __= null。

用代码来演示这段关系:

let func = function(){
   

};
console.log(func.__proto__ === Function.prototype);    //  true
console.log(func.__proto__.__proto__ === Object.prototype);    //  true
console.log(func.__proto__.__proto__.__proto__);  //null

(4)class中的原型

1)先来看第一段代码。

//父类
class People{
   
    constructor(){
   
        this.name = name;
    }
    eat(){
   
        console.log(`${
     this.name} eat something`);
    }
}

//子类
class Student extends People{
   
    constructor(name, number){
   
        super(name);
        this.number = number;
    }
    sayHi(){
   
        console.log(`姓名:${
     this.name},学号:${
     this.number}`);
    }
}

console.log(typeof Student); //function
console.log(typeof People); //function

let xialuo = new Student('夏洛', 10010);
console.log(xialuo.__proto__);
console.log(Student.prototype);
console.log(xialuo.__proto__ === Student.prototype); //true

从上面代码中可以看到, typeof Studenttypeof People 的值是 function ,所以 class 实际上是函数,也就是语法糖。

再看下面三个 console.log 打印的值,我们来梳理一个原型间的关系。首先 Student 是一个 class ,那么每个 class 都有它的显式原型 prototype ,而 xialuo 是一个实例,每个实例都有它的隐式原型 __proto__ 。它们两者之间的关系就是,实例 xialuo__proto__ 指向对应的 class (即 Student )的 prototype

因此,对于class中的原型,可以得出以下结论:

  • 每个 class 都有显式原型 prototype
  • 每个实例都有隐式原型 __proto__ ;
  • 实例的 __proto__ 指向对应 classprototype

2)再来看第二段代码。

//父类
class People{
   
    constructor(){
   
        this.name = name;
    }
    eat(){
   
        console.log(`${
     this.name} eat something`);
    }
}

//子类
class Student extends People{
   
    constructor(name, number){
   
        super(name);
        this.number = number;
    }
    sayHi(){
   
        console.log(`姓名:${
     this.name},学号:${
     this.number}`);
    }
}

let xialuo = new Student('夏洛', 10010);
console.log(xialuo.name); //夏洛
console.log(xialuo.number); //10010
console.log(Student.sayHi()); //姓名:夏洛,学号:10010

从上面代码中可以得出, Student 类本身有 number 这个属性,所以它会直接读取自身 number 的值。同时,它是没有 name 这个属性的,但是由于它继承自父类 People ,所以当它找不到 name 则个属性时,它会自动的往 __proto__ 中查找,于是就往它的父类 People 进行查找。

所以,从上面的演示中可以得出基于原型的执行规则:

  • 先获取属性(比如 xialuo.namexiaoluo.number ) 或者获取执行方法 (比如 xialuo.sayhi() );
  • 获取后,先在自身属性和方法上寻找;
  • 如果找不到则自动去 __proto__ 中查找。

(5)new Object() 和 Object.create() 区别

  • {} 等同于 new Object() ,原型为 Object.prototype
  • Object.create(null) 没有原型;
  • Object.create({...}) 可指定原型。

4、原型链知识点

1)如果 A 沿着原型链能找到 B.prototype ,那么 A instanceof Btrue

举例1:

let obj = {
   };
console.log(obj instanceof Object); //true

对于 obj instanceof Object 进行左右运算, obj instanceof Object 的意思是查询 obj 的原型链上是否有 Object 的原型对象,即 obj 是否是 Object 的实例。

举例2:

let func = function(){
   

};
console.log(func instanceof Function); //true
console.log(func instanceof Object); //true

对于 func 来说, func 既是 Function 的实例,也是 Object 的实例。

2)如果 A 对象上没有找到 x 属性,那么会沿着原型链找 x 属性。

举例:

const obj = {
   };

Object.prototype.x = 'x';

console.log(obj.x); //x

从上述代码中可以看到,obj 在自己的区域内没有找到x的值,则会继续往它的原型链找,最终找到 Object.prototype.x ,所以 obj.x = x

接下来我们用两道常见的面试题来回顾这两个知识点。

5、常见面试题

(1)instanceof原理

知识点:如果 A 沿着原型链能找到 B.prototype ,那么 A instanceof Btrue

解法:遍历 A 的原型链,如果找到 B.prototype ,返回 true ,否则返回 false

代码演示:

// 判断A是否为B的实例
const instanceOf = (A, B) => {
   
    // 定义一个指针P指向A
    let p = A;
    // 当P存在时则继续执行
    while(p){
   
        // 判断P值是否等于B的prototype对象,是则说明A是B的实例
        if(p === B.prototype){
   
            return true;
        }
        // 不断遍历A的原型链,直到找到B的原型为止
        p = p.__proto__;
    }
    return false;
}

(2)看代码,得出输出结果。

看下面一段代码,请给出4个 console.log 打印的值。

let foo = {
   };
let F = function(){
   };
Object.prototype.a = 'value a';
Function.prototype.b = 'value b';

console.log(foo.a); //value a
console.log(foo.b); //undefind

console.log(F.a); //value a
console.log(F.b); //value b

知识点:如果在 A 对象上没有找到 x 属性,那么会沿着原型链找 x 属性。

解法:明确 fooF 变量的原型链,沿着原型链找 A 属性和 B 属性。

解析:从上面一段代码中可以看到, foo 是一个对象,那么它的 __proto__ 属性指向 Object.prototype ,所以此时 foo.a 会往它的原型链上面找具体的值,也就是 Object.prototype.a 的值。同理, foo.b 会往它的原型链找值,但是找不到 Object.prototype.b 的值,所以最终返回 undefinedF.aF.b 也是同样的道理,大家可以进行一一验证。

四、写在最后

原型和原型链在前端中是再基础不过的知识了!我们平常所写的每一个对象中,基本上都有它的原型和原型链。因此,对于前端来说,如果原型和原型链的关系都不明白的话,不知不觉中很容易写出各种各样的bug,这对于后续维护和程序来说都是一个巨大的灾难。所以,了解原型和原型链,对于前端来说是一项必备的技能。

链表在前端中的应用就讲到这里啦!如果有不理解或者有误的地方也欢迎私聊我或加我微信指正~

  • 公众号:星期一研究室
  • 微信:MondayLaboratory

创作不易,如果这篇文章对你有用,记得点个 Star 哦~

相关文章
|
1天前
|
前端开发 JavaScript 开发者
“揭秘React Hooks的神秘面纱:如何掌握这些改变游戏规则的超能力以打造无敌前端应用”
【10月更文挑战第25天】React Hooks 自 2018 年推出以来,已成为 React 功能组件的重要组成部分。本文全面解析了 React Hooks 的核心概念,包括 `useState` 和 `useEffect` 的使用方法,并提供了最佳实践,如避免过度使用 Hooks、保持 Hooks 调用顺序一致、使用 `useReducer` 管理复杂状态逻辑、自定义 Hooks 封装复用逻辑等,帮助开发者更高效地使用 Hooks,构建健壮且易于维护的 React 应用。
8 2
|
6天前
|
JavaScript 前端开发 测试技术
前端全栈之路Deno篇(五):如何快速创建 WebSocket 服务端应用 + 客户端应用 - 可能是2025最佳的Websocket全栈实时应用框架
本文介绍了如何使用Deno 2.0快速构建WebSocket全栈应用,包括服务端和客户端的创建。通过一个简单的代码示例,展示了Deno在WebSocket实现中的便捷与强大,无需额外依赖,即可轻松搭建具备基本功能的WebSocket应用。Deno 2.0被认为是最佳的WebSocket全栈应用JS运行时,适合全栈开发者学习和使用。
|
2天前
|
前端开发 API UED
深入理解微前端架构:构建灵活、高效的前端应用
【10月更文挑战第23天】微前端架构是一种将前端应用分解为多个小型、独立、可复用的服务的方法。每个服务独立开发和部署,但共同提供一致的用户体验。本文探讨了微前端架构的核心概念、优势及实施方法,包括定义服务边界、建立通信机制、共享UI组件库和版本控制等。通过实际案例和职业心得,帮助读者更好地理解和应用微前端架构。
|
6天前
|
人工智能 资源调度 数据可视化
【AI应用落地实战】智能文档处理本地部署——可视化文档解析前端TextIn ParseX实践
2024长沙·中国1024程序员节以“智能应用新生态”为主题,吸引了众多技术大咖。合合信息展示了“智能文档处理百宝箱”的三大工具:可视化文档解析前端TextIn ParseX、向量化acge-embedding模型和文档解析测评工具markdown_tester,助力智能文档处理与知识管理。
|
8天前
|
前端开发 API UED
拥抱微前端架构:构建灵活、高效的前端应用
【10月更文挑战第17天】微前端架构是一种将前端应用拆分为多个小型、独立、可复用的服务的方法,每个服务可以独立开发、部署和维护。本文介绍了微前端架构的核心概念、优势及实施步骤,并分享了业界应用案例和职业心得,帮助读者理解和应用这一新兴架构模式。
|
8天前
|
Web App开发 缓存 前端开发
拿下奇怪的前端报错(六):多摄手机webrtc拉取视频流会导致应用崩溃,从而无法进行人像扫描
本文介绍了一种解决手机摄像头切换导致应用崩溃的问题的方法。针对不支持facingMode配置的四摄手机,通过缓存和序号切换的方式,确保应用在特定设备上不会频繁崩溃,提升用户体验。
|
13天前
|
存储 监控 前端开发
掌握微前端架构:构建未来前端应用的基石
【10月更文挑战第12天】随着前端技术的发展,传统的单体应用架构已无法满足现代应用的需求。微前端架构通过将大型应用拆分为独立的小模块,提供了更高的灵活性、可维护性和快速迭代能力。本文介绍了微前端架构的概念、核心优势及实施步骤,并探讨了其在复杂应用中的应用及实战技巧。
|
14天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
14天前
|
前端开发 算法 测试技术
前端大模型应用笔记(五):大模型基础能力大比拼-计数篇-通义千文 vs 文心一言 vs 智谱 vs 讯飞vsGPT
本文对比测试了通义千文、文心一言、智谱和讯飞等多个国产大模型在处理基础计数问题上的表现,特别是通过链式推理(COT)提示的效果。结果显示,GPTo1-mini、文心一言3.5和讯飞4.0Ultra在首轮测试中表现优秀,而其他模型在COT提示后也能显著提升正确率,唯有讯飞4.0-Lite表现不佳。测试强调了COT在提升模型逻辑推理能力中的重要性,并指出免费版本中智谱GLM较为可靠。
前端大模型应用笔记(五):大模型基础能力大比拼-计数篇-通义千文 vs 文心一言 vs 智谱 vs 讯飞vsGPT
|
13天前
|
JSON 前端开发 JavaScript
构建现代前端应用的基石
【10月更文挑战第13天】构建现代前端应用的基石