一、翻越前端的三座大山——继承/原型链

news2024/9/20 0:58:57

文章目录

  • 原型专题
    • 前言
    • 什么是原型?
    • 实例和原型的关系
    • 什么是原型链?
    • Object 和 Function 的继承关系
    • 原型运用例子:
      • 手写instanceof
      • 手写call,apply
      • 手写new
      • 六大继承方式
        • 原型链继承
        • 构造函数继承
        • 原型链 + 构造函数
        • 优化(原型链 + 构造函数)
        • 再次优化(原型链 + 构造函数)
        • ES6中的class继承
    • 总结

原型专题

前言

这是一篇详细介绍Javascript中原型和继承的内容,从里到外透析继承在Javascript中的秘密,文中会参杂一些常见有关继承的面试题目,耐心看完相信会有收获。

什么是原型?

这是一个比较抽象的概念。

在Javascript中的各种官方定义好的对象如,Object和Function等其实都是由一个源对象创建出来的,这是所有数据对象的起源,我们自己定义的构造函数等其实本质上都来源于这个源对象。

实例和原型的关系

首先我们要理顺三个概念,构造函数,实例,原型这三者直接的关系。所有Javascript有关原型的内容其实都是在这三者间反复横跳。

function Person() {
}
let person = new Person()

在上面的代码中,Person就是我们的构造函数,person 就是构造函数new出来的实例。那么原型是?

从上面的原型概念已经说了,一个对象的原型是来源于创建时关联的对象。那我们应该如何找到这个对象呢?

这里需要借助两个东西,prototype 和 proto

我们构造函数可以借助prototype找到原型,而实例可以借助proto找到原型。

console.log(Person.prototype) 
console.log(Person.prototype === person.__proto__) // true

同样的原型也可以找到他生成的构造函数的,利用 constructor

console.log(Person.prototype === Person.prototype.constructor) // true

介绍到这里我们应该大致了解的原型,构造函数,实例之间的关系了。

用一张图来表示就是这样。

image

到这里,我们来正式的介绍一下prototype,proto和constructor三者。

  • prototype:构造函数用来指向原型对象,也是我们new出一个实例的关键属性。
  • proto:这是每一个对象都会有的属性(null除外),实例用来指向原型对象的属性。
  • constructor:原型对象用来指向构造函数的属性。

什么是原型链?

首先我们要明白为什么要有原型链。

我们的原型之所以重要,很重要一点是每个实例都会继承原型的属性,重点在于继承这个功能。

当我们读取一个实例的属性,如果找不到该属性,我们就可以顺着原型链去寻找这个实例的原型有没有该属性,如果还没有则一直找下去,直到找到最顶层。

举个例子:

function Person() {
}
Person.prototype.name = 'lyy';
person.name = 'LYY';
console.log(person.name) // LYY

delete person.name;
console.log(person.name) // lyy

在这个例子中,我们最开始会找实例有没有name这个属性,如果找得到就直接输出,如果找不到就去找原型的name。

通过上面这个例子,应该知道原型链大概是个什么东西了,相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

image

在源对象后面就是null。

Object 和 Function 的继承关系

既然我们定义一个函数function,我们依托这个函数生成的实例必然会继承一个对象,那么function继承的对象是否就是Object?

我们构造函数的Function和Object到底是什么关系?

举一个例子:

function Person() {
}
var obj = {
	 name: 'lyy',
	 age: 23,
}

如果我们对Object深挖下去的话, 可以发现对于Object来说,他确实是离源对象最近的一个对象。

而且直接用var obj出来的对象其实就是Javascript内置Object构造函数的一个实例。

因此我们不难发现,obj确实是继承链条上的顶点了。

console.log(Object.prototype)  // 源对象
console.log(Object.__proto__)  // f()
console.log(Object.__proto__.__proto__)  // 源对象
console.log(Object.__proto__.__proto__.__proto__) // null
console.log(Object.prototype === obj.__proto__)  // true

那么对于function呢?

Funtion的prototype指向的是和Object.proto一样的东西,我们通过打印出来会发现他其实是一个函数fn

这是啥?我相信很多人看了都会疑问

console.log(Function.prototype === Object.__proto__) // true
console.log(Function.__proto__ == Function.prototype)  // true
console.log(Person.__proto__.__proto__ === Object.prototype) // true

为了形象表述,我将整个流程大概画出来,如下图。
image

