javascript(第三篇)原型、原型链、继承问题,使用 es5、es6实现继承,一网打尽所有面试题

没错这是一道【去哪儿】的面试题目,手写一个 es5 的继承,我又没有回答上来,很惭愧,我就只知道 es5 中可以使用原型链实现继承,但是代码一行也写不出来。

关于 js 的继承,是在面试中除了【 this 指针、命名提升、事件循环】之外的又一个重要的题目,而且很容易忽视。

  1. this 指针
  2. 命名提升
  3. 事件循环

这一部分内容,还是建议看一遍《你不知道的javascript 》上这本书,看完了你就会发现,你确实是不知道。

 一、继承的概念

先明确继承的概念,继承主要是针对类的,重要的事情说三遍,继承指的是类的继承,子类继承父类的属性和方法,这个时候和对象是没有关系的。

注意,写继承是针对类,有了继承才有子类、父类这一说

在 es6 中可以使用 class + extends 关键字事件继承,但是问题是 es5 中没有 class 关键字,所以我们就使用函数来实现!

在 JavaScript 中,继承是一种机制,它允许一个对象获取另一个对象的属性和方法。这意味着一个对象可以使用另一个对象的特性,而不必重新定义这些特性

上面的定义虽然说【它允许一个对象获取另一个对象的属性和方法】但是我们的代码写的主要还是函数,并且使用这个函数来生成对象!!

es5 中的继承方法主要有四种,分别是【原型链继承、构造函数继承、组合式继承、寄生式继承】

这些名字挺能忽悠人的,尤其是最后一个!

别看一共四种继承方式,但是在文章最后我们只需要记住一套代码就行,一定要认真看完。

关于原型链,其实还有很多知识点,我们往往会被一些概念弄混,比如 prototype、constructor 等,但是我在看完《你不知道的 javascript 上》第五章的内容之后,就豁然开朗了,所以本篇文章还是先总结一下关于原型的知识点,然后再总结各种继承方式吧。

二、原型链的基本知识

2.1 对象的内置属性 [[Prototype]]

js 每一个对象都有一个内置属性 [[Prototype]],js 的对象还有其他的内置属性比如 [[class]],之所以是内置属性,意味着我们不能直接通过属性访问点操作符访问,但是我们可以使用其他的方法访问。

比如,对于内置属性 [[Prototype]] 可以使用 Object.getPrototypeOf(obj) 来获取。也可以使用obj.__proto__ 获取,但是已经弃用,已经被  Object.getPrototypeOf(obj) 取代

obj.__proto__ 已弃用

对于内置属性 [[class]] 可以使用 Object.prototype.toString.call(obj) 来获取

注意,这个内置属性是针对对象的,每个对象都有这个内置属性,也可以简单的说【每个对象都有原型】而原型对象又有原型,所以每个对象都有原型链。

注意,js 中所有的变量都是对象,这意味着函数也是对象,所以函数也有一个内置属性[[Prototype]],也可以使用 Object.getPrototypeOf(fn) 来获取,函数的内置属性指向Function.prototype,箭头函数也有内置属性[[Prototype]],因为箭头函数本质也是一个对象。

Object.getPrototypeOf(Array) === Function.prototype // true

2.2 函数的原型 prototype

函数有一个公开可访问不可枚举属性 prototype,指向一个对象,也称之为函数的原型对象。注意三个关键词【公开】【可访问】【不可枚举】

注意,箭头函数没有 prototype 属性!!!这也是箭头函数不能当作构造函数的原因之一!!参考这篇文章

记住,所有的函数(除了箭头函数)都有一个公开可访问的不可枚举的属性 prototype,这意味着可以直接使用 fn.prototype 来获取,这一点和2.1 中说的对象是不同的,对象是不可直接访问的内置属性,函数是可以访问的公开属性。

所以有一个对象和一个函数,你要判断的只能是【函数的 prototype 属性是否在对象的原型链上】

