学会这5种JS函数继承方式,前端面试你至少成功50%
摘要:函数继承是在JS里比较基础也是比较重要的一部分,而且也是面试中常常要问到的。下面带你快速了解JS中有哪几种是经常出现且必须掌握的继承方式。掌握下面的内容面试也差不多没问题啦~
本文分享自华为云社区《人类高质量JS函数继承》,作者:北极光之夜。
一. 前言:
函数继承是在JS里比较基础也是比较重要的一部分,而且也是面试中常常要问到的。下面带你快速了解JS中有哪几种是经常出现且必须掌握的继承方式。掌握下面的内容面试也差不多没问题啦~
二.原型链继承:
原型链继承的要点在于父类的实例作为子类的原型。直接看下面这个例子:
// 父函数 Person
function Person(name, age) {
// 定义一些属性
this.name = name;
this.age = age;
this.nature = ["auroras", "wind", "moon"];
}
// 定义Person原型上的一个方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
}; // 子函数 Jack
function Jack() {}
// 父类的实例作为子类的原型 (-------------------实现核心--------------------------)
Jack.prototype = new Person();
现在我们创建两个Jack 的实例,测试看是否实现了继承Person:
var jack1 = new Jack();
var jack2 = new Jack();
jack2.nature[0] = "sea";
jack1.sayLove();
jack2.sayLove();
console.log(jack1.nature);
console.log(jack2.nature);
看运行结果确实继承了,能执行sayLove方法。但有甚多缺点,创建Jack实例的时候传递不了参数name和age,而且不同实例间nature引用类型属性相互影响,一个改变那都改变:

三.借用构造函数继承(对象伪装):
核心在于“盗用构造函数”(constructor stealing)。在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和call()方法以新创建的对象为上下文执行构造函数。它能解决原型链继承中传参数和引用类型属性冲突。还是直接看例子:
// 父函数 Person
function Person(name, age) {
// 定义一些属性
this.name = name;
this.age = age;
this.nature = ["auroras", "wind", "moon"];
}
// 定义Person原型上的一个方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
}; // 子函数 Lucy
function Lucy(name, age) {
// 通过call把this指向Lucy,相当于拷贝了一份父函数 Person 里的内容(---------实现核心--------------)
Person.call(this, name, age);
}
//给子函数原型上也定义一个方法
Lucy.prototype.syaName = function () {
console.log("My name is " + this.name);
};
现在我们创建两个Lucy 的实例,测试看是否实现了继承Person:
var lucy1 = new Lucy("lucy1", "20");
var lucy2 = new Lucy("lucy2", "22");
lucy2.nature[0] = "sea";
console.log(lucy1.name);
console.log(lucy1.nature);
console.log(lucy2.nature);
lucy1.syaName();
lucy2.syaName();
lucy1.sayLove();
结果看可以继承了,能传参数,引用类型属性也不互相影响,但是缺点显而易见,可以看到报错,无法使用父类的原型上的方法sayLove。

四.组合继承:
组合继承就是结合了原型链继承和借用构造函数继承两者的核心实现的一种继承方法,既能传递参数,引用类型属性也互不影响,同时子类也能获取得到父类的方法。这也是目前比较常用的继承方式。直接看例子:
// 父函数 Person
function Person(name, age) {
// 定义一些属性
this.name = name;
this.age = age;
this.nature = ["auroras", "wind", "moon"];
}
// 定义Person原型上的一个方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
}; // 子函数Lisa
function Lisa(name, age) {
// 通过call把this指向Lisa,相当于拷贝了一份父函数 Person 里的内容(------实现核心-----------)
Person.call(this, name, age);
}
// 父类的实例作为子类的原型 (--------------实现核心-------------------)
Lisa.prototype = new Person();
//小知识点,这里是让Lisa的constructor重新指向Lisa,不然因为Lisa的原型为Person实例,constructor会指向Person
Lisa.prototype.constructor = Lisa;
现在我们创建两个Lisa 的实例,测试看是否实现了继承Person:
var lisa1 = new Lisa("lisa1", "20");
var lisa2 = new Lisa("lisa2", "21");
lisa2.nature[0] = "sea";
console.log(lisa1.name);
console.log(lisa1.nature);
console.log(lisa2.nature);
lisa1.sayLove();
lisa2.sayLove();
可以看到基本上实现了我们继承的功能。也修补了原型链和借用构造函数继承的缺点。但是呢,它还是有一个小缺点,就是可以看到在代码注释实现核心那,两次都调用了Person,那么Lisa原型上和实例上有了两份相同的属性,那就会多少有一些性能浪费。

