【H2O2|全栈】JS进阶知识(七)ES6(3)

目录

前言

开篇语

准备工作

递归

概念

形式

优缺点

案例

数组求和

斐波那契数列

递归查找数据

柯里化

概念

形式

什么时候使用柯里化?

多维数组扁平化

多维数组

扁平化

利用flat()

与字符串相互转化

与JSON字符串相互转化

some(),concat()和扩展运算符

结束语


前言

开篇语

本系列博客主要分享JavaScript的进阶语法知识,本期为第七期,依然围绕ES6的语法进行展开。

本期内容为:递归、柯里化和多维数组扁平化。

与基础部分的语法相比,ES6的语法进行了一些更加严谨的约束和优化,因此,在之后使用原生JS时,我们应该尽量使用ES6的语法进行代码编写。

准备工作

软件:【参考版本】Visual Studio Code

插件(扩展包):Open in browser, Live Preview, Live Server, Tencent Cloud AI Code Assistant, htmltagwrap

提示:在不熟练的阶段建议关闭AI助手

浏览器版本:Chrome

系统版本: Win10/11/其他非Windows版本

递归

概念

递归(recursion)就是方法内部调用自身,它通常把一个大规模复杂的问题转化为一个与原问题相似的但是规模更小的问题来求解。 

形式

首先,递归方法内部需要调用自身,一般来说,我们的递归需要返回,所以一开始可以这么写——

  function fn() {return 含有fn()的表达式}

但是,如果仅仅这么写,显然fn()在调用自身之后,还会一直不断调用自身,以至于无限调用。

这样一来,最终就会和一个退不出的while循环一样,导致栈内存溢出,即爆栈。

所以,我们还需要有一个终止条件,相当于while循环的判定条件,用于退出递归过程。

终止时,不再调用自身

  function fn() {if (终止条件) return 最终结果return 含有fn()的表达式}

其中,所谓含有fn()的表达式也就是递归调用的递推公式,即由此前的递归结果经过某种处理得到当前递归结果的表达式。

由此,我们得到了递归的所有必要条件——

  1. 终止条件
  2. 最终结果
  3. 递推公式

优缺点

优点:使用少量的代码完成大量重复的过程,即复杂问题简单化

缺点:①需要退出条件,否则会导致栈内存溢出

②多次递归调用,不断开辟栈空间,致使代码的执行效率低

案例

数组求和

要想熟悉递归的思想,首先还是要理解递推公式,可能有点抽象,我们结合案例来理解。

最简单的案例就是数组求和,比如现在有下面这个数组——

let arr = [1, 2, 3, 4, 5];

在之前我们求该数组的和,是使用for循环进行遍历,就像下面这样——

let sum = 0;
for (let item of arr) {sum += item; // 1console.log(sum); // 2
}
console.log(sum); // 3

事实上,我们就是在重复+item这一过程(代码1),而每一次循环中的sum(代码2)就是当次item上一次求和的结果的和。

如果我们把求和这一过程封装成一个方法sum(n),n是我们的当前项。这个方法的返回值为求和的结果,那么代码1实质上就是下面这样——

sum(n) = sum(n - 1) + item

而sum(n - 1),又可以进一步拆为sum(n - 2) + item,而这两次拆分在形式上完全一致

因此,这就是我们需要找的递推公式的基本雏形。 

那么,在不遍历的前提下,如何获得当前的item呢?

我们知道,数组的pop()方法删除数组的最后一个数据,并返回删除的数据。

所以,item就可以写成——

arr.pop()

而每次arr在pop之后长度也会减一,正好也满足n-1的条件。

退出条件就是arr的长度为1,最终结果为数组最后剩下的一项,即arr[0]。

所以,使用递归求数组和的步骤如下——

function sum(arr) {if (arr.length == 1) {return arr[0];}return sum(arr) + arr.pop();
}

斐波那契数列

在了解完递归之后,我们来看一些更加复杂的问题。

斐波那契数列的形式是,数组的前两项都为1,其他项为前两项之和,就像下面这样——

1, 1, 2, 3, 5, 8, 13, ......

如何求斐波那契数列的第n项?