// 有一个函数
function fn() {}
// 有一个对象
let a = new fn()// 判断对象是否在函数的原型链上
Object.getPrototypeOf(a) === fn.prototype // true

2.3 函数的prototype属性的公开可访问不可枚举属性 constructor 

对象有一个公开不可枚举属性 constructor ,翻译过来就是构造函数,注意这个 constructor 是针对对象的,而不是函数的。

函数的 prototype 属性也是一个对象,并且, fn.prototype.constructor = fn

其实,对象本身并没有 .constructor 属性,对象调用 .constructor 的本质是在对象的原型链上找的。再实现继承代码的时候前往别忘了这个 constructor 属性

function fn() {}let a = new fn()fn.prototype.constructor === fn // true 
a.constructor === fn.prototype.constructor // true
a.constructor === fn // true

详细内容还是自己看书吧,书上非常详细!

总结

总之关于原型这块记住三句话

  1. 对象有一个内置属性 [[Prototype]],使用 Object.getPrototypeOf(obj) 获取
  2. 函数有一个公开可访问不可枚举属性 prototype
  3. 函数的 prototype 属性有一个公开可访问的不可枚举属性 constructor,指向函数本身

2.4 原型相关的面试题目

2.4.1 说说你对原型和原型链的理解

回答问题分文两步

(1)原型/原型链是什么?【引用上面的三句话即可】

在 js 中每个对象都有一个内置属性 [[prototype]],可以使用 Object.getPrototypeOf 来获取,指向一个对象;同样的,这个指向的对象也有内置属性[[prototype]] 这样就构成了原型链,原型链最终会指向 Object.prototype,而 Object.prototype 的内置属性 [[prototype]] 指向 null.

同时函数都有一个公开可访问属性 prototype,这个 prototype 属性又有一个 constructor 属性指向函数本身。

(2)原型链有什么用?【属性查找、继承、扩展、属性和方法的共享】

当访问对象的一个属性的时候,如果自身没有找到,就会去原型链上查找,直到找到该属性,或者遍历完完整的原型链,也就是说可以使用原型链实现继承功能。对象可以通过原型链继承父对象的属性或者方法【继承】

也可以使用原型链对对象进行扩展,通过修改原型对象,可以给所有的实例进行属性的增加或修改。如果我们在一个对象的原型上添加属性或者方法,所有基于该原型的实例都会自动继承这些属性和方法,这样可以在不修改每个实例的情况下,实现对对象的扩展【扩展】【注意这一点也是原型链继承的弊端】【也是实例之间属性和方法的共享的方法】

题外话,for ... in 循环就会遍历到对象的原型链上的公开可访问可枚举属性!不能遍历不可枚举属性。

还要注意 for... in 和 for ...of 的区别。

2.4.2 如何获取一个对象的原型对象

(1)从构造函数获取,前提是知道对象的构造函数是谁

(2)使用 Object.getPrototypeOf(obj) 获取

(3)使用 Object.__proto__ 但是官方已经弃用,不建议用了

function fn() {//
}let a = new fn()console.log('a 的原型对象是', fn.prototype)
console.log('a 的原型对象是', Object.getPrototypeOf(a))
console.log('a 的原型对象是', a.__proto__) // 不建议

2.4.3 打印结果

关于原型的面试题,还有各种打印结果的,而且往往和 this 指针、命名提升掺合在一起,所以基础一定要扎实。

随便看一道题目,可能就答不上来

var F = function() {};
Object.prototype.a = function() {console.log('a');
};
Function.prototype.b = function() {console.log('b');
}
var f = new F();
f.a();
f.b();
F.a();
F.b()

打印结果是【a、报错 f.b is not a function、 a、 b】 

  1. F 是函数,所有的函数都是 Function 的实例【箭头函数也是】
  2. 函数也是对象,所有对象都是 Object 的实例
  3. f 是对象,所以不会继承 Function,但是作为对象会继承Object