五.寄生组合继承:
其实寄生组合继承和组合继承差不多的,就是多了一个解决组合继承上原型和实例产生两份相同属性的缺点。解决核心是我们既然只是想要子类原型赋值为父类原型,那没必要new一个父类实例。直接创造一个新对象,它值为父类的原型,再将它赋值给子类原型就行了。
其中用到Object.create(proto,[propertiesObject])这个方法创建一个新对象。相当于新对象的__proto__为其参数proto。当然Object.create可能低版本ie没有,所以下面也自定义封装了Object.create方法,当然只是简单封装。直接看例子:
// 父函数 Person
function Person(name, age) {
// 定义一些属性
this.name = name;
this.age = age;
this.nature = ["auroras", "wind", "moon"];
}
// 定义Person原型上的一个方法
Person.prototype.sayLove = function () {
console.log(this.name + " like " + this.nature[0]);
}; // 子函数 Andy
function Andy(name, age) {
Person.call(this, name, age);
}
// 如果没有 Object.create()方法,简单封装下
if (!Object.create) {
Object.create = function (proto) {
function Temp() {}
Temp.prototype = proto;
return new Temp();
};
}
// 调用Object.create方法,新建一对像,其__proto__为Person.prototype,并赋值给 Andy.prototype (-------实现核心----------)
Andy.prototype = Object.create(Person.prototype);
//修改constructor指向
Andy.prototype.constructor = Andy;
现在我们创建两个Andy的实例,测试看是否实现了继承Person:
console.log(Andy.prototype.__proto__ === Person.prototype);
var andy1 = new Andy("andy1", "20");
var andy2 = new Andy("andy2", "21");
andy2.nature[0] = "sea";
console.log(andy1.name);
console.log(andy1.nature);
console.log(andy2.nature);
andy1.sayLove();
andy2.sayLove();
完美运行:

六.class继承:
ES6出了class语法糖之后,就可以通过class定义类并实现类的继承。直接看例子:
//定义一个父类 Animal
class Animal {
//这里constructor指向类本身,跟es5行为一样的
constructor(name) {
this.name = name;
}
likeEat() {
console.log(this.name + " like eat " + this.food);
}
}
//定义一个子类 Dog ,通过 extends 继承父类Animal
class Dog extends Animal {
constructor(name, food) {
//通过super(属性名)继承父类属性
super(name);
this.food = food;
}
likeEat() {
//通过super.+父类方法 实现继承父类方法
super.likeEat();
}
}
new一个Dog实例,测试看看,Dog是否继承了Animal:
var jinmao = new Dog("jinmao", "bone");
console.log(jinmao.name);
jinmao.likeEat();
可以看到完美实现了:

七.总结:

上面就是这篇文章的全部内容啦,如果有错误的地方恳请指出~
学会这5种JS函数继承方式,前端面试你至少成功50%的更多相关文章
- js的5种继承方式——前端面试
js主要有以下几种继承方式:对象冒充,call()方法,apply()方法,原型链继承以及混合方式.下面就每种方法就代码讲解具体的继承是怎么实现的. 1.继承第一种方式:对象冒充 function P ...
- js各种继承方式和优缺点的介绍
js各种继承方式和优缺点的介绍 作者: default 参考网址2 写在前面 本文讲解JavaScript各种继承方式和优缺点. 注意: 跟<JavaScript深入之创建对象>一样,更像 ...
- js 中继承方式小谈
题外话 前段时间面试中笔试题有这道题目: 请实现一个继承链,要求如下: 构造函数A():构造函数中有consoleA方法,可以实现console.log("a") 实例对象 a:a ...
- Javascript学习笔记:3种定义函数的方式
①使用函数声明语法定义函数 function sum(num1,num2){ return num1+num2; } ②使用函数表达式定义函数 var sum=function(num1,num2){ ...
- js函数验证方式:验证是否是数字,支持小数,负数
验证 datatype="/^\d+(\.\d+)?$/" validatform验证是否是数字 支持小数点 datatype="d" 貌似支持小数 js函数验 ...
- javascript两种声明函数的方式的一次深入解析
声明函数的方式 javascript有两种声明函数的方式,一个是函数表达式定义函数,也就是我们说的匿名函数方式,一个是函数语句定义函数,下面看代码: /*方式一*/ var FUNCTION_NAME ...
- JS中继承方式总结
说在前面:为了使代码更为简洁方便理解, 本文中的代码均将"非核心实现"部分的代码移出. 一.原型链方式关于原型链,可点击<深入浅出,JS原型链的工作原理>,本文不再重复 ...
- 【深入JavaScript】一种JS的继承方法
这些天读了John Resig的<Secrets of JavaScript Ninja>,其中讨论到JS中实现继承的方案,非常有趣,自己探索了一下,形成了笔记,放到这里. 这个方案在Re ...
- 漫谈JS 的继承方式
一.原型链原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指针.如果:我们让 ...
- js的继承方式分别适合哪些应用场景?
一.原型链 利用 Person.prototype = new Animal("Human") 实现继承: static式继承.能继承Animal.prototype.不可多重继承 ...
随机推荐
- c# 引入同一个版本dll(比如包含opencv不同模块的dll,但是版本却是一致的)
1.使用dnSpy.exe工具,打开相关dll 在左侧"管理器"中选中并点击右键 2.在弹出菜单中点击编辑程序集 3.更改名称和版本号 4.工具栏-文件-全部保存 5.在VS中引入 ...
- camerabin error:"Internal data stream error,使用QT打开MIPI摄像头
使用QT自带的QCamera打开MIPI摄像头 遇到:camerabin error:"Internal data stream error 降低分辨率为640*480 TRANSLATE ...
- CodeTON Round 4 (Div. 1 + Div. 2)C
C. Make It Permutation 我们希望尽可能少地进行操作可以使代价最小,我们如果要排列的话,那些重复的元素我们无论如何都要进行删除的,所以我们可以先把去重的代价计算出来,然后依次枚举排 ...
- P4870 [BalticOI 2009 Day1]甲虫 题解
题目链接 简要题意 在一个数轴上有 \(n\) 滴露水,每滴露水初始水量为 \(m\),每秒会蒸发一滴水,一个甲虫初始在原点,速度为 1,水能瞬间喝完,问它最多能喝到几滴水. 题目分析 对于这种移动区 ...
- Go 接口:nil接口为什么不等于nil?
Go 接口:nil接口为什么不等于nil? 本文主要内容:深入了解接口类型的运行时表示层. 目录 Go 接口:nil接口为什么不等于nil? 一.Go 接口的地位 二.接口的静态特性与动态特性 2.1 ...
- 2023-11-18:用go语言,如果一个正方形矩阵上下对称并且左右对称,对称的意思是互为镜像, 那么称这个正方形矩阵叫做神奇矩阵。 比如 : 1 5 5 1 6 3 3 6 6 3 3 6 1 5
2023-11-18:用go语言,如果一个正方形矩阵上下对称并且左右对称,对称的意思是互为镜像, 那么称这个正方形矩阵叫做神奇矩阵. 比如 : 1 5 5 1 6 3 3 6 6 3 3 6 1 5 ...
- 文心一言 VS 讯飞星火 VS chatgpt (141)-- 算法导论12.1 1题
一.用go语言,对于关键字集合(1,4,5,10,16,17,21),分别画出高度为 2.3.4.5和6的二又搜索树. 文心一言: 以下是用Go语言为给定的关键字集合(1, 4, 5, 10, 16, ...
- 2023第十四届极客大挑战 — MISC WP
Misc方向题解:来自本人 cheekin 请前往"三叶草小组Syclover"微信公众号输入flag获得flag 我的解答: 关注公众号回复就可以得到一张图片,图片隐写zsteg ...
- 如何检测Windows服务停止后自动启动?自动运行.bat批处理文件?
作者:西瓜程序猿 主页传送门:https://www.cnblogs.com/kimiliucn 前言 想要确保你的Windows服务即使在崩溃后也能自动重启吗?这篇文章教你如何用一个小巧的批处理脚本 ...
- Optional源码分析(涉及Objects源码和Stream源码)
研究Optional源码之前先谈一谈Objects源码. 主要代码: @ForceInline public static <T> T requireNonNull(T obj) { if ...