我们可以封装fei()方法,用于求斐波那契数列的第n项。

当前项为fei(n),拆分之后就是fei(n - 1) + fei(n - 2),即前两项和

那么,最简单的fei()的形式如下——

function fei(n) {if (n == 1 || n == 2) {return 1;}return fei(n - 1) + fei(n - 2);
}

那么,这个方法合理吗?

分析一下方法的执行过程,每执行一次fei(),在return中都要递归调用两次fei(),即求了两次和

这就意味着,求第n项时,整个方法总共需要执行2^n次,即时间复杂度为O(2^n),这是效率极其低下,且具有爆栈风险的。

所以,我们需要对此做出优化。

回到数列上,当前项为前两项之和,比如第三项为第一项和第二项的和,第四项为第二项和第三项的和。

实际上,第三项的值在上一步已经算出来了,我们可以在这里减少一次递归

在fei()的参数列表中,传入三个参数——

fei(n, pre, next)

n为当前项数 - 2(不包含第一项和第二项),pre为前一项,next为当前项。 

首次调用时,为pre和next设置初始值1。

在递推公式中,原pre处传入next,原next处传入pre + next,实现每一次只递归求一次和的效果。

当n = 1时,返回最后一次pre + next的结果。

优化后的完整的fei()方法如下——

function fei(n, pre = 1, next = 1) {if (n == 1) {return pre + next;}return fei(n - 1, next, pre + next);
}

该方法只运行n次,时间复杂度为O(n)。注意求第n项时实际上需要输入n - 2。 

递归查找数据

下面,我们来看一些偏向实际一些的应用,递归常常用于查找数据并返回。

我们存储数据的结构,比如说文件夹,常常有很多层。

我们在查找时,判断当前项是否是存储结构,如果是,则继续查找;如果已经是需要查找的数据,则返回(或输出)数据。

由于继续查找使用的也是当前的查找方法,所以这里用到的也是递归。

现在,我们有下面的数据存储结构——

    var data = [{name: "所有物品",children: [{name: "水果",children: [{ name: "苹果"}]},{name: '主食',children: [{ name: "米饭", children: [{ name: '北方米饭' }, { name: '南方米饭' }] }]},{name: '生活用品',children: [{ name: "电脑类", children: [{ name: '联想电脑' }, { name: '苹果电脑' }] },{ name: "工具类", children: [{ name: "锄头" }, { name: "锤子" }] },{ name: "生活用品", children: [{ name: "洗发水" }, { name: "沐浴露" }] }]}]}]

我们需要获取最终的children下的name值,也就是数据,拼接为字符串输出。

输出示例如下——

观察存储结构,它是由数组和对象组成的,对象中有至多两种属性——name和children。显然,我们需要获取最后一层children下的name值。 

首先,我们可以使用forEach方法拿到数组中存放的对象。

然后,看该对象是否包含children属性,如果没有,则说明是最后一层,直接获取name的值;反之,继续查找children。

这里我们需要一个全局的str,用于拼接找到的结果。

由此,可以得到完整的搜索方法——

    // 结果字符串    let str = ''// 搜索方法function search(child) {child.forEach(item => {// 判定是否为后的数据层if (!item.children) {str += item.name + ';';return; // 终止递归}search(item.children);})}search(data);console.log(str);

柯里化

概念

柯里化(Currying)是一种关于函数的高阶技术,实质是将多个参数输入的方法转化为单个输入的方法,其他的参数在返回的方法中传入,重复该过程,并在最后返回的方法中输出结果

形式

比如,我们现在有一个方法,它的作用是求三个参数的和,大概是下面这个样子——

function sum(a, b, c) {return a + b + c;
}

我们只保留第一个参数,让剩下的参数在返回值中的方法里传入——