2.4.5 如何使用原型链实现继承,存在什么问题,怎么解决

这个就是本篇文章文章的重点了,不过别担心,最终我们只有一套代码需要记住,前面的都是铺垫!

三、使用原型链继承

3.1 代码实现

原型继承的属性和方法,所以我们在自定义实现的时候,最好是定一个属性,再定义一个方法。而且都会用到 this 指针。

首先要定一个一个子类(函数),一个父类(函数),实现子类继承父类,也就是子类创建的方法可以拥有父类的属性,那么步骤很简单:

  1. 定义一个函数作为父类 Person,并定义一个 name 属性【使用 this 指针】
  2. 给父类原型上加一个方法 getName【使用函数的 prototype 属性 + this 指针】
  3. 定义一个函数作为子类 Student,定一个 gender 属性 【使用 this 指针】
  4. 子类 Student 通过原型继承 Person【使用函数的 prototype 属性 + new 操作符】
  5. 处理子类 Student.prototype 的 constructor,指向 Student
  6. 使用子类创建一个对象 student【使用 new 操作符】
  7. 访问 student.name 和 student.getName
  8. 完成子类 Student 对父类 Person 的属性和方法的继承
function Person() {this.name = 'mike';
}
Person.prototype.getName = function() {return this.name;
}
function Student(gender) {this.gender = gender
}
Student.prototype = new Person();
Student.prototype.constructor = Student;const student = new Student('man');console.log(student.gender);  // 子类自己的属性
console.log(student.name); // 继承父类的属性
console.log(student.getName());  // 继承父类的方法

3.2 存在的问题

面试官肯定会问你这个问题,使用原型继承存在是什么问题?然后再引出怎么解决问题,再引出 es6 中的 class 的继承。

3.2.1 引用类型属性共享问题

原型链继承存在的问题就是,多个子类的实例,指向同一个父类的实例,所以对于父类的引用类型,修改一个子类的实例会影响到其他的实例!【这个问题是可以解决的,具体看第四章】

3.2.2 原型链上所有的属性和方法都是共享的

在原型链中,子类实例共享父类原型对象上的属性和方法。这意味着,如果一个子类实例修改了原型对象上的属性或方法,那么其他所有子类实例也会受到影响,可能会导致意外的副作用。

3.2.3 子类向父类传参需要手动调用父类构造函数

除非我们手动显式的使用 call/apply 方法调用父类的构造函数,否则无法给父类构造函数传递参数。所以传递参数这个问题也是可以解决的,具体看第四章。

3.2.4 无法实现多重继承

一个子类只能继承一个父类,无法实现多重继承

3.2.4 破坏封装性

原型链继承会导致父类的内部属性和方法暴露给子类,从而破坏了封装性。子类可以直接访问父类原型对象上的属性和方法,无法实现严格的控制访问权限。

3.3 总结

使用原型继承,是 es5 中实现继承的必须要学会的,同时还要记住原型继承存在的问题!这个时候就有一个新的问题了,就是如何使用 es5 中的知识解决这些问题。

答案是将四种继承方式组合起来,取各自的优点。不过在此之前我们还是先看看其他的继称方式。

四、构造函数继承

4.1 代码实现

利用 this 指针的显示绑定方法 call 和 apply ,在子类中调用父类构造函数,把父类的成员属性和方法都挂在到子类的 this上。这个方法解决了 3.2.1 和 3.2.3 中的问题。具体代码如下:

function Person(age) {this.name = 'mike';this.age = {num: age}
}Person.prototype.getName = function() {return this.name;
}
function Student(gender, age) {// 手动调用父类的构造函数Person.call(this, age)this.gender = gender
}const student = new Student('man', 12);
const student1 = new Student('women', 25)// 修改第一个实例
student.age.num = 3
console.log('第一个学生', student.age)
console.log('第二个学生', student1.age)