实际上我们发现Funtion和Object其实也没什么太大联系,也不是我们之间谁继承谁,只是他们的源对象都是一致,并且为Funtion的prototype指向的是和Object.proto一样。

通过查阅资料发现:

Function.prototype 对象是一个函数对象(它的 [[Class]] 是 Function),Function.prototype 可以被调用,它接受任何参数,并且返回 undefined。
但是 Function.prototype 的原型是 Object.prototype。

翻译过来也就是说:

  • Function.prototype 是一个函数
  • 但是 Function.prototype.proto === Object.prototype

太奇葩了,而官方解释是:

The Function prototype object is specified to be a function object to ensure compatibility with ECMAScript code that was created prior to the ECMAScript 2015 specification.

翻译:这么做是为了兼容之前的 ECMAScript 代码。

所以function和Object并不是相互继承的关系,他们之间的fn函数是来源于历史遗留问题。

原型运用例子:

下面写一些依据原型继承的常见面试八股吧

手写instanceof

instanceof是判断构造函数和实例对象在原型链上是否有关系,有就返回true,否则就返回false

这个就是典型的利用原理链去比对即可,我们知道构造函数可以用prototype找到原型,而实例对象也可以靠proto找到原型,还能在此基础上不断向上搜索。

function myInstanceof(target,origin) {
    while(target) {
        if (target.__proto__ === origin.prototype) return true;
        target = target.__proto__;
    }
    return false;
}

手写call,apply

call 和 apply都是用来继承的,写一个方法,让一个新对象来继承他的方法。

call作用:提供新的 this 值给当前调用的函数/方法。更简单点的理解,call把this换成了obj的this,并且给调用的函数用了。

手写call:
call 需要完成两个点:

  • 改变this指向,将之变为传入的对象this
  • 可以传入一个不定长的参数。
Function.prototype.call2 = function (obj) {
    obj = obj || window;  // 如果传进来为null,则为函数应该挂到window上
    obj.fn = this;

    var args = [];
    for (let i = 1; i < arguments.length; i ++ ) {
        args.push('arguments[' + i  +']');
    }
    var res = eval('obj.fn(' + args + ')');

    delete obj.fn;
    return res;
}

手写apply:
apply和call实现一样,唯一区别他会给出一个数组参数。

Function.prototype.apply2 = function(obj,arr) {
    var obj = obj || window;
    obj.fn = this;

    var res,args = [];
    for (let i = 0; i < arr.length; i ++ ) {
        args.push('arr[ ' + i + ']');
    }

    res = eval('obj.fn(' + args + ')');

    delete obj.fn
    return res;
}

手写new

new的作用是利用构造函数来创建一个实例。

实际上,我们创建一个对象非常容易,直接var obj就可以立即创建,这是第一步。

第二步,我们创建的实例是依据于对应的构造函数的,所以从上面的原型链学习就已经知道了,实例的proto指向必须指向原型,也就是和构造函数的prototype一致。

第三步,我们要继承构造函数的属性和方法,所以继承的手段我们直接用call或者apply即可。

function mynew(fn, ...args) {
    // 1. 创建一个新对象
    const obj = {};
    // 2. 为新对象添加属性__proto__,将该属性链接至构造函数的原型对象
    obj.__proto__ = fn.prototype;
    // 3. 执行构造函数,this被绑定在新对象上
    const res = fn.call(obj, ...args);
    // 4. 确保返回一个对象
    return res instanceof Object ? res : obj;
}

在这里还是说明一下最后一步为什么要来判断吧。

call的返回值和构造函数的返回值有关,如果构造函数有返回值就会返回构造函数的返回值,如果没有的话就会返回undefined。

所以call让obj继承完后,需要判断一下构造函数是否有返回值。

例如:

/// 1.如果在mynew中打印res,结果是undefined
function Person(name, age) {
    this.name = name;
    this.age = age;
}

///2.如果在mynew中打印res,结果就是实例对象了。
function Person(name, age) {
    this.name = name;
    this.age = age;
    return this
}

六大继承方式

继承是我们面向对象最重要的性质之一。另A对象通过继承B对象,获取B对象的属性和方法来提高代码复用性。

下面来介绍Javascript的六大继承。为了方便,我们先在这里举个例子,下面的各种继承都以这个例子进行操作,下文代码不会重复写。

