写出高质量的前端代码之消除代码中的重复

news2025/7/26 19:57:39

软件开发中,有个很重要的DRY原则,即Dont Repeat Yourself,也就是不要重复自己。

重复的代码会带来以下问题:

  • 开发效率低,重复造轮子
  • 不同人开发的同一类功能,质量层次不齐
  • 修改问题时可能会遗漏,修了这个地方,忘了那个地方,导致一个bug反复修改多次。

重复产生的原因是多方面,有的是工作模式导致的,有的是编码导致的,弄清楚重复的原因,也就很容易找出消除重复的方法。

工作模式导致的重复

团队缺少沟通

同一个团队可能会针对相同或相似的功能进行编码,有时你封装了一个功能,却没有在团队内部大量使用, 究其原因可能是大家根本不知道有这么个功能,或者不知道怎么使用,干脆自己写,也或者虽然有点相似,但是并不完全适用自己。

这类问题核心还是沟通问题,所以要根本解决这类重复,我们必须在工作流程上做一些改进。

1.基础功能开发评审

当你要开发一个基础功能时,需要发起设计评审,这样首先起到了周知的作用,让大家知道有这么个东西, 其次大家会针对功能提出自己的建议,以方便他人后续使用。

2.基础功能宣贯

在开发完某个功能之后,需要及时的进行宣贯,让其他同事知道可以使用了,可以在每周的团队周会上安排一个环节, 进行公共功能的变更宣贯。

3.CodeReview

CodeReview中发现存在重复问题,及时提出修正。

缺少基础设施-公共库

在一个项目中,我们可能建一个公共的文件夹,比如common,用来存放我们的基础组件和库,但是一旦跨项目,这种方式就无效了, 如果没有公共库,就需要进行复制粘贴了,把一段代码从一个项目复制到另一个项目,这样就造成一个问题的修改需要同时修改多个项目, 如果赶上项目时间紧张,再加上可能有懒惰思想,慢慢的多个项目之间的基础功能就不再一致了,后续的维护更加复杂。

解决这类问题可以参考两个方法:

1.使用monorepo

monorepo也就是单一仓库管理多个项目,有些公司将所有代码存储在一个代码库中,由所有人共享, 因此monorepo可以非常大。例如,理论上谷歌拥有有史以来最大的代码库,每天有成百上千次提交,整个代码库超过 80 TB。 其他已知运营大型单一代码库的公司还有微软、Facebook 和 Twitter。

我们可以在monorepo中添加一个公共项目,用来存放我们的基础组件和utils工具库。

2.通过发布公共包

可以建设一个基础组件库,发包到npm或者公司内部的包管理系统。

monorepo和npm发包各有优缺点,根据自身情况进行选择。

知识的重复

在团队中不只是代码存在重复,在知识层面也会存在重复,比如大家重复的进行某个知识的学习,包括技术上的和业务上的, 整体上增加了团队工作的重复,降低了效率。

针对知识的重复我们可以定期举办内部分享。

1.新功能上线演示

针对一些大的功能上线,可以组织内部的上线演示,一方面增加大家对业务的了解,另一方面,减少大家后续重复学习的问题。

2.定期分享

团队内部定期举办业务、技术、工作方法/效率等方面的分享,减少对知识学习的重复工作。

编码层面的重复

没有意识到重复

有时候重复的代码并不是那么明显,可能只是几行代码,由于重复的行数较少,所以就很自然的采用复制粘贴, 没有意识到重复的发生。

比如针对一个应用的状态判断,应用存在多种状态,比如未安装(Uninstalled)、运行中(Running)、已销毁(Destroyed)等, 只有当应用处于未安装或者已销毁状态才允许安装,而这个条件可能在多处都使用。

// 应用列表页在进行安装操作时进行状态的判断
function install(app) {if (['Uninstalled', 'Destroyed'].includes(app.status)) {}
} 
<!--应用详情页在进行安装时进行状态的判断-->
<template><button v-if="['Uninstalled', 'Destroyed'].includes(app.status)">安装</button>
</template> 

每当安装条件发生变更时,都要四处寻找然后一个一个地方修改,针对这种也应该进行封装,虽然他很小。 我们可以封装一个判断是否能安装的方法,其他地方进行引用。

//抽象一个方法,判断是否能按照
function canInstall(app) {return ['Uninstalled', 'Destroyed'].includes(app.status)
}

