uni-app--》基于小程序开发的电商平台项目实战(三)

🏍️作者简介:大家好,我是亦世凡华、渴望知识储备自己的一名在校大学生

🛵个人主页:亦世凡华、

🛺系列专栏:uni-app

🚲座右铭:人生亦可燃烧,亦可腐败,我愿燃烧,耗尽所有光芒。

👀引言

        ⚓经过web前端的学习,相信大家对于前端开发有了一定深入的了解,今天我开设了uni-app专栏,主要想从移动端开发方向进一步发展,而对于我来说写移动端博文的第二站就是uni-app开发,希望看到我文章的朋友能对你有所帮助。

今天开始使用 vue3 + uni-app 搭建一个电商购物的小程序,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的GitHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关注本专栏,学习更多前端uni-app知识。然后开篇先简单介绍一下本项目用到的技术栈都有哪几个方面(阅读此次项目实践文章能够学习到的技术):

uni-app:跨平台的应用开发框架,基于vue.js可以一套代码同时构建运行在多个平台。

pnpm:高性能、轻量级npm替代品,帮助开发人员更加高效地处理应用程序的依赖关系。

vue3:vue.js最新版本的用于构建用户界面的渐进式JavaScript框架。

typescript:JavaScript的超集,提供了静态类型检查,使得代码更加健壮。

pinia:vue3构建的Vuex替代品,具有响应式能力,提供非常简单的 API,进行状态管理。

uni-ui:基于vue.js和uni-app的前端UI组件库,开发人员可以快速地构建跨平台应用程序。

如果是第一次接触uni-app并且想学习uni-app的朋友,我是不建议直接从此次实战项目开始看起,可以先阅读一下我以前的基础文章:什么是uniapp?如何开发uniapp?按部就班的学习可以让学习变得更轻松更容易上手哦,闲话少说我们直接开始今天的uni-app实战篇。

目录

实现上拉触底

实现下拉刷新

生成骨架屏

热门推荐详情实现

分页条件设置


实现上拉触底

在上一章节我们已经实现首页静态模块的搭建,接下来我们需要加载猜你喜欢的更多相关的数据,这里需要借助上拉触底的功能,因为滚动容器内包裹着猜你喜欢的组件,所以我们还需要通过父调子的方法来拿到相应的组件实例,这里我们先实现上拉触底的具体操作:

这里我们需要在滚动容器当中添加 scrolltolower 函数来触发上拉触底的事件:

接下来我们需要在调用上拉触底事件的时候,调用猜你喜欢组件内容的方法,这里需要我们将猜你喜欢的组件调用数据方法先暴露出来:

然后通过 InstanceType<typeof Swiper> 用于获取 Swiper 这个类的实例类型。 

// 组件实例类型
export type SwGuessInstance = InstanceType<typeof SwGuess>

然后通过ref获取 SwGuess 组件的相应实例,之后便可以调用SwGuess组件当中的方法:

// 滚动触底事件函数
let guessRef = ref<SwGuessInstance>() // 获取猜你组件实例
const onScrolltolower = () => {guessRef.value?.getGuess()
}

父组件调用子组件获取猜你喜欢函数的方法之后,当我们进行上拉触底的时候,该函数就会再次杯执行,如下:

接下来对猜你喜欢的接口函数进行修改,我们之前编写的接口仅仅是获取默认第一页的数据而已,这里我们还需要传入可选参数页码page和页数pageSize,如下:

/** 通用分页参数类型 */
export type PageParams = {/** 页码:默认值为 1 */page?: number/** 页大小:默认值为 10 */pageSize?: number
}

接下来我们就需要对猜你喜欢的接口函数进行相应的修改了:

/* ** 获取首页猜你喜欢的接口函数*/
export const getHomeGoodsGuessLike = (data?: PageParams) => {return http<PageResult<GuessItem>>({method: 'GET',url: '/home/goods/guessLike',data,})
}

