摘要:函数继承是在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%的更多相关文章

  1. js的5种继承方式——前端面试

    js主要有以下几种继承方式:对象冒充,call()方法,apply()方法,原型链继承以及混合方式.下面就每种方法就代码讲解具体的继承是怎么实现的. 1.继承第一种方式:对象冒充 function P ...

  2. js各种继承方式和优缺点的介绍

    js各种继承方式和优缺点的介绍 作者: default 参考网址2 写在前面 本文讲解JavaScript各种继承方式和优缺点. 注意: 跟<JavaScript深入之创建对象>一样,更像 ...

  3. js 中继承方式小谈

    题外话 前段时间面试中笔试题有这道题目: 请实现一个继承链,要求如下: 构造函数A():构造函数中有consoleA方法,可以实现console.log("a") 实例对象 a:a ...

  4. Javascript学习笔记:3种定义函数的方式

    ①使用函数声明语法定义函数 function sum(num1,num2){ return num1+num2; } ②使用函数表达式定义函数 var sum=function(num1,num2){ ...

  5. js函数验证方式:验证是否是数字,支持小数,负数

    验证 datatype="/^\d+(\.\d+)?$/" validatform验证是否是数字 支持小数点 datatype="d" 貌似支持小数 js函数验 ...

  6. javascript两种声明函数的方式的一次深入解析

    声明函数的方式 javascript有两种声明函数的方式,一个是函数表达式定义函数,也就是我们说的匿名函数方式,一个是函数语句定义函数,下面看代码: /*方式一*/ var FUNCTION_NAME ...

  7. JS中继承方式总结

    说在前面:为了使代码更为简洁方便理解, 本文中的代码均将"非核心实现"部分的代码移出. 一.原型链方式关于原型链,可点击<深入浅出,JS原型链的工作原理>,本文不再重复 ...

  8. 【深入JavaScript】一种JS的继承方法

    这些天读了John Resig的<Secrets of JavaScript Ninja>,其中讨论到JS中实现继承的方案,非常有趣,自己探索了一下,形成了笔记,放到这里. 这个方案在Re ...

  9. 漫谈JS 的继承方式

    一.原型链原型链的基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法.每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的指针.如果:我们让 ...

  10. js的继承方式分别适合哪些应用场景?

    一.原型链 利用 Person.prototype = new Animal("Human") 实现继承: static式继承.能继承Animal.prototype.不可多重继承 ...

随机推荐

  1. Flask后端开发(一)-基础知识和前期准备

    目录 1.背景介绍 1.1. 项目背景 1.2. 项目难点 1.3. 项目环境 2. flask后端开发实现的功能 3. flask部署和前后端对接 3.1. flask运行配置和服务器部署 3.2. ...

  2. 机器学习|K邻近(K Nearest-Neighbours)

    本文从概念.原理.距离函数.K 值选择.K 值影响..优缺点.应用几方面详细讲述了 KNN 算法 K 近临(K Nearest-Neighbours) 一种简单的监督学习算法,惰性学习算法,在技术上并 ...

  3. k8s-服务网格实战-配置 Mesh(灰度发布)

    在上一篇 k8s-服务网格实战-入门Istio中分享了如何安装部署 Istio,同时可以利用 Istio 实现 gRPC 的负载均衡. 今天我们更进一步,深入了解使用 Istio 的功能. 从 Ist ...

  4. 【misc】ctfshow-stega10 --套娃

    附件下载下来是一张图片 各种隐写工具一把梭,无果,分析其二进制数据,把图片拖进hxd,发现一段疑似base64的东西 base64解密试试 解密出来是一个网址,打开下载第二个附件:flag.zip,打 ...

  5. C语言求100以内的全部素数,每行输出10个。素数就是只能被1和自身整除的正整数,1不是素数,2是素数。要求定义和调用函数prime(m)判断m是否为素数,当m为素数时返回1,否则返回0。

    /* 开发者:慢蜗牛 开发时间:2020.5.28 程序功能:求100以内的素数 */ #include<stdio.h> int prime(int m); int prime(int ...

  6. 数据库系列:业内主流MySQL数据中间件梳理

    数据库系列:MySQL慢查询分析和性能优化 数据库系列:MySQL索引优化总结(综合版) 数据库系列:高并发下的数据字段变更 数据库系列:覆盖索引和规避回表 数据库系列:数据库高可用及无损扩容 数据库 ...

  7. Vite4+Typescript+Vue3+Pinia 从零搭建(7) - request封装

    项目代码同步至码云 weiz-vue3-template 基于 axios 封装请求,支持多域名请求地址 安装 npm i axios 封装 utils 目录下新建 request 文件夹,并新建 i ...

  8. python 获取android 应用使用情况

    python 获取android 应用使用情况 本文主要讲述python 脚本获取android 应用使用情况. 主要思路:使用adb 获取当前activity ,1s 一次输出. 主要涉及知识点: ...

  9. C#Socket编程详解(一)TCP与UDP简介

    一.TCP与UDP(转载) 1.TCP 1.1 定义 TCP(TransmissionControl Protocol)传输控制协议. 是一种可靠的.面向连接的协议(eg:打电话).传输效率低全双工通 ...

  10. Windows下使用图形化的Havoc C2

    Windows下使用图形化的Havoc C2 前言 这几天用goland去连虚拟机去coding真的心累,想着搞个wsl算了虽然测试又变麻烦了(wsl2和VMware一起开有问题,可能保存不了快照), ...