//在需要进行状态判断的地方引用封装的函数
function install(app) {if (canInstall(app)) {}
} 

后续安装条件发生变更,只需要修改canInstall方法即可

//需求发生变化,InstallError状态和没有runtimeId也支持安装
function canInstall(app) {return ['Uninstalled', 'Destroyed', 'InstallError'].includes(app.status)|| !app.runtimeId
} 

我们一般对大块的重复代码比较敏感,而对于小块的代码重复,则一般会忽略,但是重复是部分大小的,小段代码很可能在更多的地方使用。

缺少抽象和封装

比如要让你炸毁地球,不应该直接写一个炸毁地球的方法,而是写一个炸毁星球的方法,将地球作为参数传进去。

抽象的东西要比具体的东西复用性更强,因此要想提高复用性,就要对所做的功能进行抽象,而不是面向具体单一的业务需求开发。

示例1:组件的重复

比如在删除k8s资源时需要输入k8s资源的名称进行确认,输入正确后才能进行删除,于是针对这个业务场景封装了一个弹窗确认组件, 在调用显示弹窗方法showDialog时,传入了一个名为k8s资源名称(k8sResourceName)的参数。

<!--删除确认弹窗 DeleteResource.vue 的实现-->
<template><el-dialogtitle="删除确认":visible.sync="dialogVisible"@confirm="deleteResource"><div>请输入<span>{{ k8sResourceName }}</span>确定删除</div><el-input v-model.trim="inputName"/></el-dialog>
</template>

<script>
export default {data() {return {dialogVisible: false,k8sResourceName: '',inputName: ''};},methods: {showDialog(params) {this.k8sResourceName = params.k8sResourceName;this.dialogVisible = true;this.inputName = '';},deleteResource() {if (this.inputName === this.k8sResourceName) {this.dialogVisible = false;this.$emit('delete');}}}
};
</script> 

其实这是一个很常用的功能,可能很多地方都会使用,比如在删除应用页面,也可能用到这个组件进行确认, 那么我们传递一个名为k8sResourceName的参数显然是很不合适的(这里只是简化了这块的实现,实际可能并不是简单改一个名称这么简单)。 显然,并不是写这块代码的同事, 没有能力封装一个通用的组件,而是缺少抽象的思维,导致写出了面向具体单一业务的组件。

试着利用抽象思维,写出更具复用性的代码。

示例2:样式的重复

比如在CSS中,经常会针对某段文字设置字体大小、颜色等,一般情况下,同一个网站的字体大小、颜色是存在共性的, 比如标题的颜色每个页面都是一样的,提示类型的文字颜色也是一样的,如果没有进行抽象,那么我们代码可能是这样的。

.news-title {color: #409EFF;font-size: 16px;
}

.app-title {color: #409EFF;font-size: 16px;
} 

这样的设置会在样式中存在大量的重复,大致有两个解决方法。

第一种就是对整个网站的css进行分层,比如样式分为全局样式、页面样式和组件样式。我们可以在全局样式中抽取共性的css样式。

比如抽取common.css,定义网站通用标题的样式。

.common-title {color: #409EFF;font-size: 16px;
} 

另外一种就是通过抽取一些变量,在页面样式和组件样式中引用这些变量。

比如element中的var.scss

/* Color
-------------------------- */
/// color|1|Brand Color|0
$--color-primary: #409EFF !default;
/// color|1|Background Color|4
$--color-white: #FFFFFF !default;
/// color|1|Background Color|4
$--color-black: #000000 !default;

/* Size
-------------------------- */
$--size-base: 14px !default;

/* z-index
-------------------------- */
$--index-normal: 1 !default;
$--index-top: 1000 !default;
$--index-popper: 2000 !default; 

后续如果想修改网站的配色,只需要修改模板文件即可。

示例3:网络请求的重复

网络请求也是重复容易滋生的地方,比如在一个组件内部请求文章的详情方法getNewsDetail,看看有那几处重复呢?

 <script>
import axios from 'axios'

export default {methods: {getNewsDetail(id) {axios.get('/api/v1/news/detail/' + id).then(res => {this.detail = res}).catch(e => {if (e.code === '401') {showToast('未登录,请先登录')} else {showToast('接口请求失败')}})}}
}
</script> 