编写完相应的接口函数之后,接下来我们需要在相应的猜你喜欢的组件中对猜你喜欢的接口函数传入相应的参数数据,然后对相应的数据进行一个数组的push,然后在进行一个页码数据的累加,因为页码的接口类型是可选参数,直接进行累加的话会ts类型报错,这里我们需要将可选参数变为必选参数,所以这里使用了必须类型的Required泛型方式,如下:

// 获取猜你喜欢数据
let pageParmas: Required<PageParams> = {page: 1,pageSize: 10,
}
let guessList = ref<GuessItem[]>([]) // 猜你喜欢数据列表
const getHomeGoodsGuessLikeData = async () => {const res = await getHomeGoodsGuessLike(pageParmas)// guessList.value = res.result.itemsguessList.value.push(...res.result.items)// 页码累加pageParmas.page++
}

这里我们还需要对分页进行一个条件的判断,因为数据可能是有限的,如果我们不加以限制的话数据可能就会一直无限的循环下去,这里需要我们进行数据总数的一个判断,其对应的逻辑如下:

这里通过设置一个结束标记来判断当前的页码是否小于总页码数,如果已经或等于的话,这里就借助uni-app提供的一个弹出消息框来提醒用户,当前的数据已经全部加载完毕了:

// 设置一个结束标记
let finish = ref<boolean>(false)
let guessList = ref<GuessItem[]>([]) // 猜你喜欢数据列表
const getHomeGoodsGuessLikeData = async () => {if (finish.value) {return uni.showToast({title: '没有更多数据了~',icon: 'none',})}const res = await getHomeGoodsGuessLike(pageParmas)// guessList.value = res.result.itemsguessList.value.push(...res.result.items)if (pageParmas.page < res.result.pages) {// 页码累加pageParmas.page++} else {finish.value = true}
}

呈现的效果如下:

实现下拉刷新

实现下拉刷新需要借助的 refresher-enabled 属性来实现,其默认为false,这里我们在滚动容器中填写该属性即可,然后再通过自定义下拉刷新函数来设置相应的规则:

自定义下拉刷新函数,这里我们把首页每个模块获取到的数据的函数再重新的调用一下:

// 自定义下拉刷新
const onRefresherrefresh = () => {getHomeBannerData() // 轮播图数据getHomeCategoryData() // 分类数据getHomeHotData() // 猜你喜欢数据
}

最终呈现的结果如下:

虽然我们实现了下拉刷新后,数据的重新加载变化,但是下拉刷新的状态还是存在一直没消失,这里我们需要对其进行处理一下,通过 refresher-triggered 属性设置其下拉刷新的状态,如下:

然后通过函数的调用时机,巧妙在数据加载完成之后再关闭下拉刷新的动画效果:

// 设置下拉刷新状态
let isTriggered = ref<boolean>(false)
// 自定义下拉刷新
const onRefresherrefresh = async () => {// 开启动画isTriggered.value = true// 加载数据await getHomeBannerData() // 轮播图数据await getHomeCategoryData() // 分类数据await getHomeHotData() // 猜你喜欢数据// 加载数据完成关闭动画isTriggered.value = false
}

虽然我们实现了下拉刷新的效果,但是这个存在着一个性能问题,就是需要按个等每个数据都加载完成之后才会结束下拉刷新的效果:

为了避免这个问题,我们可以借助es6语法的Promise.all()方法,等所有异步函数都加载完才执行

// 设置下拉刷新状态
let isTriggered = ref<boolean>(false)
// 自定义下拉刷新
const onRefresherrefresh = async () => {// 开启动画isTriggered.value = true// 加载数据// await getHomeBannerData() // 轮播图数据// await getHomeCategoryData() // 分类数据// await getHomeHotData() // 猜你喜欢数据await Promise.all([getHomeBannerData(), getHomeCategoryData(), getHomeHotData()])// 加载数据完成关闭动画isTriggered.value = false
}

这样的方式就会很快结束我们下拉刷新的进程,提高了资源的利用效率:

接下来我们还需要处理猜你喜欢组件下拉刷新时更新数据的方法,这里我们需要在猜你组件当中对我们当前的页码数据列表以及相应的结束标记都需要进行一个相应的重置,然后将我们重置的方法对外暴露出去:

然后再在下拉刷新函数加载数据处,先调用猜你喜欢重置函数,然后再调用获取数据函数,这样的话当我们下拉查看到大于1的页码猜你喜欢的数据之后,回到首页再下拉刷新一下,猜你喜欢的数据又会重新加载到第一页:

生成骨架屏

骨架屏(Skeleton Screen)是一种应用于移动端和网页端的用户体验优化策略。它是在页面或者应用还未加载完毕时,先展示一个大致布局结构相同、内容却尚未加载完成的界面UI效果,让用户感知到应用正在加载中并且保证用户对内容的期待。之后再逐渐替换成真实数据。

在微信开发者工具当中已经帮助我们提供了一键生成骨架屏的按钮,在模拟器的右下方就有按钮:

我们点击生成骨架屏之后会生成相应的代码文件,这里我们需要将微信开发者工具生成的wxml和wxss文件转换成vue文件即可:

接下来我们将生成好的vue文件导入到首页当中,然后通过v-if和v-else进行相应的判断

我们在页面挂载的时候设置一个标记,开始时处于骨架屏状态,当页面同时都加载完成之后,接下来我们就可以将标记赋值为false然后关闭骨架屏展示相应的页面内容:

// 标记是否在加载中
let isLoading = ref<boolean>(false)
// 组件刚加载的时候调用
onLoad(async () => {// 页码处于加载中isLoading.value = trueawait Promise.all([getHomeBannerData(), getHomeCategoryData(), getHomeHotData()])// 页面加载完毕isLoading.value = false
})

这里我们将微信开发者工具的网速调低,方便我们清晰的看到初始时骨架屏加载后的效果:

热门推荐详情实现

在之前我们已经实现了首页四个热门推荐详情数据的静态展示,接下来我们实现点击热门推荐进行页面跳转,实现热门推荐具体详情数据的展示,首先我们先在pages文件夹下新建uniapp页面,该页面会自动加载到我们的pages.json里面:

页面新建好之后,接下来我们开始实现路由的跳转,因为热门推荐在首页的展示也是通过组件进行展示的,所以我们只要在热门推荐相关书写的组件中进行设置路由的跳转即可,这里我们借助uniapp中navigator固有的语法进行设置即可,具体的属性讲解这里就不再赘述了,想了解的朋友可以随时去官网进行查看,这里仅仅是简单提一下我们目前所使用的方式是什么:

根据官方文档给我们提供的相关属性,这里我们就直接使用,并且巧妙的借助模板字符串进行动态的路由传参,将接口数据中特别分类热门推荐的数据type进行传递过去,便于区分我们点击的是谁

接下来我们开始编写获取热门推荐相关数据的接口函数,这里函数设置的传递参数有两个,url路径是必传参数,data为可选参数为了后面设置分页数据加载做准备:

import type { PageParams } from '@/types/global'
import type { HotResult } from '@/types/hot'
import { http } from '@/utils/http'type HotParams = PageParams & { subType?: string }
// 热门推荐相关数据展示
export const getHotRecommendAPI = (url: string, data?: HotParams) => {return http<HotResult>({method: 'GET',url,data,})
}

编写完接口函数之后,这里我们在我们新建的hot.vue页面进行接口函数的调用,然后通过ref响应式数据进行数据赋值:

<script setup lang="ts">
import { ref } from 'vue'
import { getHotRecommendAPI } from '@/api/home/hot'
import { onLoad } from '@dcloudio/uni-app'
import type { SubTypeItem } from '@/types/hot'// 热门推荐页 标题和url
const hotMap = [{ type: '1', title: '特惠推荐', url: '/hot/preference' },{ type: '2', title: '爆款推荐', url: '/hot/inVogue' },{ type: '3', title: '一站买全', url: '/hot/oneStop' },{ type: '4', title: '新鲜好物', url: '/hot/new' },
]
// uni-app 获取页面参数
const query = defineProps<{type: string
}>()
const currUrlMap = hotMap.find((v) => v.type === query.type)
// 动态设置标题
uni.setNavigationBarTitle({ title: currUrlMap!.title })// 推荐封面图
const bannerPicture = ref<string>('')
// 推荐选项
const subTypes = ref<SubTypeItem[]>([])
// 高亮的下标
const activeIndex = ref<number>(0)
// 获取热门推荐数据
const getHotRecommendData = async () => {const res = await getHotRecommendAPI(currUrlMap!.url)bannerPicture.value = res.result.bannerPicturesubTypes.value = res.result.subTypes
}
// 页面加载
onLoad(() => {getHotRecommendData()
})
</script>