//父类型
function Person(name, age) {
    this.name = name,
    this.age = age,
    this.play = [1, 2, 3]
    this.setName = function () { }
}
Person.prototype.setAge = function () { }
//子类型
    function Student(price) {
    this.price = price
    this.setScore = function () { }
}

原型链继承

这是最简单粗暴的继承方式,直接让父类型的实例对象作为子类型的一个原型。

Student.prototype = new Person()

那这样,子类就可以借助于proto访问到父类的实例甚至是原型,实现了属性和方法的继承。

优点:

  • 子类可以访问到父类的全部属性和方法。

缺点:

  • 无法实现多继承(子类只能继承一个父类的属性和方法)
  • 由于是直接调用实例,因此实际上父类的属性和方法是给全部的子类实例共享。

构造函数继承

利用 call 继承,子类在构造函数中用 call 调用父类的构造函数,让子类的 this 继承到父类的构造函数的属性和方法。

function Student(name, age, price) {
    Person.call(this, name, age)  // 相当于: this.Person(name, age)
    this.age = age
    this.price = price
}

优点:

  • 可以实现多继承(call多个父类)
  • 解决了子类实例共享父类属性/方法的问题。
  • 可以传参

缺点:

  • 无法获取父类原型的属性和方法。

原型链 + 构造函数

研究上面两个例子,我们不难发现这两个继承方式是相辅相成的,因此我们对两个种继承方式各持所需。

通过调用父类的构造函数,继承父类的属性方法和传参的特点,在利用原型链继承父类原型。

function Student(name, age, price) {
    Person.call(this,name,age)   
    this.price = price
    this.setScore = function () { }
}
Student.prototype = new Person()  
Student.prototype.constructor = Student  // 构建完整的构造函数指向原型

这里解释一下最后一步的操作:这句话的作用是保存好构造函数和原型间的链条,因此在前面重置了Student的原型,所以constructor也需要指向新的构造函数。

优点:

  • 可以多继承
  • 可以传参
  • 不会出现子类实例共享父类方法属性

缺点:

  • 调用了两次构造函数,一次在new Person,一次在call上。

优化(原型链 + 构造函数)

对于上面的缺点,我们可以进行一个优化,改变子类原型转变成父类实例,变成直接指向父类原型。

function Student(name, age, price) {
    Person.call(this, name, age)
    this.price = price
    this.setScore = function () { }
}
Student.prototype = Person.prototype
Student.prototype.sayHello = function () { }

优点:

  • 解决了两次初始化实例

缺点:

  • 无法区分创建的实例时来自子类还是父类,因为他们原型都一致。

再次优化(原型链 + 构造函数)

借助Object.create() 来创建对象,var B = Object.create(A) 以 A 为原型,生成了B对象。

function Student(name, age, price) {
    Person.call(this, name, age)
    this.price = price
    this.setScore = function () {}
}
Student.prototype = Object.create(Person.prototype) //核心代码
Student.prototype.constructor = Student //核心代码

无暇的方案,子类继承了父类全部的属性和方法,最完美的形态诞生了!

ES6中的class继承

ES6中引入了class,class 可以借助 extends 关键字来继承。

虽然引入了class,但是本质上class是语法糖,实际上还是基于原型实现的。

class Person {
    //调用类的构造方法
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    //定义一般的方法
    showName() {
        console.log("调用父类的方法")
        console.log(this.name, this.age);
    }
}
let p1 = new  Person('kobe', 39)
console.log(p1)
//定义一个子类
class Student extends Person {
    constructor(name, age, salary) {
        super(name, age)//通过super调用父类的构造方法
        this.salary = salary
    }
    showName() {//在子类自身定义方法
        console.log("调用子类的方法")
        console.log(this.name, this.age, this.salary);
    }
}
let s1 = new Student('wade', 38, 1000000000)
console.log(s1)
s1.showName()

ES5继承和ES6继承方式的区别:

ES5 是先创建出子类的this,再把父类的属性方法挂载到子类的this上。

而ES6正好反过来,先把父类的属性和方法挂载到子类this上(如在class必须向调用super) , 再用子类的构造函数去修改this。

总结

最后来个总结吧,在做项目的时候发现是时候该总结一下目前学的内容了,所以在此记录下这篇博客。这篇文章详细介绍了原型/继承,这个知识点算是Javascript的基础内功了,在实际开发过程中也会经常遇到,也是面试中非常常考的知识点,如果觉得不错来个点个👍吧。
在这里插入图片描述

