-
最近接手了一个后台管理项目,项目使用了Element-ui组件, 使用了Select 下拉菜单展示并选择内容,使用了cascader做级联城市选择器 看了官方文档我们知道可以使用v-model绑定数据,设置初始值/默认值 <el-form-item label="班级" prop="classId"> <el-select v-model="form.classId" placeholder="请选择班级"> <el-option v-for="(value) of classList" :key="value.classId" :label="value.className" :value="value.classId"></el-option> </el-select> </el-form-item> <el-form-item label="机构地址" prop="district"> <el-cascader style="width: 216px;" v-model="form.district" :options="dataDistrict" @change="handleChange" > </el-cascader> </el-form-item> 问题来了,明明已经设置了v-model但是没有显示,百度发现问题在于数据类型,form.classId的值为 String ,而 el-select列表的 value 为 Number,el-cascader的value是Array,form.district却是JSON字符串,并不全等,导致无法找到对应的项,把数据类型进行转换后就能正常显示啦 ———————————————— 原文链接:https://blog.csdn.net/weixin_43144209/article/details/120891475
-
一、背景 在写项目的过程中,突然发现el-select这个组件,绑定的值没办法显示name,而是直接显示value。在编辑项目的弹框中,要显示项目绑定了哪个数据库(项目外连数据库表主键ID),结果发现直接把关联数据库的ID显示了出来,这与我的设想不一致,因此来找找是什么问题 二、解决 有问题的,有文档的当然第一时间去看文档,一看数据类型是string。 value-key 作为 value 唯一标识的键名,绑定值为对象类型时必填 数据类型string 再去检查项目传值,发现el-option绑定的list中key的类型是string,但v-model绑定的值是int,所以就导致了无法显示正确的名字,而显示主键ID 知道问题解决起来就很简单了,直接parseInt()转一下数据类型 <el-form-item label="归属数据库:" prop="databaseId"> <el-select v-model="projectForm.databaseId" placeholder="归属项目" style="width: 400px"> <el-option v-for="item in databaseList" :key="item.id" :value="parseInt(item.id)" :label="item.name" /> </el-select> </el-form-item> 但是可以的话,还是尽量数据格式一致,如果都是int的话,也是能可以正确显示的,那就无须做额外的处理了 ———————————————— 原文链接:https://blog.csdn.net/weixin_40295575/article/details/124250581
-
前言 最近在开发任务中碰到需要在新增和修改时使用动态加载级联选择器,但是当在修改时设置默认选中项,出现了后端数据返回较慢,导致无法选中和级联框选中了但input框不显示的问题,网上找到的方法也不是很有效,还得使用ref查看组件实例和element-ui cascader源码来寻找解决方法。 完整的实现代码在最后 1. 解决方法 我们知道使用动态加载的级联选择器需要使用lazyLoad函数,那就先开始寻找lazyLoad函数,使用ref查看组件实例,可以看到是在panel下。 再来看看源码,可以发现只需要传入Object数据即可,如:this.$refs.xxx.panel.lazyLoad(val)。 知道了传参和调用,但是传参的参数从哪来?如下图可以看到是从this.$refs.xxx.panel.menus[0]循环比对获取。 如果后端数据返回快的话可以达到要实现的选中效果,如果后端数据返回的较慢就会出现没选中的情况(可以自己在lazyLoad加个一秒延迟测试),这时就需要使用handleExpand(handleExpand也在panel下),并且要在请求结束后执行handleExpand,如何在请求结束时执行handleExpand,我们对代码进行如下修改: 先声明一个waitItemExecution函数并赋给menus[0]循环出的数据,最后在lazyLoad中执行waitItemExecution函数。效果如下图,是不是发现input框没有数据,这还是得去element-ui源码内寻找答案,接着往下看。 当级联选择器选择完毕时,watch下的checkedValue会触发,并执行computePresentContent接着computePresentContent再调用computePresentText。 是不是认为只要对checkedValue进行赋值,input框就有值了?可以试着去查看ref获取的实例下的checkedValue,你会发现已经有值在里面,那为什么没有触发呢?因为!isEqual(val, value)是判断不相等为true,而我们调用的方法不全,导致val和value相等,所以if里面的方法不执行,其实我们只需要调用computePresentText。waitItemExecution函数修改如下: 这样就实现了动态加载级联选择器默认选中的问题。以上的方法只解决了一层和二层的级联情况,超过二层的级联情况不适用,下面将给出最终解决方案代码。 2. 不同层级间的默认选中解决方法 对不同层级的默认选中只需要封装函数并互相调用就可实现(一到N层都可使用),以下是完整代码: <template><div id="app"><el-cascader v-model="current" :props="props" ref="cascader"></el-cascader></div> </template> <script> let id = 0; let list = [{value: '0',}, {value: '1',}, {value: '2',}, {value: '3',}, ] export default {name: 'App',data() {return {current: [],props: {lazy: true,lazyLoad(node, resolve) {const {level} = node;function f() {list = list.map((item, i) => ({label: level ? `${node.value}-${i}` : item.value,value: level ? `${node.value}-${i}` : item.value,leaf: item.value === '2' && level === 0 ? true : `${node.value}-${i}` === '3-2' ? true : level >= 2}))resolve(list)}if (level > 0) {setTimeout(() => {f()node.func && node.func(node)}, 1000)return}f()}}};},mounted() {this.current = ['1', '1-3', '1-3-0']// this.current = ['3', '3-2']// this.current = ['2']// this.current = ['0', '0-2', '0-2-3']for (let item of this.$refs.cascader.panel.menus[0]) {if (item.value === this.current[0]) {this.loadItem(item)return}}},methods: {handleItem(val) {if (val && val instanceof Object) {this.loadItem(val)}},loadItem(val) {val.func = this.waitItemExecutionthis.$refs.cascader.panel.lazyLoad(val)},waitItemExecution(val) {for (let item of val.children) {if (this.current[val.level] === item.value) {this.handleItem(item)break}}this.$refs.cascader.panel.handleExpand(val)this.$refs.cascader.computePresentText()},} } </script> 最后 对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。 ———————————————— 原文链接:https://blog.csdn.net/web22050702/article/details/127299704
-
使用element-ui级联选择器Cascader时遇到的问题。 由于数据量过多,在进行级联选择的时候使用的是动态加载的方式。这一情况在表单中十分常见。正常的级联选择用这一组件十分便捷,但在表单修改编辑时,发现动态加载是无法准确回显的,查阅了很多资料,都比较复杂,这里记录一下我使用的简单方法,如有问题还希望各位大神指正 。因为是动态加载,编辑回显也是需要提前把数据加载出来的,赋值之前列表数据已经有了么? 最后怎么解决了呢?我是听过强制重新渲染来解决的 我的解决方案是偷了个懒,让后台同时给了上次选中的value值和label的值。然后vue文件中data里面新增两个字段来保存,一个是保存value的数组,一个是将label拼接“/”后作为字符串保存起来,当该字符串有值时就把el-cascader的placeholder的值设置为这个字段,并且在placeholder有值时,动态添加一个class将el-cascader中placeholder的字体颜色设置为有输入值的颜色(添加的class >>> .el-input__inner::placeholder { color: #606266;})。想了半天,另辟蹊径,最简单暴力的方法… 捂脸 当然这种操作方式对不开放搜索功能的el-cascader比较友好,反正是动态加载的子节点,搜也搜不出来,如果是加了filterable属性的,一获取焦点的时候placeholder值就没了. 我的解决方案是 编辑状态下用一个el-select回显当前的选择项,当然是要初始化数据的值的, 点击变更, 给这个select隐藏, 把cascader显示出来。 <!--核心是设置key值--><el-cascader :key="resetCascader" v-model="form.orgRank" :props="orgRank" ref='orgRank'></el-cascader>//props: {// userDetailData: {// type: Object,// default: null// }//},data() { return { resetCascader: 0, //记录key值 //其他代码(补充lazyLoad) //orgRank: { // lazy: true, // async lazyLoad (node, resolve) { // const { level } = node // if(!node.data) { // const res = await getOrgList() // const nodes = Array.from(res.data).map(item=>({ // value:item.id, // label:item.name, // leaf: level >= 1 // })) // resolve(nodes) // }else{ // const res = await getOrgRankList(node.data.value) // const nodes = Array.from(res.data).map(item=>({ // value:item.id, // label:item.name, // leaf: level >= 1 // })) // resolve(nodes) // } // } //} }}//watch: {// userDetailData(newValue) {// this.addAndEdit(newValue)// }//},methods: { addAndEdit(userData) { // 通过修改 key 值,实现数据回显,将组件重新渲染 this.resetCascader++ if(userData) { //this.form = { // name: userData.name, // username: userData.username, // email: userData.email, // role: userData.roles[0].id //} this.form.orgRank = [] this.form.orgRank.push(userData.orgId, userData.rank) } },}相关的业务代码已经注释掉了,留下的就是解决这个问题的核心代码了。通过为组件设定key值,使组件重新渲染,监听到需要编辑数据userDetailData的变化后,将key值改变,触发懒加载lazyLoad方法,最后再将数据添加到绑定的v-model中即可。
-
如果你有幸刷到这篇文章,恭喜你,困扰你的问题总算有解决方法了。本篇文章解决你所有级联懒加载回显的问题和修改样式不生效问题。 问题描述: 我的需求是调了三个下拉框接口进行关联。在form表单里进行选择传值都是没问题的。刚开始数据少没有出现问题。第二天数据多了测试提了个bug,三级联动竟然不显示数据了。 然后就是两个周的探索,夜不能寐啊。两个周啊!! 网上给的方法各式各样。甚至有说回显必须有options,使用递归把options的数据填好进行回显。纯属扯淡。 附上代码 html: <el-cascader v-model="data.eventComboBoxValue" :props="cascaderProps" clearable /> js: cascaderProps() { return { lazy: true, lazyLoad: this.lazyLoad }; } methods:{ async lazyLoad(node, resolve) { let level = node.level; let result; switch (level) { case 0: // 类型 let initRes = await eventList(); result = initRes.data.pageResult.list; break; case 1: //一级目录 let firstParams = { upstreamId: node.data.value }; let firstRes = await eventList(firstParams); result = firstRes.data.pageResult.list; break; case 2: // 二级目录 let secondParams = { eventTypeId: node.data.value }; let secondRes = await eventList(secondParams); result = secondRes.data.pageResult.list; result.forEach(item => { item.leaf = level >= 2; }); break; default: result = []; break; } resolve(result); }, } 这样是一般写法,很多小伙伴这样写也不会有问题,但是看下请求数据的列表。某一个列表重复请求了多次。解决方法是在请求前加一层判断,已经有数据了就不要重复请求了。 if (node.children && node.children.length > 0) return; 到了这一步还是没有回显。仔细看你的数据是不是有重复项?比如第一层和第二层id一样?恭喜你,遇到了和我一样的问题。出现问题的原因是。cascader的每一项id都不能重复,否则就不会进行下一层的加载了。和后端商量了几种解决方案。给出最完美的方案。 请求列表的时候多传一个自定义字段,我传的是fromLinkage:1,让后端知道我是在调级联列表,这么做是为了不影响其他模块下拉列表的功能。 然后后端返回一个唯一标识避免重复,我们采用的是拼接列表名称。 把返回的这一项作为你的value值,这样每一层的value都是唯一了。完美解决问题。正常回显。 至此有用过级联el-cascader的伙伴都能解决问题了。那么点赞出门左转,可以摸鱼啦~~ 以下内容偏傻瓜式教学,供尚不能解决问题的新人参考。 综上所述,我点击第二层的时候传参应该传id:3,但是现在传了id:'upstreamid3',因为你原来的value值已经绑定成了后端返回的key。value是从node中拿出来的,那我能不能拿到之前的3传给后端呢?当然可以。 只需在你rosolve的数组里添加一个自定义字段,下一轮你就能从node中拿到你存的flag了! 第二层、第三层请求传给后端。 总结:一般这种回显会出现在编辑、查看弹窗或者路由跳转中,如果你的代码还有问题不能回显。弹窗或者新的页面中中加个v-if每次关闭销毁,获取到详情再创建即可。 如果解决了你的问题请点赞!点赞!让我看看有多少被这个问题折磨的铁子们~~~ 另外新版本的element级联会有一页数据太多展示不下的问题,修改样式竟然没生效,我们会使用的是less,less穿透使用/deep/,scss穿透使用::v-deep。都不生效。 最终解决方案是把scope去掉,成功解决~ 点赞收藏加关注,前端探讨不迷路~~ ———————————————— 原文链接:https://blog.csdn.net/liuwenjie_/article/details/126279731
-
中奖结果公示感谢各位小伙伴参与本次活动,本次活动获奖名单如下:请各位获奖的伙伴在10月16日之前点击此处填写收货地址,如逾期未填写视为弃奖。再次感谢各位小伙伴参与本次活动,欢迎关注华为云DTSE Tech Talk 技术直播更多活动~直播简介【直播主题】0基础玩转OpenTiny跨端跨框架组件库,实现一站式前端进阶【直播时间】2023年10月11日 16:30-18:00【直播专家】莫春辉 华为云前端开发 DTSE技术布道师【直播简介】你还在为构建web页面却不知道选哪个组件库而苦恼吗? 你还在为从Vue2项目升级到Vue3的工作量大而惆怅吗? 你还在为不同框架的组件库由于API不同,需要重复开发而薅头发吗? 本期直播将聚焦于OpenTiny跨端、跨框架、跨版本的TinyVue组件库,其中包含了无渲染Renderless设计理念的实现方式,以及分析业务逻辑的思路及方法。让开发者能够通过一套代码在不同版本、不同框架、不同终端中使用,从而提升开发效率,保证用户在不同场景下的极致交互体验。直播链接:cid:link_1活动介绍【互动方式】直播前您可以在本帖留下您疑惑的问题,专家会在直播时为您解答。直播后您可以继续在本帖留言,与专家互动交流。我们会在全部活动结束后对参与互动的用户进行评选。【活动时间】即日起—2023年10月12日【奖励说明】评奖规则:活动1:直播期间在直播间提出与直播内容相关的问题,对专家评选为优质问题的开发者进行奖励。奖品:华为云定制U型按摩枕活动2:在本帖提出与直播内容相关的问题,由专家在所有互动贴中选出最优问题贴的开发者进行奖励。奖品:华为云定制POLO衫更多直播活动直播互动有礼:官网直播间发口令“华为云 DTSE”抽华为云定制棒球帽、填写问卷抽华为云定制长袖卫衣等好礼分享问卷有礼 :邀请5位朋友以上完成问卷即可获得华为云定制飞盘。老观众专属福利:连续报名并观看DTT直播3期以上抽送华为云DTT定制T恤。【注意事项】1、所有参与活动的问题,如发现为复用他人内容,则取消获奖资格。2、为保证您顺利领取活动奖品,请您在活动公示奖项后2个工作日内私信提前填写奖品收货信息,如您没有填写,视为自动放弃奖励。3、活动奖项公示时间截止2023年10月13日,如未反馈邮寄信息视为弃奖。本次活动奖品将于奖项公示后30个工作日内统一发出,请您耐心等待。4、活动期间同类子活动每个ID(同一姓名/电话/收货地址)只能获奖一次,若重复则中奖资格顺延至下一位合格开发者,仅一次顺延。5、如活动奖品出现没有库存的情况,华为云工作人员将会替换等价值的奖品,获奖者不同意此规则视为放弃奖品。6、其他事宜请参考【华为云社区常规活动规则】。
-
富文本编辑器,如果有旧编辑器,再添加新编辑器,如何实现兼容?
-
在前段代码中引入 weui.css ,weuix.css 和js jquery-weui.min.js就可以使用weui的一些样式了 按照官方的文档中 $.toast("我是文本","text"); 弹出的样式应该如下: 但是我在实际中使用弹出的结果却是这个样子: 为什么 原因肯定是weui的样式和其他的样式冲突了 我们看看 $.toast 这一串代码: t.toast=function(t,a,r) { "function"==typeof a&&(r=a); var o,s="weui-icon-success-no-circle", c=i.duration; "cancel"==a?(o="weui-toast_cancel",s="weui-icon-cancel"):"forbidden"==a?(o="weui-toast--forbidden",s="weui-icon-warn"):"text"==a?o="weui-toast--text":"number"==typeof a&&(c=a), e('<i class="'+s+' weui-icon_toast"></i><p class="weui-toast_content">'+(t||"已经完成")+"</p>",o),setTimeout(function(){n(r)},c)} 大概能看出来 cancel ,forbidden,text 这些都是一些选项,当选择而不同的时候,出现不同的样式 text 应该是没有图标,为什么有图标了 我在页面上设置100,来查看这个样式 可以看到的是图标来源于这一部分 我们选中 i 这个标签,看右侧使用的样式: 果然是其他的样式干扰的 如果我们引入的其他的css作用不大,我们可以找到直接删除了 或者将 important 删除了 因为这次的样式调试用了很长的时间,特此记录 希望对你有所帮助 ———————————————— 原文链接:https://blog.csdn.net/datouniao1/article/details/111311058
-
1. toFixed()方法需注意,保留两位小数,将数值类型的数据改变成了字符串类型2. Math.floor(),不四舍五入 ,向下取整注意,不改变数据类型3. 字符串匹配注意,先将数据转换为字符串,最后再转为数值类型4. 四舍五入保留2位小数(若第二位小数为0,则保留一位小数)注意,数据类型不变5. 四舍五入保留2位小数(不够位数,则用0替补)注意,数据类型变为字符串类型
-
JavaScript(简称“JS”) 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名的,但是它也被用到了很多非浏览器环境中,JavaScript 基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。pageContext.request.contextPath 是一个在JSP页面中使用的表达式,用于获取当前Web应用程序的上下文路径。 具体解释如下: pageContext 是一个隐含对象,可以在JSP页面中直接使用,它提供了对页面上下文的访问。通过pageContext对象,可以获取当前页面的请求对象(request)、响应对象(response)、会话对象(session)等。 request 是pageContext对象的一个属性,它代表当前页面的HTTP请求对象。通过request对象,可以获取关于当前请求的各种信息,例如请求参数、请求头、会话状态等。 contextPath 是request对象的一个属性,它代表当前Web应用程序的上下文路径。上下文路径是Web应用程序在服务器上部署的根路径,它可以是目录名或虚拟目录。例如,如果应用程序的上下文路径为/myapp,那么contextPath的值就是/myapp。 所以,pageContext.request.contextPath 表达式的作用就是获取当前Web应用程序的上下文路径。它通常用于构建相对路径或完整URL,确保生成的链接或表单操作与当前应用程序的部署路径保持一致。 ${pageContext.request.contextPath}是JSP取得绝对路径的方法,等价于<%=request.getContextPath()%> 。 也就是取出部署的应用程序名或者是当前的项目名称。 比如我的项目名称是SSM在浏览器中输入为 http://localhost:8080/SSM/index.jsp ${pageContext.request.contextPath}或<%=request.getContextPath()%>取出来的就是/SSM, 而"/"代表的含义就是 http://localhost:8080 可以使用el表达式,方法一: <c:set var="basePath" value="${pageContext.request.contextPath}"/> 然后获取这个值: ${basePath} 可以使用el表达式,方法二: <c:set var="basePath" value="<%=request.getContextPath()%>"/> 然后获取这个值: ${basePath} 把绝对路径的值以key-value 的形式存在请求范围,可以直接使用{basePath}取出 ———————————————— 版权声明:本文为CSDN博主「茂念」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_43802688/article/details/89532658
-
js的简介 JavaScript(简称“JS”)是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,JavaScript基于原型编程、多范式的动态脚本语言,并且支持面向对象、命令式、声明式、函数式编程范式。 [1] JavaScript在1995年由Netscape公司的Brendan Eich,在网景导航者浏览器上首次设计实现而成。因为Netscape与Sun合作,Netscape管理层希望它外观看起来像Java,因此取名为JavaScript。但实际上它的语法风格与Self及Scheme较为接近。 [2] JavaScript的标准是ECMAScript。截至2012年,所有浏览器都完整的支持ECMAScript 5.1,旧版本的浏览器至少支持ECMAScript 3标准。2015年6月17日,ECMA国际组织发布了ECMAScript的第六版,该版本正式名称为ECMAScript 2015,但通常被称为ECMAScript 6或者ES2015。 在WEB开发中,时常会用到javascript来获取当前页面的url网址信息,在这里是我的一些获取url信息的小总结。 下面我们举例一个URL,然后获得它的各个组成部分:http://i.jb51.net/EditPosts.aspx?opt=1 1、window.location.href(设置或获取整个 URL 为字符串) var test = window.location.href; alert(test); 返回:http://i.jb51.net/EditPosts.aspx?opt=1 2、window.location.protocol(设置或获取 URL 的协议部分) var test = window.location.protocol; alert(test); 返回:http: 3、window.location.host(设置或获取 URL 的主机部分) var test = window.location.host; alert(test); 返回:i.jb51.net 4、window.location.port(设置或获取与 URL 关联的端口号码) var test = window.location.port; alert(test); 返回:空字符(如果采用默认的80端口(update:即使添加了:80),那么返回值并不是默认的80而是空字符) 5、window.location.pathname(设置或获取与 URL 的路径部分(就是文件地址)) var test = window.location.pathname; alert(test); 返回:/EditPosts.aspx 6、window.location.search(设置或获取 href 属性中跟在问号后面的部分) var test = window.location.search; alert(test); 返回:?opt=1 PS:获得查询(参数)部分,除了给动态语言赋值以外,我们同样可以给静态页面,并使用javascript来获得相信应的参数值。 7、window.location.hash(设置或获取 href 属性中在井号“#”后面的分段) var test = window.location.hash; alert(test); 返回:空字符(因为url中没有) 8、js获取url中的参数值 一、正则法 function getQueryString(name) { var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i'); var r = window.location.search.substr(1).match(reg); if (r != null) { return unescape(r[2]); } return null; } // 这样调用: alert(GetQueryString("参数名1")); alert(GetQueryString("参数名2")); alert(GetQueryString("参数名3")); 二、split拆分法 function GetRequest() { var url = location.search; //获取url中"?"符后的字串 var theRequest = new Object(); if (url.indexOf("?") != -1) { var str = url.substr(1); strs = str.split("&"); for(var i = 0; i < strs.length; i ++) { theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]); } } return theRequest; } var Request = new Object(); Request = GetRequest();<br>// var id=Request["id"]; // var 参数1,参数2,参数3,参数N; // 参数1 = Request['参数1']; // 参数2 = Request['参数2']; // 参数3 = Request['参数3']; // 参数N = Request['参数N']; 三、指定取 比如说一个url:http://i.jb51.net/?j=js,我们想得到参数j的值,可以通过以下函数调用。 function GetQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i"); var r = window.location.search.substr(1).match(reg); //获取url中"?"符后的字符串并正则匹配 var context = ""; if (r != null) context = r[2]; reg = null; r = null; return context == null || context == "" || context == "undefined" ? "" : context; } alert(GetQueryString("j")); 四、单个参数的获取方法 function GetRequest() { var url = location.search; //获取url中"?"符后的字串 if (url.indexOf("?") != -1) { //判断是否有参数 var str = url.substr(1); //从第一个字符开始 因为第0个是?号 获取所有除问号的所有符串 strs = str.split("="); //用等号进行分隔 (因为知道只有一个参数 所以直接用等号进分隔 如果有多个参数 要用&号分隔 再用等号进行分隔) alert(strs[1]); //直接弹出第一个参数 (如果有多个参数 还要进行循环的) } } javascript 获取当前 URL 参数的两种方法 //返回的是字符串形式的参数,例如:class_id=3&id=2& function getUrlArgStr(){ var q=location.search.substr(1); var qs=q.split('&'); var argStr=''; if(qs){ for(var i=0;i<qs.length;i++){ argStr+=qs[i].substring(0,qs[i].indexOf('='))+'='+qs[i].substring(qs[i].indexOf('=')+1)+'&'; } } return argStr; } //返回的是对象形式的参数 function getUrlArgObject(){ var args=new Object(); var query=location.search.substring(1);//获取查询串 var pairs=query.split(",");//在逗号处断开 for(var i=0;i<pairs.length;i++){ var pos=pairs[i].indexOf('=');//查找name=value if(pos==-1){//如果没有找到就跳过 continue; } var argname=pairs[i].substring(0,pos);//提取name var value=pairs[i].substring(pos+1);//提取value args[argname]=unescape(value);//存为属性 } return args;//返回对象 } 另外列出一些 javascript 获取url中各个部分的功能方法: window.location.host; //返回url 的主机部分,例如:www.xxx.com window.location.hostname; //返回www.xxx.com window.location.href; //返回整个url字符串(在浏览器中就是完整的地址栏),例如:www.xxx.com/index.php?class_id=3&id=2 window.location.pathname; //返回/a/index.php或者/index.php window.location.protocol; //返回url 的协议部分,例如: http:,ftp:,maito:等等。 window.location.port //url 的端口部分,如果采用默认的80端口,那么返回值并不是默认的80而是空字符 以上就是本文的全部内容,希望对大家理解如何获取当前页面url网址信息有所帮助。 原文链接:https://www.jb51.net/article/82519.htm
-
PM:喂,那个切图仔,我这里有个100G的视频要上传,你帮我做一个上传后台,下班前给我哦,辛苦了。 我:。。。 相信每个切图工程师,都接触过文件上传的需求,一般的小文件,我们直接使用 input file,然后构造一个 new FormData()对象,扔给后端就可以了。如果使用了 Ant design 或者 element ui 之类的ui库,那更简单,直接调用一下api即可。当然了,复杂一些的,市面上也有不少优秀的第三方插件,比如WebUploader。但是作为一个有追求的工程师,怎么能仅仅满足于使用插件呢,今天我们就来自己实现一个。 首先我们来分析一下需求 一个上传组件,需要具备的功能: 需要校验文件格式 可以上传任何文件,包括超大的视频文件(切片) 上传期间断网后,再次联网可以继续上传(断点续传) 要有进度条提示 已经上传过同一个文件后,直接上传完成(秒传) 前后端分工: 前端: 文件格式校验 文件切片、md5计算 发起检查请求,把当前文件的hash发送给服务端,检查是否有相同hash的文件 上传进度计算 上传完成后通知后端合并切片 后端: 检查接收到的hash是否有相同的文件,并通知前端当前hash是否有未完成的上传 接收切片 合并所有切片 架构图如下 接下来开始具体实现 一、 格式校验 对于上传的文件,一般来说,我们要校验其格式,仅需要获取文件的后缀(扩展名),即可判断其是否符合我们的上传限制: //文件路径 var filePath = "file://upload/test.png"; //获取最后一个.的位置 var index= filePath.lastIndexOf("."); //获取后缀 var ext = filePath.substr(index+1); //输出结果 console.log(ext); // 输出:png 但是,这种方式有个弊端,那就是我们可以随便篡改文件的后缀名,比如:test.mp4 ,我们可以通过修改其后缀名:test.mp4 -> test.png ,这样即可绕过限制进行上传。那有没有更严格的限制方式呢?当然是有的。 那就是通过查看文件的二进制数据来识别其真实的文件类型,因为计算机识别文件类型时,并不是真的通过文件的后缀名来识别的,而是通过 “魔数”(Magic Number)来区分,对于某一些类型的文件,起始的几个字节内容都是固定的,根据这几个字节的内容就可以判断文件的类型。借助十六进制编辑器,可以查看一下图片的二进制数据,我们还是以test.png为例:由上图可知,PNG 类型的图片前 8 个字节是 0x89 50 4E 47 0D 0A 1A 0A。基于这个结果,我们可以据此来做文件的格式校验,以vue项目为例: <template> <div> <input type="file" id="inputFile" @change="handleChange" /> </div> </template> <script> export default { name: "HelloWorld", methods: { check(headers) { return (buffers, options = { offset: 0 }) => headers.every( (header, index) => header === buffers[options.offset + index] ); }, async handleChange(event) { const file = event.target.files[0]; // 以PNG为例,只需要获取前8个字节,即可识别其类型 const buffers = await this.readBuffer(file, 0, 8); const uint8Array = new Uint8Array(buffers); const isPNG = this.check([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); // 上传test.png后,打印结果为true console.log(isPNG(uint8Array)) }, readBuffer(file, start = 0, end = 2) { // 获取文件的二进制数据,因为我们只需要校验前几个字节即可,所以并不需要获取整个文件的数据 return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { resolve(reader.result); }; reader.onerror = reject; reader.readAsArrayBuffer(file.slice(start, end)); }); } } }; </script> 以上为校验文件类型的方法,对于其他类型的文件,比如mp4,xsl等,大家感兴趣的话,也可以通过工具查看其二进制数据,以此来做格式校验。 以下为汇总的一些文件的二进制标识: 1.JPEG/JPG - 文件头标识 (2 bytes): ff, d8 文件结束标识 (2 bytes): ff, d9 2.TGA - 未压缩的前 5 字节 00 00 02 00 00 - RLE 压缩的前 5 字节 00 00 10 00 00 3.PNG - 文件头标识 (8 bytes) 89 50 4E 47 0D 0A 1A 0A 4.GIF - 文件头标识 (6 bytes) 47 49 46 38 39(37) 61 5.BMP - 文件头标识 (2 bytes) 42 4D B M 6.PCX - 文件头标识 (1 bytes) 0A 7.TIFF - 文件头标识 (2 bytes) 4D 4D 或 49 49 8.ICO - 文件头标识 (8 bytes) 00 00 01 00 01 00 20 20 9.CUR - 文件头标识 (8 bytes) 00 00 02 00 01 00 20 20 10.IFF - 文件头标识 (4 bytes) 46 4F 52 4D 11.ANI - 文件头标识 (4 bytes) 52 49 46 46 二、 文件切片 假设我们要把一个1G的视频,分割为每块1MB的切片,可定义 DefualtChunkSize = 1 * 1024 * 1024,通过 spark-md5来计算文件内容的hash值。那如何分割文件呢,使用文件对象File的方法File.prototype.slice即可。 需要注意的是,切割一个较大的文件,比如10G,那分割为1Mb大小的话,将会生成一万个切片,众所周知,js是单线程模型,如果这个计算过程在主线程中的话,那我们的页面必然会直接崩溃,这时,就该我们的 Web Worker 来上场了。 Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。具体的作用,不了解的同学可以自行去学些一下。这里就不展开讲了。 以下为部分关键代码: // upload.js // 创建一个worker对象 const worker = new worker('worker.js') // 向子线程发送消息,并传入文件对象和切片大小,开始计算分割切片 worker.postMessage(file, DefualtChunkSize) // 子线程计算完成后,会将切片返回主线程 worker.onmessage = (chunks) => { ... } 子线程代码: // worker.js // 接收文件对象及切片大小 onmessage (file, DefualtChunkSize) => { let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, chunks = Math.ceil(file.size / DefualtChunkSize), currentChunk = 0, spark = new SparkMD5.ArrayBuffer(), fileReader = new FileReader(); fileReader.onload = function (e) { console.log('read chunk nr', currentChunk + 1, 'of'); const chunk = e.target.result; spark.append(chunk); currentChunk++; if (currentChunk < chunks) { loadNext(); } else { let fileHash = spark.end(); console.info('finished computed hash', fileHash); // 此处为重点,计算完成后,仍然通过postMessage通知主线程 postMessage({ fileHash, fileReader }) } }; fileReader.onerror = function () { console.warn('oops, something went wrong.'); }; function loadNext() { let start = currentChunk * DefualtChunkSize, end = ((start + DefualtChunkSize) >= file.size) ? file.size : start + DefualtChunkSize; let chunk = blobSlice.call(file, start, end); fileReader.readAsArrayBuffer(chunk); } loadNext(); } 以上利用worker线程,我们即可得到计算后的切片,以及md5值。 三、 断点续传+秒传+上传进度计算 在拿到切片和md5后,我们首先去服务器查询一下,是否已经存在当前文件。 如果已存在,并且已经是上传成功的文件,则直接返回前端上传成功,即可实现"秒传"。 如果已存在,并且有一部分切片上传失败,则返回给前端已经上传成功的切片name,前端拿到后,根据返回的切片,计算出未上传成功的剩余切片,然后把剩余的切片继续上传,即可实现"断点续传"。 如果不存在,则开始上传,这里需要注意的是,在并发上传切片时,需要控制并发量,避免一次性上传过多切片,导致崩溃。 // 检查是否已存在相同文件 async function checkAndUploadChunk(chunkList, fileMd5Value) { const requestList = [] // 如果不存在,则上传 for (let i = 0; i < chunkList; i++) { requestList.push(upload({ chunkList[i], fileMd5Value, i })) } // 并发上传 if (requestList?.length) { await Promise.all(requestList) } } // 上传chunk function upload({ chunkList, chunk, fileMd5Value, i }) { current = 0 let form = new FormData() form.append("data", chunk) //切片流 form.append("total", chunkList.length) //总片数 form.append("index", i) //当前是第几片 form.append("fileMd5Value", fileMd5Value) return axios({ method: 'post', url: BaseUrl + "/upload", data: form }).then(({ data }) => { if (data.stat) { current = current + 1 // 获取到上传的进度 const uploadPercent = Math.ceil((current / chunkList.length) * 100) } }) } 在以上代码中,我们在上传切片的同时,也会告诉后端当前上传切片的index,后端接收后,记录该index以便在合并时知道切片的顺序。 当所有切片上传完成后,再向后端发送一个上传完成的请求,即通知后端把所有切片进行合并,最终完成整个上传流程。 大功告成!由于篇幅有限,本文主要讲了前端的实现思路,最终落地成完整的项目,还是需要大家根据真实的项目需求来实现。 ———————————————— 版权声明:本文为CSDN博主「大转转FE」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/P6P7qsW6ua47A2Sb/article/details/125175992
-
1. 场景描述:因为生产环境组件服务进程执行缓慢导致部分资源无法释放,进而引起了各种任务超时。研究源码发现,部分执行过长的是操作都是在单线程中串行 。这就意味着,单个过程的执行效率会影响到整个流程的调度周期。为此,我们需要对部分组件源码进行改造,串行改并行。串行改并行,我们要小心流程执行顺序之间的依赖关系和各个线程对共享变量的读写。这里的前提是我们得知道改哪里。其实我们的需求很明确,执行过程耗时长的我们优先改造,也就是我们需要找到组件的性能瓶颈。但是如何确认呢?最终,我是通过火焰图观察,找出了耗时比较长的过程。下面记录一下这个过程,以便日后需要的时候可以有所帮助。 2. 火焰图火焰图是通过可交互的图片反映出程序执行过程中的调用栈和大致的CPU占用百分比。在火焰图中,顺着Y轴从下往上看表示的是调用顺序,高度表示的就是调用栈的深度。顺着X轴看,一个方法的长度表示的就是这个方法的被抽到的样本数,被抽到的次数越多,占用的CPU时间就越长。当把鼠标放在一个方法上时,会显示一段说明,例如(5222 samples, 7.6%),这个表示的就是,这个方法在采样期间被采样了5222次,占用总样本的7.6%。注意X轴不表示时间,不是说左边的方法一定在右边之前执行。火焰图应该是高低起伏的,如果都是很宽的平顶,那程序性能就需要重新评测了。另外,火焰图的颜色没有任何含义。火焰图是可交互的,在浏览器中我们打开火焰图之后可以直接CTRL+F进行搜索。输入完整的方法名或者正则表达式之后,所有匹配到的方法都是高亮显示出来。 单击这些高亮部分就会放大这一部分,显示选中方法的子方法(被调用的方法)。这样就可以在这张图中搜索自己想要确认的模块,然后找到比较宽的部分,定位到代码,确定是否可以修改。 3. 生成火焰图3.1 工具火焰图生成工具:javaPfro.zip 提取码:76o9这个工具包解压之后会用两个文件夹:async-profiler-master FlameGraph-master文件夹下部分可执行文件,可能需要755权限。这里不做一一声明,直接更改文件夹下所有文件的权限。chmod -R 755 async-profiler-master/*chmod -R 755 FlameGraph-master/*3.2 操作步骤火焰图只是以图片形式展示出了CPU中的调用栈,实际的数据还是需要从CPU中采集。所以生成火焰图共分为两步:采样和生成svg图。3.2.1 采样采样使用async-profiler-master工具,具体命令如下:./async-profiler-master/profiler.sh -d 10 -o collapsed -f /tmp/collapsed.txt 1234以上命令表示的意思是,采样时间为10秒,采样得到的数据重定向到/tmp/collapsed.txt,被采样进程id为1234。 3.2.2 生成svg图根据采样数据生成svg图,使用的是FlameGraph-master,具体命令如下:./FlameGraph-master/flamegraph.pl --colors=java /tmp/collapsed.txt > flamegraph.svg命令表示的意思是把/tmp/collapsed.txt这个文件转换svg火焰图,文件名为flamegraph.svg。然后,把这个文件拉到window环境下,用浏览器就可以打开了。这里我用的是chrome浏览器。 4. 分析火焰图分析火焰图一般需要先确定需要分析的类或方法名,或者代码入口方法,或者入口类。这样我们可以先定位到一个方法调用,再分析其调用栈。例如,在这张图片中,我确定的方法是定义在CoordinatorRunnable类中的,就可以直接搜索这个类。定位到这个类我们可以明显地看到有三个方法占用了大部分的时间。再继续追踪调用栈。可以看出,耗时大部分都花费在集合元素的比较上。这里我们如果需要优化的话,可以考虑一下比较器的逻辑或者可以根据场景判断一下是否可以选择其他的集合等等。转自 https://zhuanlan.zhihu.com/p/96245085
-
批量创建工作表/簿 批量创建工作表: function 批量创建工作表(){ for (var city of ["成都","上海","北京"]){ let sht = Worksheets.Add(); sht.Name = city; } } function 批量创建工作簿(){ for (var city of ["成都","上海","北京"]){ book = Workbooks.Add(); book.SaveAs(`E:/tmp/${city}`); book.Close(); } } 若book.SaveAs不传入全路径,则默认可能保存到我的文档文件夹中。 简单的方法就是直接筛选再复制粘贴,但是现在我们需要完全基于js宏的语法实现: function 分数筛选(){ var Arr1=[]; var Arr2=Range("a2:b13").Value(); for (var row of Arr2){ if (row[1]>=100){ Arr1.push(row); } } Range("d4").Resize(Arr1.length,2).Value2=Arr1 } function 工资条制作() { Application.DisplayAlerts=false; let src=Sheets("工资表"); for(let sht of Sheets){ if(sht.Name=="结果") sht.Delete(); } Application.DisplayAlerts=true; var sht=Worksheets.Add(); sht.Name="结果"; for(i=0;i<10;i++) { src.Range("a1:m4").Copy(); sht.Cells.Item(5*i+1,1).PasteSpecial(); src.Range(`A${i+5}:M${i+5}`).Copy(); sht.Cells.Item(5*i+5,1).PasteSpecial(); } sht.Activate() } 注意:如果原表不止10人,则修改循环次数即可。 如果允许直接在原表上修改,可以使用相对引用录制宏得到代码: function 工资条制作2() { Range("a1:m4").Select(); for (var i=1;i<=9;i++){ Selection.Copy(); ActiveCell.Offset(5, 0).Range("A1:M4").Insert(xlShiftDown, undefined); Application.CutCopyMode = false; ActiveCell.Offset(5, 0).Range("A1:M4").Select(); } } 任意多列SN号合并到一列 原数据都是如下格式的SN号: 下面我们考虑通过宏代码合并到一列,代码如下: function 单表多列合并到一列新文件() { var vs=Range("A1").CurrentRegion.Value().flat(); // 过滤掉空值并去重 vs=Array.from(new Set(vs.filter(v=>v!=undefined))); // 一维数组默认赋值给一行,赋值给一列需要先转置二维 vs = WorksheetFunction.Transpose(vs); var wb = Workbooks.Add(); wb.Sheets(1).Range("A1").Resize(vs.length,1).Value2 = vs; } 注意:上面的代码使用JavaScript的语法对数组进行了去重。 Array.from(new Set(arr)) arr.filter(v=>v!=undefined) 批量合并工作表并添加来源表名 function 合并工作表数据(){ var NewArr=[],n=1; for (var ws of Sheets){ var Arr=ws.Range("a1").CurrentRegion.Value(); if (n++ ==1){var title=Arr[0].concat("工作表名")}; delete Arr[0] Arr.forEach(ar=>NewArr.push(ar.concat(ws.Name))); } NewArr.unshift(title); var wb=Workbooks.Add(); wb.Sheets(1).Range("a1").Resize(NewArr.length,NewArr[0].length).Value2=NewArr; } 语音朗读 代码如下: function Workbook_Open() { Application.Speech.Speak("美好的一天就从这一刻开始吧!",true) } function Workbook_NewSheet(Sh) { Application.Speech.Speak("果然狠人老表,区区几张sheet是不够你消遣的!",true) } function Application_WorkbookBeforeClose(Wb, Cancel) { Application.Speech.Speak("就想问老板,可以下班了吗?") } function Application_SheetSelectionChange(Sh, Target){ if(Sh.Name!="词汇"|Target.Value()==undefined) return; Application.Speech.Speak(Target.Value()); } 保存上面的代码后,当打开或关闭该文件或新建工作表都会朗读对应的文字。 对于词汇这张工作表,点击任何有值的单元格都会对其进行朗读。 Application.Speech.Speak第二个参数传入true表示异步,默认为同步。 富文本弹窗 alert函数支持传入HTML: function testAlert_CSS(){ let foo = ` <h3 style="color:red">Hi</h3> <p style="color:green;text-shadow: 1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue;"> Hi ${new Date().toLocaleDateString()} </p>`; alert(foo) } 判断目标是否在指定区域内 function _m_isInArea(uArea,cell){ uArea = typeof(uArea) =="string" ? Range(uArea):uArea; cell = typeof(cell) =="string" ? Range(cell):cell; if(uArea.Parent.Name!=cell.Parent.Name) return false; let s_row=uArea.Row,e_row=s_row+uArea.Rows.Count-1; let s_col=uArea.Column,e_col=s_col+uArea.Columns.Count-1; let t_row = cell.Row,t_col = cell.Column; // Console.log(`${s_row}-${e_row},${s_col}-${e_col},(${t_row},${t_col})`); return s_row<=t_row && t_row<=e_row && s_col<=t_col && t_col<=e_col; } function _m_test(){ Console.log(_m_isInArea("A2:C20","B3")); Console.log(_m_isInArea("A2:C20","D3")); } 结果: true false 本地文件读写 function read_write_data_txt(){ let txtFile = "E:/tmp/a.txt"; let f = FreeFile() ; Open(txtFile,f,jsOutput,jsWrite); Write(f,"123"); Write(f,"456,789"); Write(f,"aaa,bbb"); Close(); Console.clear() let fNumber = FreeFile() Open(txtFile, fNumber,jsInput) while(!EOF(fNumber)) { let p=LineInput(fNumber); Console.log(p) } Close(fNumber) } "123" "456,789" 生成文件: JavaScript语法补充 undefined和null的区别 undefined和null的区别(值相等,但类型不等) : typeof undefined // undefined typeof null // object null === undefined // false null == undefined // true typeof操作符 typeof 操作符可以检测变量的数据类型 : typeof "John" // 返回 string typeof 3.14 // 返回 number typeof NaN // 返回 number typeof false // 返回 boolean typeof [1,2,3,4] // 返回 object typeof {name:'John', age:34} // 返回 object typeof new Date() // 返回 object typeof function () {} // 返回 function typeof myCar // 返回 undefined (如果 myCar 没有声明) typeof null // 返回 object 解构赋值 var [x, y, z] = ['hello', 'JavaScript', 'ES6']; console.log('x = ' + x + ', y = ' + y + ', z = ' + z); // x = hello, y = JavaScript, z = ES6 [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; console.log('x = ' + x + ', y = ' + y + ', z = ' + z); // x = hello, y = JavaScript, z = ES6 常用方法 方法 描述 concat() 连接两个或更多的数组,并返回结果。 copyWithin() 从数组的指定位置拷贝元素到数组的另一个指定位置中。 entries() 返回数组的可迭代对象。 every() 检测数值元素的每个元素是否都符合条件。 fill() 使用一个固定值来填充数组。 filter() 检测数值元素,并返回符合条件所有元素的数组。 find() 返回符合传入测试(函数)条件的数组元素。 findIndex() 返回符合传入测试(函数)条件的数组元素索引。 forEach() 数组每个元素都执行一次回调函数。 from() 通过给定的对象中创建一个数组。 includes() 判断一个数组是否包含一个指定的值。 indexOf() 搜索数组中的元素,并返回它所在的位置。 isArray() 判断对象是否为数组。 join() 把数组的所有元素放入一个字符串。 keys() 返回数组的可迭代对象,包含原始数组的键(key)。 lastIndexOf() 搜索数组中的元素,并返回它最后出现的位置。 map() 通过指定函数处理数组的每个元素,并返回处理后的数组。 pop() 删除数组的最后一个元素并返回删除的元素。 push() 向数组的末尾添加一个或更多元素,并返回新的长度。 reduce() 将数组元素计算为一个值(从左到右)。 reduceRight() 将数组元素计算为一个值(从右到左)。 reverse() 反转数组的元素顺序。 shift() 删除并返回数组的第一个元素。 slice() 选取数组的的一部分,并返回一个新数组。 some() 检测数组元素中是否有元素符合指定条件。 sort() 对数组的元素进行排序。 splice() 从数组中添加或删除元素。 unshift() 向数组的开头添加一个或更多元素,并返回新的长度。 splice: splice()方法可以从指定的索引开始删除若干元素,然后再从该位置添加若干元素: var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle']; // 从索引2开始删除3个元素,然后再添加两个元素: arr.splice(2, 3, 'Google', 'Facebook'); // 返回删除的元素 ['Yahoo', 'AOL', 'Excite'] arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle'] // 只删除,不添加: arr.splice(2, 2); // ['Google', 'Facebook'] arr; // ['Microsoft', 'Apple', 'Oracle'] // 只添加,不删除: arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因为没有删除任何元素 arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle'] concat: concat()方法把当前的Array和另一个Array连接起来,并返回一个新的Array: var arr = ['A', 'B', 'C']; var added = arr.concat([1, 2, 3]); added; // ['A', 'B', 'C', 1, 2, 3] arr; // ['A', 'B', 'C'] 请注意,concat()方法并没有修改当前Array,而是返回了一个新的Array。 实际上,concat()方法可以接收任意个元素和Array,并且自动把Array拆开,然后全部添加到新的Array里: var arr = ['A', 'B', 'C']; arr.concat(1, 2, [3, 4]); // ['A', 'B', 'C', 1, 2, 3, 4] join: join()方法把当前Array的每个元素都用指定的字符串连接起来,然后返回连接后的字符串: var arr = ['A', 'B', 'C', 1, 2, 3]; arr.join('-'); // 'A-B-C-1-2-3' 如果Array的元素不是字符串,将自动转换为字符串后再连接。 map函数遇到的问题 map的语法: array.map(function(currentValue,index,arr), thisValue) currentValue: 当前元素的值 index : 当前元素的索引值 arr : 前元素属于的数组对象 thisValue:用作 “this” 的值。如果省略了 thisValue,或者传入 null、undefined,那么回调函数的 this 为全局对象。 利用map()把字符串变成整数 : var arr = ['1', '2', '3']; var r = arr.map(parseInt); console.log(r); //结果却是1, NaN, NaN 这是因为parseInt接受两个参数(string, radix),第二个参数指定被转换的“数字”多少进制。 当arr = [1,2,3]时,arr.map(parseInt)实际为: parseInt('1', 0); // 按十进制转换'1' parseInt('2', 1); // 按一进制转换'2',但一进制中只有0没有1 parseInt('3', 2); // 按二进制转换3,但二进制中只有0和1没有2 解决办法: var arr = ['1', '2', '3']; var r = arr.map(str=>parseInt(str)); console.log(r); // 结果:1,2,3 模板字符串 var name = '小华'; var age = 20; var message = `你好, ${name}, 你今年${age}岁了!`; 1 2 3 循环语句 for循环 语法: for (语句 1; 语句 2; 语句 3) { 被执行的代码块 } 语句1 (代码块)开始前执行 语句2 定义运行循环(代码块)的条件 语句3 在循环(代码块)已被执行之后执行 for (var i=0,len=cars.length; i<len; i++) { document.write(cars[i] + "<br>"); } // 或 var i=2,len=cars.length; for (; i<len; i++) { document.write(cars[i] + "<br>"); } for…in循环 for/in 语句循环遍历对象的属性: var person={fname:"John",lname:"Doe",age:25}; for (x in person) // x 为属性名 { txt=txt + person[x]; } 循环遍历Array的索引: var a = ['A', 'B', 'C']; for (var i in a) { console.log(i); // '0', '1', '2' console.log(a[i]); // 'A', 'B', 'C' } 注意:for ... in对Array的循环得到的是String而不是Number。 for…of循环 具有iterable类型的集合还可以通过for ... of循环来遍历,它是ES6引入的新的语法。 var a = ['A', 'B', 'C']; var s = new Set(['A', 'B', 'C']); var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); for (var x of a) { // 遍历Array console.log(x); } for (var x of s) { // 遍历Set console.log(x); } for (var x of m) { // 遍历Map console.log(x[0] + '=' + x[1]); } for of循环和for in循环的区别 for ... in循环遍历的实际是对象的属性名称,手动给Array对象添加了额外的属性后: 当我们手动给Array对象添加了额外的属性后,for ... in循环将带来意想不到的意外效果: var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x in a) { console.log(x); // '0', '1', '2', 'name' } for ... of循环则只循环集合本身的元素: var a = ['A', 'B', 'C']; a.name = 'Hello'; for (var x of a) { console.log(x); // 'A', 'B', 'C' } iterable内置的forEach方法 var a = ['A', 'B', 'C']; a.forEach(function (element, index, array) { // element: 指向当前元素的值 // index: 指向当前索引 // array: 指向Array对象本身 console.log(element + ', index = ' + index); }); var s = new Set(['A', 'B', 'C']); //Set没有索引,因此回调函数的前两个参数都是元素本身 s.forEach(function (element, sameElement, set) { console.log(element); }); var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]); //Map的回调函数参数依次为value、key和map本身 m.forEach(function (value, key, map) { console.log(value); }); 回调函数可以省略参数: var a = ['A', 'B', 'C']; a.forEach(function (element) { console.log(element); }); while 循环 语法: while (条件) { 需要执行的代码 } // 或 do { 需要执行的代码 } while (条件); 例子: while (i<5) { x=x + "The number is " + i + "<br>"; i++; } do { x=x + "The number is " + i + "<br>"; i++; } while (i<5); 箭头函数与装饰器 ES6 新增了箭头函数,语法 : (参数1, 参数2, …, 参数N) => { 函数声明 } 单一参数 => {函数声明} () => {函数声明} 例子: const x = (x, y) => x * y; const x = (x, y) => { return x * y }; 箭头函数简化了函数定义,如果只包含一个表达式,可以省略{ ... }和return。 如果要返回一个对象,单表达式会报错: // SyntaxError: x => { foo: x } 因为和函数体的{ ... }有语法冲突,所以要改为: // ok: x => ({ foo: x }) 箭头函数看上去是匿名函数的一种简写,但有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。 箭头函数的this总是指向词法作用域,也就是外层调用者obj: var obj = { birth: 1990, getAge: function () { var b = this.birth; // 1990 var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象 return fn(); } }; obj.getAge(); // 25 由于this在箭头函数中已经按照词法作用域绑定了,所以,用call()或者ap***()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略: var obj = { birth: 2000, getAge: function (year) { var b = this.birth; // 1990 var fn = (y) => y - this.birth; // this.birth仍是2000 return fn.call({birth:2010}, year); } }; obj.getAge(2015); // 15 在一个独立的函数调用中,根据是否是strict模式,this指向undefined或window。 要指定函数的this指向哪个对象,可以用函数本身的ap***方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。 用ap***修复getAge()调用: function getAge() { var y = new Date().getFullYear(); return y - this.birth; } var xiaoming = { name: '小明', birth: 1990, age: getAge }; xiaoming.age(); // 25 getAge.ap***(xiaoming, []); // 25, this指向xiaoming, 参数为空 利用ap***()可实现装饰器,比如想统计一下代码一共调用了多少次: 'use strict'; var count = 0; var oldParseInt = parseInt; // 保存原函数 window.parseInt = function () { count += 1; return oldParseInt.ap***(null, arguments); // 调用原函数 }; parseInt('10'); parseInt('20'); parseInt('30'); console.log('count = ' + count); // 3 call()与ap***()类似,唯一区别是: ap***()把参数打包成Array再传入; call()把参数按顺序传入。 比如调用Math.max(3, 5, 4),分别用ap***()和call()实现如下: Math.max.ap***(null, [3, 5, 4]); // 5 Math.max.call(null, 3, 5, 4); // 5 对普通函数调用,通常把this绑定为null。 ———————————————— 版权声明:本文为CSDN博主「小小明-代码实体」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/as604049322/article/details/127097880
-
JavaScript中的事件监听是Web开发中非常重要的一个概念。它允许我们在特定的事件发生时执行特定的代码,从而实现交互效果和动态行为。本篇博客将详细介绍JavaScript事件监听的用法,并举例说明。 事件监听的基本概念 在JavaScript中,事件监听是通过addEventListener()方法实现的。该方法的基本语法如下: element.addEventListener(event, function, useCapture); 1 其中,element指的是要监听的HTML元素,event指的是要监听的事件类型,function指的是在事件发生时要执行的函数,useCapture是一个可选的布尔值,用于指定事件是否在捕获阶段处理。 例如,我们可以在一个按钮上添加一个点击事件监听器,如下所示: <button id="myButton">Click me!</button> 1 var button = document.getElementById("myButton"); button.addEventListener("click", function() { alert("Button clicked!"); }); 在上述代码中,我们使用getElementById()方法获取了ID为myButton的按钮元素,并使用addEventListener()方法在其上添加了一个点击事件监听器。当用户点击该按钮时,会弹出一个警告框,显示"Button clicked!"。 事件类型 在JavaScript中,有很多不同的事件类型可供监听。以下是一些常见的事件类型及其描述: click:用户单击了某个元素。 dblclick:用户双击了某个元素。 mousedown:用户按下了鼠标按钮。 mouseup:用户释放了鼠标按钮。 mousemove:用户移动了鼠标。 mouseover:鼠标移到某个元素上。 mouseout:鼠标从某个元素移开。 keydown:用户按下了键盘上的某个键。 keyup:用户释放了键盘上的某个键。 focus:某个元素获得了焦点。 blur:某个元素失去了焦点。 load:某个元素完成加载。 unload:某个元素被卸载。 以上仅是一部分常用的事件类型,还有很多其他的事件类型可供选择。在实际开发中,我们需要根据具体的需求选择合适的事件类型。 事件处理函数 在事件监听器中,我们需要定义一个事件处理函数,用于在事件发生时执行特定的代码。事件处理函数可以是任何JavaScript函数,包括匿名函数和已命名函数。 例如,以下代码定义了一个名为myFunction的函数,该函数用于在按钮被点击时改变按钮的文本: <button id="myButton">Click me!</button> 1 var button = document.getElementById("myButton"); button.addEventListener("click", myFunction); function myFunction() { button.innerHTML = "Button clicked!"; } 在上述代码中,我们使用addEventListener()方法在按钮上添加了一个点击事件监听器,并将事件处理函数设置为myFunction。当用户点击该按钮时,myFunction函数会被调用,并将按钮的文本更改为"Button clicked!"。 事件传播 在JavaScript中,事件传播分为三个阶段:捕获阶段、目标阶段和冒泡阶段。默认情况下,事件处理函数会在冒泡阶段执行。但是,我们可以使用useCapture参数来将事件处理函数设置为在捕获阶段执行。 以下是事件传播的详细描述: 捕获阶段:事件从文档根节点开始向下传播,直到到达事件目标的父级元素。 目标阶段:事件到达事件目标元素。 冒泡阶段:事件从事件目标的父级元素开始向上冒泡,直到到达文档根节点。 例如,以下代码演示了事件传播的过程: <div id="outer"> <div id="inner"> <button id="myButton">Click me!</button> </div> </div> var outer = document.getElementById("outer"); var inner = document.getElementById("inner"); var button = document.getElementById("myButton"); outer.addEventListener("click", function() { console.log("Outer clicked!"); }, true); inner.addEventListener("click", function() { console.log("Inner clicked!"); }, true); button.addEventListener("click", function() { console.log("Button clicked!"); }, true); 在上述代码中,我们在三个元素上分别添加了一个点击事件监听器,并将事件处理函数设置为在捕获阶段执行。当用户单击按钮时,控制台会输出以下内容: Outer clicked! Inner clicked! Button clicked! 从输出结果可以看出,事件从外到内依次经过了outer、inner和button三个元素,并在每个元素上执行了事件处理函数。 事件对象 在事件监听器中,事件对象是一个非常重要的概念。事件对象包含了关于事件的所有信息,例如事件类型、事件目标和鼠标位置等。我们可以使用事件对象来获取这些信息,并在事件处理函数中进行处理。 以下是一些常用的事件对象属性: type:事件类型。 target:事件目标元素。 currentTarget:当前正在处理事件的元素。 clientX/clientY:鼠标相对于浏览器窗口左上角的坐标。 pageX/pageY:鼠标相对于文档左上角的坐标。 keyCode:按下的键盘键的键码值。 例如,以下代码演示了如何使用事件对象获取鼠标位置: <div id="myDiv">学习事件监听</div> 1 var div = document.getElementById("myDiv"); div.addEventListener("mousemove", function(event) { console.log("X: " + event.clientX + ", Y: " + event.clientY); }); 在上述代码中,我们在一个div元素上添加了一个鼠标移动事件监听器,并使用事件对象获取了鼠标相对于浏览器窗口左上角的坐标。当用户在该div元素上移动鼠标时,控制台会输出鼠标位置信息。 移除事件监听器 在JavaScript中,我们可以使用removeEventListener()方法来移除已添加的事件监听器,以避免出现意外的事件触发。该方法的语法如下: element.removeEventListener(event, function, useCapture); 1 与addEventListener()方法类似,removeEventListener()方法需要指定要移除的事件类型、事件处理函数和是否在捕获阶段处理。 例如,以下代码演示了如何移除一个事件监听器: <button id="myButton">Click me!</button> 1 var button = document.getElementById("myButton"); var handleClick = function() { alert("Button clicked!"); }; button.addEventListener("click", handleClick); setTimeout(function() { button.removeEventListener("click", handleClick); }, 5000); 在上述代码中,我们在按钮上添加了一个点击事件监听器,并将其保存在一个变量handleClick中。然后,使用setTimeout()方法在5秒后移除该事件监听器。 总结 事件监听是Web开发中非常重要的一个概念,掌握了它的用法,可以让我们实现更加丰富和动态的交互效果。在实际开发中,我们需要根据具体的需求选择合适的事件类型和事件处理函数,以及注意事件传播和事件对象的相关问题。 ———————————————— 版权声明:本文为CSDN博主「爱学习的Akali King」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/dyk11111/article/details/129813690
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签