上述代码可能存在如下重复问题:

  • 接口地址可能存在重复,别的页面也能会使用文章详情接口,如果接口地址发生变更需要修改多处
  • 请求方法可能存在重复,别的页面如果也要请求文章详情,也需要写一个getNewsDetail方法
  • 错误处理存在重复,多个网络请求可能都要进行相同的错误处理
  • 请求依赖具体的第三方库axios,如果有一天要更换网络请求库,则需要进行大量的修改

接下来我们来逐一解决这些重复。

首先是依赖axios的问题,我们尽量不要直接依赖某个第三方插件,解决办法也很简单,就是增加一层封装, 这样把依赖局限在具体某个方法内部,后续要替换只需要更改一处即可,也就是上层业务不依赖底层实现。 不是axios有什么功能,我的网络请求怎么调用,而是我想怎么调用网络请求,怎么利用axios进行实现, 也就是要遵循"依赖倒置原则"。

我们可以实现一个request方法,来完成网络请求,内部调用axios,同时进行通用的错误处理。

// request.js
import axios from 'axios'

export default function request(config) {return new Promise((function (resolve, reject) {axios(config).then(res => {if (res.code && res.code === 200) {resolve(res)} else if (res.code === 401) {//其他各种错误处理类似showToast("网络请求失败")window.location.href = '/#/login'} else {reject(res)}}).catch(e => {reject(e)})}))
}

['get', 'post', 'put', 'delete'].forEach(method => {return function (url, params, body, options) {return request({method,url,params,body,...options})}
}) 

针对接口重复问题,我们可以创建一个 api.js文件,来配置各个接口地址。

export default {newDetail: '/api/v1/news/detail/:id'// 其他接口地址
} 

针对接口调用重复问题,我们可以封装一个service层,所有页面调用均通过调用service层方法来实现。 service/news.js

import request from './utils/request'
import api from './api'

export default {getNewsDetail(id) {return request.get(api.newDetail, {id}).then(res => {return res.data})}
} 

修改之前和修改之后对比如下,通过增加中间层service.js、封装网络请求方法request.js以及封装接口地址配置常量api.js, 大大降低网络请求的复用,同时也不再依赖第三方axios,实现了解耦。

职责不单一

单一职责的函数或者组件,就像是一个积木块,而不满足单一职责特性的函数或组件就像是一个功能模块。

单一职责的积木块可以进行各种排列组合,绽放出强大的生命力,而多功能的模块,只适用于特定的业务场景,复用性则大大降低。

比如我们要实现一个下载一段文字的功能,这个功能分为两步,第一步将文字内容转为url,第二步通过创建a标签实现下载。 假如实现的downloadText方法如下:

function downloadText(text, filename) {//根据text内容,创建urllet blob = new Blob([content]);let url = URL.createObjectURL(blob);//创建a标签,通过模拟a标签的点击实现下载let eleLink = document.createElement('a');eleLink.download = filename;eleLink.style.display = 'none';eleLink.href = urldocument.body.appendChild(eleLink);eleLink.click();document.body.removeChild(eleLink);
} 

很明显这里不符合单一职责,假如我现在不是根据text文本进行下载,而是给定一个具体src下载,可能还要再写个downloadUrl方法,很明显,二者之间有很多重复。

function downloadText(text, filename) {//根据text内容,创建urllet blob = new Blob([text]);let url = URL.createObjectURL(blob);//创建a标签,通过模拟a标签的点击实现下载let eleLink = document.createElement('a');eleLink.download = filename;eleLink.style.display = 'none';eleLink.href = urldocument.body.appendChild(eleLink);eleLink.click();document.body.removeChild(eleLink);
}

function downloadUrl(url, filename) {//创建a标签,通过模拟a标签的点击实现下载let eleLink = document.createElement('a');eleLink.download = filename;eleLink.style.display = 'none';eleLink.href = urldocument.body.appendChild(eleLink);eleLink.click();document.body.removeChild(eleLink);
} 

根据单一职责原则对downloadText方法进行拆分,可以拆成两个,一个根据文本生成url,一个根据url进行下载,如果还想保留downloadText, 只需要组合这两个小方法即可,这样我们一下就产出了3个通用方法。

 function createUrlByText(text) {let blob = new Blob([text]);return URL.createObjectURL(blob);
}

function downloadUrl(url, filename) {let eleLink = document.createElement('a');eleLink.download = filename;eleLink.style.display = 'none';eleLink.href = urldocument.body.appendChild(eleLink);eleLink.click();document.body.removeChild(eleLink);
}

//对上面的单一职责功能进行组合
function downloadText(text, filename) {let url = createUrlByText(text)downloadUrl(url, filename)
} 

其实功能实现并没有本质的区别,只是简单的进行拆分,使其满足单一职责原则,即大大提高了复用性。

数据之间的重复

数据之间的重复,也是经常出现的一种重复问题,也就是能用1个字段表示的数据,不要用2个或多个字段表示。 通常可以有1个基础的数据字段,其他字段可以通过这个基础字段来计算,而不是维护其他几个额外字段。

比如要实现如下的一个列表,一共有3个字段:列表数据list、已选中数量selectedCount、总数量totalCount。

假如维护这三个字段,每次当列表数据发生变化时(可能是选择状态变化,也可能是增删数据), 都要小心设置selectedCount、totalCount,否则就会出现数据不一致的bug,这其实是一种逻辑上的重复。

我们可以只维护一个基础数据list,selectedCount和totalCount都可以通过对list进行计算而得到, 可能是写一个方法每次刷新组件时重新计算,也可以利用vue中的计算属性。

 <script>
export default {data() {return {list: [{name: '苹果',selected: false},{name: '橘子',selected: true},{name: '香蕉',selected: true}]}},computed: {selectedCount() {return this.list.filter(item => item.selected).length},totalCount() {return this.list.length}}
}
</script> 

这样我们只需要对list进行处理即可,其他两个字段自动计算出来,避免数据不一致问题。

复用和耦合

复用虽好,但是也不能贪杯,因为复用就意味着耦合,不合理的复用会导致严重的耦合,代码可读性及可维护性变差。

不合理的复用,甚至不如不进行复用,所以在进行复用抽象时,应该慎重。

充斥着各种if-else的复用

有的复用并不是真正的复用,而是将一些功能集中到了一个函数内部,然后再内部进行大量的if-else判断,实际是把逻辑复杂性转移到了函数内部, 由于存在大量的分支判断,复杂度呈现指数式增长。

比如应用的安装、重启、销毁、切换版本等都使用一个函数,表面上都复用了operate函数,但是却在函数内部进行大量的分支判断,各自处理各自的事情, 而且由于各个操作需要的传参可能还不一样,导致虽然共用一个函数,但是传参不一样,内部处理逻辑也没有复用,还增加了耦合, 导致想要搞明白某个操作的处理流程,非常复杂,这样的复用有什么意义呢?

<!--不合理的复用-->
<template><div><el-button @click="operate(row, 'install')">部署</el-button><el-button @click="operate(row, 'restart')">重启</el-button><el-button @click="operate(row, 'destroy')">销毁</el-button><el-button @click="operate(row, 'upgrade-version', row.package_version)">切换版本</el-button></div>
</template>
<script>
export default {methods: {operate(row, opt, package_version) {if (opt === 'install') {//...}if (opt === 'destroy') {//...} else if (['install', 'update-values', 'update-default', 'upgrade-version'].includes(opt)) {//...} else {if (['restart'].includes(opt)) {//...} else {//...}}}}
}
</script> 

上面这个,不如针对不同的操作,直接对应一个独立的处理函数,如果多个函数之间有复用,可以抽取出来公共函数,但是每个操作的处理流程是清晰的。 修改之后,每个交互对应的操作是明确的,由于拆分了,传参也简化了,每个操作内部没有了分支判断,逻辑也简化了。

<!--修改之后-->
<template><div><el-button @click="install(row)">部署</el-button><el-button @click="restart(row)">重启</el-button><el-button @click="destroy(row)">销毁</el-button><el-button @click="upgradeVersion(row,row.package_version)">切换版本</el-button></div>
</template>
<script>
export default {methods: {install(row) {//安装处理流程this.commonOperate()},restart(row) {//重启处理流程this.commonOperate()},destroy(row) {//销毁处理流程this.commonOperate()},upgradeVersion(row, version) {//切换版本处理流程this.commonOperate()},commonOperate() {//...}}
}
</script> 

尽量不要复用不同业务的处理流程,不同业务的流程后续很可能向着不同的方向发展,强行复用只会增加复杂度和耦合,我们可以抽象各个流程的公共处理方法, 比如多个操作的接口请求是一致的,但是每个操作的参数是不同的,那么可以在不同的业务处理方法中准备不同的参数,然后调用统一的网络请求方法完成接口调用。

还有一种类似的问题,出现在UI的复用上,根据不同的属性,比如应用的操作类型(安装、重启等待),渲染不同的页面,由于充斥大量的分支, 很难搞明白某个操作对应那些UI,修改某个bug时很容易引入新的bug。

 <template><div><div v-if="operate === 'install'">***</div><div v-else>***</div><div v-if="['install', 'start'].includes(operate)">***</div></div>
</template>
<script>
export default {props: ['operate']
}
</script> 

综上,当遇到有大量分支判断时,就要考虑这个复用是否合理了。

入口文件尽量不要复用

所谓的入口文件就是和用户直接进行交互的文件,比如不同路由对应的页面是一种入口文件,不同按钮对应的handle函数也是一种入口文件, 入口文件耦合着业务逻辑,最好不进行复用。

比如两个路由对应的页面非常相似,如果我们直接复用这个路由文件,那么就会在路由文件的实现中,进行各种if-else判断,来区分环境, 而且很难了解不同路由到底会对页面产生哪些影响。

比如,route1和route2复用User组件,那么User组件内部肯定要进行各种条件判断,来针对route1和route2呈现不同的效果, 那么要问route1和route2的表现在User组件有什么不同,你必须去仔细阅读User的实现才能知晓。

//假设两个页面很相似,复用了路由入口文件
const routes = [{path: 'route1',name: 'route1',component: User,},{path: 'route2',name: 'route2',component: User,},
] 

User组件实现

 <template><div><!-- 入口文件只能通过路由不同的参数来区分到底是从哪个路由进来的,呈现不同效果 --><div v-if="$route.params.test === '**'">**</div><div v-else>**</div></div>
</template>
<script>
export default {mounted() {// 入口文件只能通过路由不同的参数来区分到底是从哪个路由进来的,呈现不同效果if (this.$route.params.test === '**') {//...} else {//...}}
}
</script> 

复用了入口文件后,很难说清楚不同入口有什么区别,如果入口文件不复用,我们可以把公共内容封装成组件,然后在不同的入口文件中进行调用, 调用时传递明确的属性,很清楚整体的逻辑。

比如route1对应User1文件,route2对应User2文件

//假设两个页面很相似,复用了路由入口文件
const routes = [{path: 'route1',name: 'route1',component: User1,},{path: 'route2',name: 'route2',component: User2,},
] 

User1组件实现如下,User1组件中调用组件User,同时传递属性过去

<template><User :can-add="true" :can-delete="false" />
</template> 

User2组件实现如下,User2组件也调用复用的组件User,同时传递所需属性过去

<template><User :can-add="false" :can-delete="true" />
</template> 

User组件实现

<template><div v-if="canAdd">添加</div><div v-if="canDelete">删除</div>
</template>
<script>
export default {props:['canAdd', 'canDelete']
}
</script> 

我们通过抽取User组件,实现了主要功能的复用,同时又避免了入口文件的复用,每个入口文件传递什么属性都很容易看到,也很容易理解其中逻辑。

类似的,不同按钮对应的操作函数尽量不要复用,因为不同的按钮就代表不同的业务流程,很难说多个业务的流程始终能保持一致,可能初始时, 两个按钮操作基本一样,但是随着后续需求变化,这个复用的函数就开始增加各种if-else以应对需求变化,导致可读性越来越差,耦合越来越多。

不能因为长得相似就复用

假如某个管理系统的2个列表页非常相似,比如一个是资讯列表页,一个是商品列表页,它们都有一个添加按钮、一个按名称搜索的输入框,一个表格, 而且表格的列都只有名称、创建人、创建时间三列,那么我们应该复用吗?

很显然,这样的情况是不能复用的,随着业务的发展,资讯和商品,一定会向着两个不同的方向发展,不可避免的后续会增加各种逻辑判断, 而且因为这种耦合还会造成各种意外的bug。

我们应该复用其中的组件单元,比如统一的button组件、搜索表单、表格组件,通过组合基础组件,来完成两个页面的开发。

类似的还有表单校验的规则,比如名称和描述,可能初始的校验规则一致,就进行了复用,但是本质上,名称和描述是不同的业务元素, 他们的校验规则并没有完全的相关性,很可能后续往着不同的方向发展。但是如果是多个表单的同一个业务元素的校验,则是可以复用的, 可以预料到如果该元素在这个页面变化了校验,在其他页面也应该会变化。

是否能复用,要看逻辑上有没有相关性,而不是仅仅因为长得像就复用,复用的代价就是耦合。

最后

整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

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

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

相关文章

vue3 + vite + ts 集成mars3d

vue3 vite ts 集成mars3d 文章目录vue3 vite ts 集成mars3d前言一、创建一个vue3 vite ts项目二、引入mars3d相关依赖三、vite.config.ts 相关配置四、 新建DIV容器 创建地图前言 使用mars3d过程中&#xff0c;需要集成mars3d到自己的项目中&#xff0c;mars3d开发教程…

MFC实现曲柄滑块以及铰链四杆机构的运动仿真

创建MFC应用&#xff0c;选择单文档&#xff0c;在应用程序类型中的应用程序类型选择单文档&#xff0c;在项目样式中选择MFC standard&#xff0c;在用户界面功能中的命令行中选择 使用菜单栏和工具栏。这样选择界面更好看一点&#xff0c;下面给出截图&#xff1a;在资源视图…

代码随想录算法训练营第三十八天 | 理论基础 ,509. 斐波那契数,70. 爬楼梯,746. 使用最小花费爬楼梯

Day36 周日休息~一、参考资料理论基础https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 刷题大纲递推五部曲&#xff1a;确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确…

云计算专业和计算机专业哪个好就业?

云计算专业其实也是属于计算机类专业呢&#xff0c;他包括了计算机硬件设备、计算机网络、磁盘柜、操作系统、中间件、数据库、服务器/虚拟机、应用软件开发等技术内容&#xff0c;云计算技术是以IT服务的形式面向用户的&#xff1b;所以云计算不是一门技术&#xff0c;而是众多…

一个底层30岁的测试员的故事,连躺平都是奢望的....

背景 我是一个生活在某二线城市的测试员&#xff0c;家里面有两位小朋友&#xff0c;老大现在读幼小衔接&#xff0c;明年上小学&#xff0c;老二两岁多一点点&#xff0c;明年上幼儿园。家庭主要开支有房贷、车贷、车位贷、保险、时不时小意外、还有一笔 3万的信用卡分期&…

【亲测】PHP进销存源码 ERP多仓库管理系统 网络版手机端+小程序版进销存 二次开发

可电脑端操作&#xff0c;也可以小程序端操作&#xff0c;非常方便&#xff01;适合小型企业个人使用。 服务端thinkphp5全套开源源码&#xff0c;uniapp前端全套开源 功能 1、支持采购单录入、审核、入库、退货等采购过程中的记录追踪 2、支持销售、出库、销售审核、出库审核…

Smtplib之发邮件模块

目录 创建Smtp对象 Smtp类中的方法 MIME MIMEBase MIMEBase MIMEMultipart MIMEApplication MIMEAudio MIMEImage MIMEText 实例 texthtml格式 发送带图片附件的邮件 发送带附件的邮件 含多种格式 SMTP模块 SMTP 简单传输协议&#xff0c;它是一组用于由源…

JAVA商城源码-多用户商城系统源码-B2B2C商城系统

项目介绍 三勾多商户小程序商城基于springbootelement-uiuniapp打造的面向开发的小程序商城&#xff0c;方便二次开发或直接使用&#xff0c;可发布到多端&#xff0c;包括微信小程序、微信公众号、QQ小程序、支付宝小程序、字节跳动小程序、百度小程序、android端、ios端。 采…

leaflet 导出图片,打印图片(A4横版或竖版)

第093个 点击查看专栏目录 本示例的目的是介绍如何在vue+leaflet中打印图片导出图片。一个简单的leaflet插件示例,添加了一个图标来打印或导出地图。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共85行)安装插…

自然语言处理(NLP)之word2vec的实现(PTB语料库)<找语义相近的词>

在2013年Google开源了一款用于词向量计算的工具&#xff1a;word2vec&#xff0c;它本身不是一种深度学习之类的模型&#xff0c;是一种用于计算词嵌入的体系结构。实际上大家平时说的这个指代的就是前面介绍过的跳字(元)模型与连续词袋模型CBow&#xff1a;自然语言处理(NLP)之…

OpenCV-Python学习(21)—— OpenCV 图像几何变换之图像翻转(cv.flip、np.flip)

1. 学习目标 学习 OpenCV 图像的翻转函数 cv.flip&#xff1b;学习 NumPy 矩阵的反转函数 np.flip&#xff1b;自己实现矩阵反转的函数。 2. OpenCV 翻转 翻转也称镜像&#xff0c;是指将图像沿轴线进行轴对称变换。水平镜像是将图像沿垂直中轴线进行左右翻转&#xff0c;垂直…

写出高质量的前端代码之降低耦合提升正交性

耦合与正交性 什么是耦合 在百度百科中&#xff0c;对耦合的解释 耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。 我曾经买过一个遥控飞机玩具&#xff0c;当我推前进杆的时候&#xff0c;飞机除了前进&#xff0c;还会往左或者往…

字符串匹配--strstr函数的模拟实现思路和代码

一&#xff0c;strstr函数 原型&#xff1a; const char * strstr ( const char * str1, const char * str2 );char * strstr ( char * str1, const char * str2 ); strstr是一个字符串匹配函数&#xff0c;在str1中去寻找str2&#xff0c;如果找到&#xff0c;返回str2在…

科研快讯 | 14篇论文被信号处理领域顶级国际会议ICASSP录用

ICASSP 2023 近日&#xff0c;2023年IEEE声学、语音与信号处理国际会议&#xff08;2023 IEEE International Conference on Acoustics, Speech, and Signal Processing&#xff0c;ICASSP 2023&#xff09;发布录用通知&#xff0c;清华大学人机语音交互实验室&#xff08;TH…

【LSTM】2 多因素单步骤预测

基于时间序列的预测&#xff0c;一定要明白它的原理&#xff0c;不是工作原理&#xff0c;而是工程落地原因。 基于时间序列&#xff0c;以已知回归未知----这两句话是分量很重的。 多因素单步单输出组合 时间序列&#xff1a;t1 是 特征 1,2,3 预测t2 的回归值41 多因素单步多…

当科普展会和VR全景碰撞,会擦出什么样的火花?

你知道科普的重要性吗&#xff1f;一些大城市学生从小到大经历过很多科普展会&#xff0c;帮助青少年从小就树立正确的科学价值观和人生观&#xff0c;那么当科普展会和VR全景碰撞会擦出什么样的火花呢&#xff1f; 在这个信息时代&#xff0c;什么信息都可以在网上搜到&#x…

Java岗面试题--Java并发 计算机网络(日积月累,每日三题)

目录1. 面试题一&#xff1a;在 Java 程序中怎么保证多线程的运行安全&#xff1f;1.1 追问一&#xff1a;Java 线程同步的几种方法&#xff1f;2. 面试题二&#xff1a;JMM3. 面试题三&#xff1a;计算机网络的各层协议及作用&#xff1f;1. 面试题一&#xff1a;在 Java 程序…

大数据导论与Linux基础

目录标题什么是数据数据分析方向数据分析步骤分布式与集群操作系统虚拟机ssh协议Linux常用操作什么是数据 数据&#xff1a;指对官方事件进行记录并可以鉴别的符号 数据如何产生&#xff1a;对客观事物的计量和记录产生数据 数据分析方向 数据分析在企业日常分析中三大方向&…

taobao.top.oaid.client.decrypt( 端侧OAID解密 )

&#xffe5;开放平台免费API不需用户授权 解码OAID(Open Addressee ID)&#xff0c;返回收件人信息。该接口用于客户端直接查看订单隐私数据&#xff0c;解密数据不经过ISV服务器&#xff0c;且包含风控等安全检测。 公共参数 请求地址: HTTP地址&#xff1a;http://gw.api.ta…

async和await用法理解和快速上手 , 同步任务和异步任务顺序安排和轻松理解 , js代码执行顺序表面知道

学习关键语句 : async , await 用法 await 怎么使用 同步任务和异步任务 微任务和宏任务 js中代码执行顺序 写在前面 虽然说 async 和 await 是 Promise 的语法糖 , 但是用惯了Promise 的人(我) , 还真不能超快速使用上这个语法糖 , 所以赶紧写一篇文章出来让各位了解了解这个…