参考链接:
https://github.com/mqyqingfeng/Blog/issues/11
https://github.com/mqyqingfeng/Blog/issues/2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/13520.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql 中的锁

文章目录锁的分类锁粒度InnoDB 中的表锁InnoDB 中的行锁锁的分类 共享锁&#xff08;Shared Locks&#xff09; // 行共享锁 // 获取到当前记录的S锁&#xff08;共享锁&#xff09;&#xff0c;兼容其他事务的S锁&#xff0c;不兼容X锁&#xff08;排他锁&#xff09; select…

服务注册中心

什么是注册中心&#xff1f; 注册中心主要有三种角色&#xff1a; ● 服务提供者&#xff08;Provider&#xff09;&#xff1a;在启动时&#xff0c;向 Registry 注册自身服务&#xff0c;并向 Registry 定期发送心跳汇报存活状态。 ● 服务消费者&#xff08;Consumer&#…

HCSC: Hierarchical Contrastive Selective Coding

原型对比学习&#xff1a;图像表征与聚类中心之间的交互&#xff0c;可以简单总结为在表征空间中最大化图像特征与其所属的聚类中心的相似度。分层语义结构 自然存在于图像数据集中&#xff0c;其中几个语义相关的图像簇可以进一步集成到一个更大的簇中&#xff0c;具有更粗粒度…

Tomcat多实例部署

文章目录一、Tomcat多实例的操作步骤1、关闭防火墙&#xff0c;将安装 Tomcat 所需软件包传到/opt目录下2、安装JDK3、安装 tomcat4、配置 tomcat 环境变量5、修改 tomcat2 中的 server.xml 文件&#xff0c;要求各 tomcat 实例配置不能有重复的端口号6、修改各 tomcat 实例中的…

openpnp软件的使用 - 配置自动电动飞达

文章目录openpnp软件的使用 - 配置自动电动飞达概述笔记新建执行器(电动飞达类型)新建电动飞达的料站配置飞达的x,y位置配置飞达移动到料表面时的高度测试这个Z高度, 是否能让吸嘴取得元件?设置元件料封装使用的吸嘴.试试开始贴片贴片后的元件位置目测备注ENDopenpnp软件的使用…

实操小微风控报告中的地址信息的清洗与照面和司法数据使用

在中小微企业的大数据风控体系中&#xff0c;工商数据与司法数据是最基本也是最常见的两类信息维度&#xff0c;在企业大数据体系的应用场景中扮演着重要角色。由于企业工商与司法数据的多部分内容属于社会公开化信息&#xff0c;因此在行业市场内也是非常容易获取的&#xff0…

详解:看似遥不可及的元宇宙

导语:元宇宙是人们娱乐、生活乃至工作的虚拟时空。Roblox 这款游戏,展示了元宇宙的诸多特征。核心是数字创造、数字资产、数字交易、数字货币和数字消费,尤其是在用户体验方面,达到了真假难辨、虚实混同的境界。 大约再过15 年,互联网就可能会发生一次重大的变革。正如从…

技术 | 终端安全 | 服务器并不像您想象的那么安全

在从1到10的评分中&#xff0c;现状方法对服务器安全的有效性如何&#xff1f; 从理论上讲&#xff0c;应该是10分。保护服务器免受外界影响的途径(分段、防火墙、漏洞修补、安全解决方案等)是众所周知的。 然而&#xff0c;现实生活的结果显示出与理论的巨大差距。从红十字会…

【前端】Ajax-form表单与模板引擎

目录 一、form表单的基本使用 1.1什么是表单 1.2表单的组成部分 1.3form标签属性 1.4表单的同步提交及缺点 1.4.1什么是表单的同步提交 1.4.2表单同步提交的缺点 1.4.3如何解决表单同步提交的缺点 二、通过Ajax提交表单数据 2.1监听表单提交事件 2.2阻止表单默认提交…

(超级详细1秒钟秒懂)华为网络初级工程师知识总结(一)

文章目录一&#xff0c;人机交互的工作模式二&#xff0c;OSI参考模型---OSI/RM三&#xff0c;常见的网络协议端口号四&#xff0c;网络层的地址查询&#xff0c;转发。五&#xff0c;ARP协议的转发原理六&#xff0c;TCP/IP协议的封装和解封装及跨层封装一&#xff0c;人机交互…