具体的页面布局设置如下,也是非常简单的动态绑定数据然后通过插值表达式进行数据的展示即可,这里简单提一下,我们在上面设置了高亮下标的标记,通过我们点击选项和我们当前的下标进行对比来展示不同的样式和不同的热门推荐数据,如下:

<template><view class="viewport"><!-- 推荐封面图 --><view class="cover"><image class="imgae" :src="bannerPicture"></image></view><!-- 推荐选项 --><view class="tabs"><textv-for="(item, index) in subTypes":key="item.id"class="text":class="{ active: index === activeIndex }"@tap="activeIndex = index">{{ item.title }}</text></view><!-- 推荐列表 --><scroll-viewv-for="(item, index) in subTypes":key="item.id"v-show="activeIndex === index"scroll-yclass="scroll-view"><view class="goods"><navigatorhover-class="none"class="navigator"v-for="goods in item.goodsItems.items":key="goods.id":url="`/pages/goods/goods?id=${goods.id}`"><image class="thumb" :src="goods.picture"></image><view class="name ellipsis">{{ goods.name }}</view><view class="price"><text class="symbol">¥</text><text class="number">{{ goods.price }}</text></view></navigator></view><view class="loading-text">正在加载...</view></scroll-view></view>
</template>

最终呈现的效果如下:

分页条件设置

当我们设置完初始的热门推荐详情页面之后,接下来我们需要开始为该详情页面进行设置相应的分页加载数据了,关于分页加载数据的讲解在上一篇文章讲解猜你喜欢数据的时候已经讲解过一遍了,当然这里在简单的进行讲解一下:

首先我们需要先给滚动容器设置滚动触底事件,因为热门推荐详情页面有小tab按钮进行切换展示不同的热门推荐的数据,所以这里我们需要先获取我们点击当前热门推荐详情tab的下标,因为我们之前是通过v-show进行页面的切换展示的,不同的tab按钮展示的数据相互独立不会影响,所以不同担心。

// 获取当前的选项
const currsubTypes = subTypes.value[activeIndex.value]
// 分页条件
if (currsubTypes.goodsItems.page < currsubTypes.goodsItems.pages) {// 当前页码累加currsubTypes.goodsItems.page++
} else {// 标记已结束currsubTypes.finish = true// 退出并提示消息return uni.showToast({ icon: 'none', title: '没有更多数据了~' })
}

根据条件的判断来确保是否要增加当前页码,然后接下来将增加的页码值再通过热门推荐的接口函数中进行传递再获取当前的数据,将获取到的数据再进行追加到我们设置好的参数中然后再进行调用相应的接口函数再一次获取数据,完整代码如下:

// 自定义滚动触底事件
const onScrolltolower = async () => {// 获取当前的选项const currsubTypes = subTypes.value[activeIndex.value]// 分页条件if (currsubTypes.goodsItems.page < currsubTypes.goodsItems.pages) {// 当前页码累加currsubTypes.goodsItems.page++} else {// 标记已结束currsubTypes.finish = true// 退出并提示消息return uni.showToast({ icon: 'none', title: '没有更多数据了~' })}// 调用API传参const res = await getHotRecommendAPI(currUrlMap!.url, {subType: currsubTypes.id,page: currsubTypes.goodsItems.page,pageSize: currsubTypes.goodsItems.pageSize,})// 新的列表选项const newsubTypes = res.result.subTypes[activeIndex.value]// 数据追加currsubTypes.goodsItems.items.push(...newsubTypes.goodsItems.items)
}