function sum(a) {return function(b, c) {return a + b + c;
}

重复柯里化的过程,直到每一个方法都仅有一个参数——

function sum(a) {return function(b) {return function(c) {return a + b + c;}}
}

原本我们求和的过程是这样——

sum(a, b, c);

而现在则变成了这样——

sum(a)(b)(c);

什么时候使用柯里化?

看起来,柯里化似乎让方法的形式变得更加复杂了,然而,柯里化真的是一种麻烦的方式吗?

要想了解柯里化的好处,不妨设想下面的情况:

现在有一个商场,全场的商品为88折。假设现在有一个顾客,她需要购买一件原价为1000的大衣,顾客在终端输入商品价格之后,程序返回待支付的金额。可以怎么实现? 

假设有一个用于求折扣价的函数,那么求最终支付金额的方式应当类似下面这样——

getPrice(1000, 0.88);

在这个方法中,我们需要输入两个值——原价和折扣,但是,折扣对于当前的任意商品而言都是完全一致的,我们输入折扣的过程发生了重复

而且,这个折扣难道是由顾客来决定的吗?

实际上,柯里化可以帮助我们解决上述存在的问题。对于需要重复输入且可以预设的参数,我们使用柯里化将其分离出来——

let forSale = getPrice(0.88) ;

forSale(1000);

多维数组扁平化

多维数组

多维数组就是层数超过一层的数组,就像下面这样——

 let arr = [1, [2, 3]]

由于上面的数组最高有两层,即最内层的[]距离外部有2层[],所以该数组为二维数组。

同理,最高层数为n层的数组就是n维数组。 

扁平化

扁平化实质上就是数组降维的过程,比如把三维数组降低成二维,或者再降低成一维,就是扁平化的过程。

注意,降维是由外层向内部进行的。

在本节中,我们所说的扁平化就是指把n维的数组降到最低的一维数组。

一般来说,常见的必须掌握的数组扁平化的方法有四种——

利用flat()

数组为我们提供了扁平化的方法——flat(),其中文释义就是扁、平。该方法传入一个参数,代表扁平化降维的层数。

比如有下面这个数组——

let arr = [1, [2, [3, [4, [5, 6]]]]]

使用flat()降低一维——

arr.flat(1) 

那么数组就会变成下边这样——

[1, 2, [3, [4, [5, 6]]]]

在不知道数组有几维的前提下,我们可以为flat()传入参数Infinity,代表无限降维,直到不再可降(一维)。 

arr.flat(Infinity)         // [1, 2, 3, 4, 5, 6]

与字符串相互转化

我们知道,使用toString()可以将数组以逗号进行分割,转化为字符串。

还是用上面的arr——

arr.toString();

那么数组就会变成下面的字符串——

显然,这个字符串中没有任何[],因为它们不会被toString()保留下来。

那么,我们直接使用split()方法,以逗号为分隔符将字符串转回数组——

arr.toString().split(',');

得到下面的字符(串)数组——

然后,我们需要将这些字符串类型的数据转回数字类型,可以使用map()进行遍历(forEach()没有返回值),利用Number()进行包装转化——

arr.toString().split(',').map(item => Number(item));

得到的结果就是我们要的一维数组了——

与JSON字符串相互转化

我们知道,JSON也提供了一种字符串的转化方法stringify(),该方法将完全保留原数据的结构。

JSON.stringify(arr);

此时,我们得到了一个和原数组长得一模一样但是完全由字符串组成的n维数组——

接下来,我们需要把这个字符串中的 [ 和 ] 剔除,可以使用replace(),传入正则表达式匹配(注意使用g进行全局匹配)删除——

JSON.stringify(arr).replace(/\[|\]/g, '');

得到剔除[]之后的字符串——

然后就和之前处理字符串的方法一致了,这里不再赘述。

some(),concat()和扩展运算符

我们知道,多维数组中有很多元素,它们有的是数字,有的是数组。而我们的目的就是找到这些数组,将它们进行降维。

我们想要的就是遍历多维数组,如果还能找到子数组,就接着找,并降一次维

如果完全找不到了,则说明已经是最低维,不需要再找了。

很明显,我们可以使用while()循环来完成降维,循环条件是可以找到子数组。

那么怎么给这个循环条件呢?

ES6中数组有一个API,叫做some(),可以用来遍历数组,只要数组中有一个元素符合条件,some()就会返回true。

显然,我们只需要让some()的回调函数返回值为判定是否为数组的boolean值即可。 

while(arr.some(item => item.constructor == Array)) {}

那么,如何实现只降低一维呢?

为了避免与flat(1)重复,这里我们就不再使用flat(),那样的确是有些画蛇添足了。

ES6为我们提供了扩展运算符,它的作用是把数据集合从语法层面展开,而这个特性恰好可以帮助我们去除最外层的[],也就是降低一维。

举个例子,来看看...[1, [2]]的结果——

可以看到,最外层的[]被去除了,原数组变成了一串单独的数据

现在,我们可以利用concat(),将展开后的数据以参数的形式拼接到一个空数组中,并覆盖原数组

而我们知道,数组参数 [2] 拼接之后实际上不再具有[],即arr0.concat([2])实际上往arr0中拼接的是2而非[2],这说明concat()帮助我们实现了一次降维。

所以,整个扁平化的操作步骤就十分明朗了——

while(arr.some(item => item.constructor == Array)) {

        arr = [].concat(...arr);

}

结束语

本期内容到此结束。关于本系列的其他博客,可以查看我的JS进阶专栏。

在全栈领域,博主也只不过是一个普通的萌新而已。本系列的博客主要是记录一下自己学习的一些经历,然后把自己领悟到的一些东西总结一下,分享给大家。

文章全篇的操作过程都是笔者亲自操作完成的,一些定义性的文字加入了笔者自己的很多理解在里面,所以仅供参考。如果有说的不对的地方,还请谅解。

==期待与你在下一期博客中再次相遇==

——临期的【H2O2】

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

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

相关文章

【架构】主流企业架构Zachman、ToGAF、FEA、DoDAF介绍

文章目录 前言一、Zachman架构二、ToGAF架构三、FEA架构四、DoDAF 前言 企业架构(Enterprise Architecture,EA)是指企业在信息技术和业务流程方面的整体设计和规划。 最近接触到“企业架构”这个概念,转念一想必定和我们软件架构…

迷宫题解 题目ID:8015

题目描述 时间限制: 1s 空间限制:32M 题目描述: 给定一个N∗M 方格的迷宫,每个方格最多经过一次,且迷宫里有 T 处障碍,障碍处不可通过。 在迷宫中有上下左右四种移动方式,每次只能移动一个方…

Kafka Stream实战教程

Kafka Stream实战教程 1. Kafka Streams 基础入门 1.1 什么是 Kafka Streams Kafka Streams 是 Kafka 生态中用于 处理实时流数据 的一款轻量级流处理库。它利用 Kafka 作为数据来源和数据输出,可以让开发者轻松地对实时数据进行处理,比如计数、聚合、…

Python中常用的内置函数介绍

1、生成器(Generator): 通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间&#x…

基于python Django的boss直聘数据采集与分析预测系统,爬虫可以在线采集,实时动态显示爬取数据,预测基于技能匹配的预测模型

本系统是基于Python Django框架构建的“Boss直聘”数据采集与分析预测系统,旨在通过技能匹配的方式对招聘信息进行分析与预测,帮助求职者根据自身技能找到最合适的职位,同时为招聘方提供更精准的候选人推荐。系统的核心预测模型基于职位需求技…

vulhub之fastjson

fastjson 1.2.24 反序列化 RCE 漏洞(CVE-2017-18349) 漏洞简介 什么是json json全称是JavaScript object notation。即JavaScript对象标记法,使用键值对进行信息的存储。举个简单的例子如下: {"name":"BossFrank", "age":23, "isDevel…

Scala案例:全文单词统计

2.txt内容如下 Thank you very much.Well I want to thank you all very much this is great, these are our friends, we have thousands of friends in this incredible movement.This was a movement like no nobodys ever seen before, and frankly this was I believe the…

【STK学习】part2-星座-目标可见性与覆盖性分析

【Satellite Tool Kit】学习并深入了解卫星/星座生成、可见性分析、覆盖性分析等知识,并基于STK软件实现对应数据的导出,以用于算法的约束输入。 文章目录 一、学习目标二、学习内容2.1 星地可见性分析2.1.1 单星单地2.1.2 单星多地2.1.3 多星单地 2.2 星…

金融数据中心容灾“大咖说” | 美创科技赋能“灾备一体化”建设

中国人民银行发布的《金融数据中心容灾建设指引》(JR/T 0264—2024)已于2024年7月29日正式实施。这一金融行业标准对金融数据中心容灾建设中的“组织保障、需求分析、体系规划、建设要求、运维管理”进行了规范和指导。面对不断增加的各类网络、业务、应…

《第十部分》1.STM32之通信接口《精讲》之IIC通信---介绍

经过近一周的USART学习,我深刻体会到通信对单片机的重要性。它就像人类的手脚和大脑,只有掌握了通信技术,单片机才能与外界交互,展现出丰富多彩的功能,变得更加强大和实用。 单片机最基础的“语言”是二进制。可惜&am…

Vue通过file控件上传文件到Node服务器

功能: 1.多文件同时上传、2.拖动上传、3.实时上传进度条、4.中断上传和删除文件、5.原生file控件的美化 搁置的功能: 上传文件夹、大文件切片上传、以及其他限制条件未处理 Node服务器的前置准备: 新建文件夹: file_upload_serve初始化npm: npm …

如何使用 Docker Compose 安装 WireGuard UI

简介 wireguard是什么?维基百科是这样描述的: WireGuard是一种实现加密虚拟专用网络(VPN) 的通信协议和免费开源软件,其设计目标是易于使用,高速性能和低攻击面。它旨在比IPsec和OpenVPN这两种常见的隧道协议具有更好的性能和更…

IM项目-----客户端网络通讯流程

文章目录 前言数据中心类数据持久化网络通信类http客户端websocket客户端 前言 对即时通信系统客户端部分的网络通讯流程的总结。 数据中心类 通过model/datacenter.h 中的DataCenter 类来管理所有客⼾端需要的数据.这是⼀个单例类. 这里管理一份数据有两个作用,1…

软件测试—— Selenium 常用函数(一)

前一篇文章:软件测试 —— 自动化基础-CSDN博客 目录 前言 一、窗口 1.屏幕截图 2.切换窗口 3.窗口设置大小 4.关闭窗口 二、等待 1.等待意义 2.强制等待 3.隐式等待 4.显式等待 总结 前言 在前一篇文章中,我们介绍了自动化的一些基础知识&a…

WebSocket详解、WebSocket入门案例

目录 1.1 WebSocket介绍 http协议: webSocket协议: 1.2WebSocket协议: 1.3客户端(浏览器)实现 1.3.2 WebSocket对象的相关事宜: 1.3.3 WebSOcket方法 1.4 服务端实现 服务端如何接收客户端发送的请…

【图像分割】SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers

SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers 论文链接:http://arxiv.org/abs/2105.15203 代码链接:https://github.com/NVlabs/SegFormer 一、摘要 文中提出了SegFormer,一个简单、高效且强大的…

Linux修改/etc/hosts不起作用(ping: xxx: Name or service not known)的解决方法——开启NSCD

​ 问题描述 起因是我在实验室云资源池的一台虚拟机(CentOS 8.5)上的/etc/hosts文件中为Fabric网络节点的域名指定了IP: IP可以ping通,但是ping域名时提示ping: xxx: Name or service not known。 问题本身应该是Linux通用的&a…

OpenTelemetry 赋能DevOps流程的可观测性革命

原作者:天颇 原出处:微信公众号 乘云数字DATABUFF 原文地址:https://mp.weixin.qq.com/s/D_f31EBtLu7Rr0gahuF-bw 引言 在当今快节奏的软件开发和运维环境中,DevOps 已经成为主流,它通过整合开发和运维流程&#xff0…

windows 和 linux检查操作系统基本信息

windows检查操作系统基本信息 systeminfolinux检查操作系统基本信息 获取系统位数 getconf LONG_BIT查询操作系统release信息 lsb_release -a查询系统信息 cat /etc/issue查询系统名称 uname -a

使用SaaS化的Aurora应用快速搭建私人ChatGPT助手

使用SaaS化的Aurora应用快速搭建私人ChatGPT助手 简介: Aurora是一个带UI且免费的GPT私人聊天助手,可切换GPT-3.5,4,4o等常用版本。用户可通过部署Aurora,快速打造自己专属的AI助手。阿里云计算巢已将Aurora打包为SaaS…