预编码ZF,MMSE,THP准则线性预编码误码率仿真

目录 1.算法概述 2.仿真效果预览 3.核心MATLAB代码预览 4.完整MATLAB程序 1.算法概述 恒定包络( Constant Enve-lope&#xff0c;CE) 预编码&#xff1b; 该算法规定&#xff0c;每根天线上的发射功率被限定为一个与信道条件和信号符号均无关的常数&#xff0c;各根天线均…

Nacos下载和安装步骤

1. 下载安装包 1.1. Nacos官网 :https://nacos.io/zh-cn/hub 打开官网&#xff0c;点击前往Github 1. Nacos官网 1.2. 打开Nacos Github主页&#xff0c;点击Release&#xff0c;点击tags&#xff0c;可以看到所有的版本&#xff0c;选择自己需要的版本下载 Nacos Github主页 …

线性代数 --- 投影Projection 四(投影有什么用?Why projection)

笔者在本系列的开篇就说过&#xff0c;我在学习投影的过程中&#xff0c;有很长的一段时间都是把重点放在了&#xff0c;如何计算投影本身&#xff0c;也就是背公式。 现在我发现&#xff08;尤其是明白了投影即分量之后&#xff09;&#xff0c;学习投影的主要目的&#xff0c…

IB数学AA/AI应该如何选择?IB数学AA HL有多难?

IB课程即国际文凭组织IBO&#xff0c;是为全球学生开设从幼儿园到大学预科的课程&#xff0c;为3-19岁的学生提供智力、情感、个人发展、社会技能等方面的教育&#xff0c;使其获得学习&#xff0c;工作以及生存于世的各项能力。 IB课程难在哪&#xff1f; IB课程不像AP、A-lev…

读书笔记-学习GNU Emacs-1

学习本书目的&#xff1a; emacs的学习一直是陆陆续续看博客和上手实践&#xff0c;这次想通过阅读"学习GNU Emacs"这本书好好系统的再复习下emacs。 ps:读技术书应该是带着一定的目的去读的&#xff0c;最简单的目的可能就是为了学好某一项技术或者复习下某一项技术…

基于Java+JSP+MySQL共享单车管理系统的设计与实现-计算机毕业设计

项目介绍 随着时代的发展&#xff0c;我国的国民经济一直在稳步的提升&#xff0c;共享单车的是用来一直在不断的攀升&#xff0c;为了能够更加方便快捷的管理共享单车&#xff0c;需要开发一套利用计算机进行管理的JSP共享单车管理系统。 本项目利用软件工程原理&#xff0c…

最新出炉!开源 API 网关的性能对比:APISIX 3.0 和 Kong 3.0

背景 云原生时代下&#xff0c;企业逐渐向云上迁移&#xff0c;越来越多的应用和服务都在进行容器化改造&#xff0c;服务之间的流量也开始爆发性的增长。为了能高效地管理这些规模庞大的 API&#xff0c;API 网关开始在技术领域大展身手。 用户除了需要 API 网关提供请求代理…

springcloud集成Seata AT 模式

注意&#xff1a; 1.seata版本1.4.1 2.使用db配置&#xff0c;mysql 3.nacos版本2.2.3.RELEASE 4.spring-boot-starter-parent版本2.3.1.RELEASE 版本匹配很关键&#xff0c;否则报奇奇怪怪的错&#xff01;&#xff01;&#xff01;&#xff01; seata库必要的表 -- -------…

微服务拆分技巧

微服务架构整体思路 常见场景实施建议 拆分方式基础设施要求服务拆分落地方式从0开始构建业务系统按业务拆分微服务搭建完善基础设施&#xff0c;按照微服务基础设施优先级逐步落地一步到位单体架构微服务化按业务拆分微服务&#xff0c;先从非核心业务开始拆分搭建完善基础设…

【python初学者日记】用PIL批量给HEIC格式的照片,添加拍摄日期、拍摄地点的水印戳

【python初学者日记】用PIL批量给HEIC格式的照片&#xff0c;添加拍摄日期、拍摄地点的水印戳问题合集1、读取 HEIC 格式照片的拍摄信息2、将已知坐标转码成具体省市地址的文字信息3、将文字添加到HEIC格式的照片上问题解决一、问题分析二、代码实现最近在整理手机相册&#xf…