4.2 存在的问题

因为我们没有写下面原型继承的那两句话,所以就无法继承来自父类原型上的属性和方法。

// 构造函数继承没有这两句话
Student.prototype = new Person()
Student.prototype.constructor = Student;

其实我们要继承原型上的属性和方法,写上就行了呗,但是呢,很多教程中都是这样写的,把构造函数继承和原项链继承分开,然后再引出后面的组合继承,那我也就这么弄吧。

五、组合继承

5.1 代码实现

就是把原型链继承和构造函数继承的优点组合起来,完整代码如下。

function Person(age) {this.name = 'mike';this.age = {num: age}
}Person.prototype.getName = function() {return this.name;
}
function Student(gender, age) {// 手动调用父类的构造函数// 构造函数继承Person.call(this, age)this.gender = gender
}// 原型链继承
Student.prototype = new Person()
Student.prototype.constructor = Student;const student = new Student('man', 12);
const student1 = new Student('women', 25)// 修改第一个实例
student.age.num = 3
console.log('第一个学生', student.age, student.getName())
console.log('第二个学生', student1.age, student1.getName())

5.2 存在的问题

每次创建子类实例都执行了两次构造函数 Person.call 和 new Person() ,虽然不影响功能,但是每次创建子类实例,实例的原型中都有两份相同的属性和方法。

这是可以 Object.create 优化的,这就迎来了 es5 继承的最终极版代码。需要有感情的朗读并背诵全文!!

六、寄生式组合继承【必会】

我不喜欢这个名字,因为他听起来很高端的样子,还不如叫 es5 继承终极版!

很简单,把 new Person() 换成 Object.create(Person.prototype)就行了。

function Person(age) {this.name = 'mike';this.age = {num: age}
}Person.prototype.getName = function() {return this.name;
}
function Student(gender, age) {// 重点1Person.call(this, age)this.gender = gender
}// 重点2
Student.prototype = Object.create(Person.prototype)
// 重点3
Student.prototype.constructor = Student;const student = new Student('man', 12);
const student1 = new Student('women', 25)
console.log(Object.getPrototypeOf(student))// 修改第一个实例
student.age.num = 3
console.log('第一个学生', student.age, student.getName())
console.log('第二个学生', student1.age, student1.getName())

这里面其实应用到了,Object.create 的原理,这也是一个面试题目,而且也有可能让你手写一个 Object.create 请看这篇文章。

小结

好吧,整半天就一套代码,如果面试官让你写 es5 的继承,你直接上来就终极版代码安排,我想他应该没有什么可问的了吧,所以你别看概念上那么继承方式那么多,但是实际应用就是一个!一定要记住,可别再翻车了。

那么还有最后一个问题就是 es6 中的继承了!

七、es6 继承

7.1 代码实现

使用类 class + extends 实现继承。主要还是学会使用class 类的各种语法,有几个关键点

  1. class 中只能有一个构造函数 constructor
  2. 可以使用 static 定义静态属性和方法,直接使用类名调用
  3. 子类使用 extends 关键字继承父类,且只能继承一个【说明 es6 原生也不支持多重继承】
  4. 子类在构造函数 constructor 中使用 super 来调用父类的构造函数,并且可以传递参数
  5. 子类中的方法和父类的同名,会覆盖父类的方法
  6. 必须使用 new 操作符,创建 class 示例
class Person {// 定义属性lang = 'zh'// 定义静态属性static nation = 'china'// 构造函数constructor(age) {this.name = 'mike'this.age = {num: age}}// 定义方法getName() {return this.name}// 定义静态方法static getDes () {return 'hello word'}
}class Student extends Person {constructor(gender, age) {super(age)this.gender = gender}
}
const student = new Student('man', 12)
const student1 = new Student('women', 25)
student.age.num = 234console.log('静态属性方法',Person.nation, Person.getDes())
console.log('第一个学生', student.lang, student.getName())
console.log('第二个学生', student1, student.getName())