这里我们设置了一个结束标记finish,用来判断当前的数据是否已经加载完毕,因为subTypes本身是没有这个ts类型的,所以这里我们在之前设置ts类型基础之上,再添加一个:

// 推荐选项
const subTypes = ref<(SubTypeItem & { finish?: boolean })[]>([])

然后这里我们就可以通过三元表达式进行判断当前的数据是否已经加载完毕了:

<view class="loading-text">{{ item.finish ? '没有更多数据了~' : '正在加载...' }}</view>

这里还有一个小技巧,因为我们目前的分页数据有很多,当我们进行测试的时候需要不停的滚动数据,这里只要我们通过判断当前是否是开发还是生产环境,来动态的改变我们当前初始的页码值就可以方便的进行我们数据的测试了:

如果是开发环境,测试页码是30否则就是1,所以当我们进行开发的时候就会从30页开始,打包上线之后,我们的初始页码数据就会从1进行开始了:

最终呈现的效果如下:

本项目首页的一些基本功能的搭建就讲解到这,下一篇文章将继续讲解项目的分类页码代码操作,关注博主学习更多前端uni-app知识,您的支持就是博主创作的最大动力!

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

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

相关文章

7.1 为什么要用函数

主要内容&#xff1a; 这段文字主要讲述了为什么要使用函数来进行程序设计&#xff0c;以及函数在程序设计中的重要性和作用。以下是这段文字的主要内容和要点&#xff1a; ### 1. **简化和清晰度** - 当程序规模较大&#xff0c;功能较多时&#xff0c;如果所有代码都写在主…

04-Zookeeper集群详解

上一篇&#xff1a;03-Zookeeper客户端使用 Zookeeper 集群模式一共有三种类型的角色 Leader: 处理所有的事务请求&#xff08;写请求&#xff09;&#xff0c;可以处理读请求&#xff0c;集群中只能有一个LeaderFollower&#xff1a;只能处理读请求&#xff0c;同时作为 Le…

【数据库——MySQL】(6)查询(1)

目录 1. 数据库查询1.1 输出项为列名1.2 输出项为表达式1.3 输出内容变换1.4 消除输出项的重复行1.5 聚合函数 2. 查询条件&#xff1a;逻辑条件2.1 比较运算2.2 模式匹配2.3 范围限定2.4 空值判断 3. 分组3.1 基本分组3.2 分组汇总 4. 分组后筛选5. 输出行排序5.1 ORDER BY5.2…

Anchors

这是源代码定义的anchors概念&#xff1a; 实现过程&#xff1a; 假如有一张500500的图片&#xff0c;那么经过第一步深度卷积网络之后&#xff08;4次池化&#xff09;&#xff0c;最终就会变成一个3232的特征&#xff1a; 在开源代码实现里面&#xff1a; 所以经过卷积完之后…

leetCode 62.不同路径 动态规划 + 空间复杂度优化

62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xf…

基于SpringBoot的酒店客房管理系统

基于SpringBoot的酒店管理系统、酒店客房管理系统 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 首页 管理员界面 用户界面 代码展示 <temp…

如何使用docker快速部署MinDoc文档系统

MinDoc是非常优秀的知识分享系统&#xff0c;但是很多刚接触的人会一脸懵逼&#xff0c;而且官方文档写的也并不清晰&#xff0c;所以和大家分享一下快速部署MinDoc的方法。 首先docker环境先自行安装好&#xff0c;这里不再赘述。 拉取docker镜像&#xff1a; docker pull …

MybatisPlus自定义SQL用法

1、功能概述&#xff1f; MybatisPlus框架提供了BaseMapper接口供我们使用&#xff0c;大大的方便了我们的基础开发&#xff0c;但是BaseMapper中提供的方法很多情况下不够用&#xff0c;这个时候我们依旧需要自定义SQL,也就是跟mybatis的用法相同&#xff0c;自定义xml映射文…

lv5 嵌入式开发-8 内存映射

目录 1 内存映射基本使用 1.1 内存映射概念 1.2 内存映射的使用 2 共享内存&#xff08;古老的 System V IPC&#xff09; 2.1 基本概念 2.2 共享内存使用步骤 2.3 共享内存使用 掌握&#xff1a;内存映射概念、内存映射使用、内存映射注意事项、了解SYSTEM V 共享内存概…