7.2 面试题目

这个时候肯定会问 es5 中的类和 es6 中的类的区别了,用自己的话总结一些这篇文章的内容即可。

7.2.1 es5 中类 es6 中的继承有什么区别

注意 es6 的class 有一个私有属性和方法,以#开头的,这个倒是不常用。

7.2.2 ts 中的类和 es6 中的类有什么区别

  1. ts 中有类型检查
  2. ts 有访问描述符 private 、public 、protected 等,js 中只有 #开头描述的私有属性
  3. ts 中有抽象类和方法的概念
    1. 抽象类可以包含抽象方法,而接口只能定义方法的签名
  4. ts 支持范型

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

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

相关文章

如何配置googleplay谷歌后台的Auth登陆和支付权限

相信很多谷歌开发者在谷歌平台发布过app产品,如果你接入过登陆和支付,那么你对下面的后台配置步骤以及服务器如何使用这些参数来进行校验并不陌生,这篇文章我将分享给大家关于如何在后台配置你上架应用的登陆权限和支付权限,服务器端如何使用相应的参数来做验证。 配置谷歌…

动态创建运行时Java Bean

基于Java字节码技术,如ASM、Javassist,前者偏底层、构建复杂,但性能相对较高;后者提供了友好的API接口方法,优雅简单,但性能稍弱。 本文基于Javassist,初探性以创建一个简单的类、里面创建一个…

利用Spring中的SchedulingConfigurer实现数据库配置化定时任务

目录 1.利用Scheduled来实现传统的定时任务 2.两者的区别 3.Spring中的SchedulingConfigurer来拓展定时任务的灵活性 1)UrTaskConfig 2)TaskMain 3)BaseTask 4)效果 (1)插入配置定时任务的sql语句 …

【webrtc】Chrome和Firefox在SDP协商过程中,针对localhost的不同处理

内网下chrome端webrtc协商失败 现象 我有一个webrtc服务器在局域网内,使用chrome浏览器访问时,发现webrtc在做媒体协商时失败。 具体表现是,在交换sdp后,ice的状态是oniceconnectionstatechange: failed 但是换成Firefox浏览器…

广东理工学院携手泰迪智能科技成功部署人工智能实验室

广东理工学院是经国家教育部批准设立的全日制普通本科院校,入选全国应用型人才培养工程培养基地、国家级众创空间试点单位、广东省高校电子商务人才孵化基地。开设34个本科专业,涵盖工学、经济学、管理学、文学、艺术学、教育学等6大学科门类&#xff0c…

docker容器技术篇:容器集群管理实战mesos+zookeeper+marathon(一)

容器集群管理实战mesoszookeepermarathon(一) mesos概述 1.1 Mesos是什么 Apache Mesos 是一个基于多资源调度的集群管理软件,提供了有效的、跨分布式应用或框架的资源隔离和共享,可以运行 Hadoop、Spark以及docker等。 1.2 为…

自然语言处理: 第二十八章大模型基底之llama3

项目地址: meta-llama/llama3: The official Meta Llama 3 GitHub site 前言 LLaMa系列一直是人们关注的焦点,Meta在4月18日发布了其最新大型语言模型 LLaMA 3。该模型将被集成到其虚拟助手Meta AI中。Meta自称8B和70B的LLaMA 3是当今 8B 和 70B 参数规模的最佳模…

npm install 卡在still idealTree buildDeps不动

前言 再使用npm install 安装包依赖时 发现一直卡住 停留在 观察node_cache下的_logs文件 发现一直在拉取包 37 silly idealTree buildDeps 38 silly fetch manifest riophae/vue-treeselect0.4.0尝试解决 尝试设置了taobao镜像源 依然如此 获取已经设置的镜像源 确实是ta…

图像哈希:全局+局部提取特征

文章信息 作者:梁小平,唐振军期刊:ACM Trans. Multimedia Comput. Commun. Appl(三区)题目:Robust Hashing via Global and Local Invariant Features for Image Copy Detection 目的、实验步骤及结论 目…

学习Rust的第10天:枚举和模式匹配

今天我们来看看一个类似的概念 enums 。 Enums: We saw that in Rust, enums are data types that list possible values, giving a simple and type-safe mechanism to describe alternatives. We looked at how to create enums and use them to represent similar possibili…

webpack中mode、NODE_ENV、DefinePlugin、cross-env的使用

本文讲的全部知识点,都是和webpack相关的。如果你之前有疑问,那本文一定能帮你搞清楚。 问题来源一般是类似下面代码(webpack.json中): "scripts": {"dev": "cross-env NODE_ENVdevelopmen…

opencv android 使用笔记

目录 获取app路径: 下载:OpenCV-android-sdk cmakelist配置: 头文件路径: 编译报错:clang: error: linker command failed with exit code 1 (use -v to see invocation) 读取图片例子 保存mp4 获取app路径&am…

自定义一个RedisTemplate

1.引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>redis.clients</groupId><artifactId>jedis&…

springcloud Ribbon的详解

1、Ribbon是什么 Ribbon是Netflix发布的开源项目&#xff0c;Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的框架。 2、Ribbon能干什么 LB负载均衡(Load Balance)是什么&#xff1f;简单的说就是将用户的请求平摊的分配到多个服务上&#xff0c;从而达…

<前端>Electron-builder为公证后的app打更新信息latest.yml

MacOS下&#xff0c;Electron-builder可以很方便的为测试包app打更新信息&#xff08;latest-mac.yml&#xff09;。 但是&#xff0c;正式发布的时候&#xff0c;不可能用测试包app&#xff0c;因为还没有进行公证。如何为公证的app打latest-mac.yml呢。 其实观察latest-mac.y…

Keil和VSCode协同开发STM32程序

系列文章 STM32单片机系列专栏 C语言术语和结构总结专栏 文章目录 1. 配置环境 2. 测试打开工程 3. 测试编译工程 随着项目的复杂度上升&#xff0c;开发者不仅需要强大的硬件支持&#xff0c;还需要一个高效和灵活的开发环境。 vscode是一款集成大量可以便携开发插件的代码…

C++中的list类模拟实现

目录 list类模拟实现 list类节点结构设计 list类非const迭代器结构设计 迭代器基本结构设计 迭代器构造函数 operator()函数 operator*()函数 operator!()函数 operator(int)函数 operator--()函数 operator--(int)函数 operator()函数 operator->()函数 list…

TiDB 6.x 新特性解读 | Collation 规则

对数据库而言&#xff0c;合适的字符集和 collation 规则能够大大提升使用者运维和分析的效率。TiDB 从 v4.0 开始支持新 collation 规则&#xff0c;并于 TiDB 6.0 版本进行了更新。本文将深入解读 Collation 规则在 TiDB 6.0 中的变更和应用。 引 这里的“引”&#xff0c;…

Modbus转Profinet网关接称重设备与工控机通讯

Modbus转Profinet网关&#xff08;XD-MDPN100&#xff09;是一种能够实现Modbus协议和Profinet协议之间转换的设备。Modbus转Profinet网关可提供单个或多个RS485接口&#xff0c;使得不同设备之间可以顺利进行通信&#xff0c;进一步提升了工业自动化程度。 通过使用Modbus转Pr…

相亲平台app小程序

相亲平台app小程序是一种基于手机应用的微型程序&#xff0c;专为在线相亲交友活动设计。它提供了一系列的功能&#xff0c;旨在帮助用户更方便、更高效地找到心仪的伴侣。 首先&#xff0c;用户可以在个人资料部分上传照片、填写个人资料、设置兴趣爱好等信息&#xff0c;以便…