nodejs+vue中国非物质文化遗产网站设计与实现elementui

前端页面&#xff1a; 导航栏借鉴下面的 1首页&#xff1a;带有一个全屏轮播图和其他的内容 2咨询页&#xff1a;有关中国非物质文化遗产的一些新闻咨询网站对于记录非遗这种无形的、动态的文化资源有着其他技术无可替代的优势。用户可以在该网站浏览、了解和学习非遗文化&…

uni-app:canvas-绘制图形4(获取画布宽高,根据画布宽高进行图形绘制)

效果 代码 var width ; var height ; const query uni.createSelectorQuery(); //获取宽度 query.select(#firstCanvas).fields({ size: true }, (res) > { width res.width; height res.height; }).exec(); console.log(宽度width); console.log(高…

关于Pod的内存使用率一直很高的问题分析

生产环境中在流量高峰期出现pod内存使用率很高&#xff0c;pod批量重启&#xff0c;错误日志中还有OOM相关信息。 查看堆内存的使用值 Pod使用的内存不能直接在pod中通过top命令查看&#xff0c;这种方式看到的是pod所在node的资源使用情况。想查看pod的资源使用情况需要用ku…

SEO的优化教程(百度SEO的介绍和优化)

百度SEO关键字介绍&#xff1a; 百度SEO关键字是指用户在搜索引擎上输入的词语&#xff0c;是搜索引擎了解网站内容和相关性的重要因素。百度SEO关键字可以分为短尾词、中尾词和长尾词&#xff0c;其中长尾词更具有针对性和精准性&#xff0c;更易于获得高质量的流量。蘑菇号-…

【Matplotlib画图】使用Python Matplotlib画三维的子图

文章目录 1. 代码2. 画图效果写在最后 1. 代码 在matlab转过来&#xff0c;之前一直不知道python的写法&#xff0c;以为是像matlab一样返回一个句柄然后在上面添加元素&#xff1b; 其实是应该先创建一个画布&#xff0c;然后再在上面添加子图&#xff0c;然后再使用返回的句…

解决 MyBatis-Plus 中增加修改时,对应时间的更新问题

问题&#xff1a;在添加修改时&#xff0c;对应的 create_time 与 insert_time 不会随着添加修改而自动的更新时间 第一步&#xff1a;首先在对应的属性上&#xff0c;加上以下注解 如果只添加以下注解&#xff0c;在增加或者修改时&#xff0c;可能对应的 LocalDateTime 会出…

基于微信小程序的公交信息在线查询系统小程序设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

支付宝支付模块开发

生成二维码 使用Hutool工具类生成二维码 引入对应的依赖 <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.5</version> </dependency><dependency><groupId>com.go…

数码产品数码配件无线键盘等出口欧盟CE-RED认证测试办理

数码产品数码配件无线键盘CE-RED认证测试办理 无线产品CE-RED认证进入东欧市场规定&#xff1a; 在通信终端设备和无线产品在这些/地区合法销售之前&#xff0c;必须按照 RED 指令进行测试&#xff0c;并且还必须提供 CE 标志。无线远程控制产品必须符合 RED 指令的 REDEU 要…

华为云HECS云服务器docker环境下安装nginx

前提&#xff1a;有一台华为云服务器。 华为云HECS云服务器&#xff0c;安装docker环境&#xff0c;查看如下文章。 华为云HECS安装docker-CSDN博客 一、拉取镜像 下载最新版Nginx镜像 (其实此命令就等同于 : docker pull nginx:latest ) docker pull nginx查看镜像 dock…

JS对象数组去重

JS对象数组去重 一、数组去重1.使用 new Set()2.使用 indexOf 去重3.使用 includes 去重4.使用 hasOwnProperty5.使用 filter6.使用递归7.利用 Map 数据结构去重8.使用用 reduce includes9.使用 new Set() 的简化 二、对象数组去重1.使用 new Map() 和 filter2.使用reduce3.使…