• [技术干货] weui 弹出 $.toast(“我是文本“,“text“); 总是带有图标
    在前段代码中引入 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 
  • [技术干货] js保留两位小数方法总结【转】
    1. toFixed()方法需注意,保留两位小数,将数值类型的数据改变成了字符串类型2. Math.floor(),不四舍五入 ,向下取整注意,不改变数据类型3. 字符串匹配注意,先将数据转换为字符串,最后再转为数值类型4. 四舍五入保留2位小数(若第二位小数为0,则保留一位小数)注意,数据类型不变5. 四舍五入保留2位小数(不够位数,则用0替补)注意,数据类型变为字符串类型
  • [技术干货] ${pageContext.request.contextPath}的理解和用法
    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如何准确获取当前页面url网址信息
    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 
  • [技术干货] JS 自己实现一个大文件切片上传+断点续传【转】
     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
  • [技术干货] WPS JS宏入门案例集锦-转载
    批量创建工作表/簿 批量创建工作表:  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 
  • [技术干货] JS中的事件监听-转载
     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 
  • [技术干货] 常见的JS数据存储方式及其特点
    在前端开发中,经常需要在浏览器端存储和管理数据。为了实现数据的持久化存储和方便的访问,JavaScript提供了多种数据存储方式。本文将介绍几种常见的前端JS数据存储方式及其特点。1. CookieCookie是一种小型的文本文件,由浏览器保存在用户计算机上。使用JavaScript可以读取和写入Cookie。Cookie主要用于存储少量的数据,并且会在每次HTTP请求中自动发送到服务器。但是,Cookie的存储容量有限,通常不超过4KB,而且受浏览器设置的限制。2. LocalStorageLocalStorage是HTML5引入的一种持久化的本地存储机制。它允许在浏览器中存储大量的数据,并且在浏览器关闭后数据仍然保持不变。LocalStorage使用简单的键值对来存储数据,可以通过JavaScript的localStorage对象进行访问和操作。LocalStorage的存储容量通常为5MB或更大,但是由于是在用户浏览器上存储数据,因此需要注意不要存储敏感或重要的信息。3. SessionStorageSessionStorage与LocalStorage类似,也是一种HTML5提供的本地存储机制。不同的是,SessionStorage中存储的数据在浏览器会话结束后就会被清除,而不是持久化存储。SessionStorage适用于临时保存会话数据,比如在同一浏览器选项卡或窗口之间共享数据。4. IndexedDBIndexedDB是一种高级的客户端数据库,用于在浏览器中存储结构化数据。它提供了事务性的存储机制和丰富的查询功能。IndexedDB适用于需要存储大量结构化数据或进行复杂查询的场景。与其他存储方式相比,IndexedDB的学习曲线较陡峭,需要使用JavaScript API进行操作。5. Web Storage APIWeb Storage API是一个统一的API,包括LocalStorage和SessionStorage。它提供了一套简单的接口,用于访问和操作这两种存储方式。通过Web Storage API,可以轻松地读取、写入和删除数据。6.其他存储方式除了上述提到的存储方式,还有一些其他的存储方式可供选择,如WebSQL、FileSystem API等。然而,由于这些存储方式的兼容性和使用限制等问题,它们的使用已经逐渐减少,不再被广泛推荐。总结开发人员可以根据具体需求选择合适的存储方式。Cookie适合存储小型数据和会话相关信息,LocalStorage和SessionStorage适合在浏览器中进行持久化或临时存储,IndexedDB适合处理大量结构化数据和复杂查询。使用这些数据存储方式,可以有效管理和利用前端的数据。如果你想要查看当前网站存储了哪些数据,可以点击“F12”,在开发者工具中,切换到 “存储” 选项卡 其中,会话存储就是 SessionStorage;本地存储就是localStorage
  • [技术干货] JavaScript获取http,http://请求协议头,域名,端口,url
    window.location.protocol 获取http,http://请求协议头  window.location.host 获取域名并带端口 xxx.com 如果有端口就是 xxx.com:8080  window.location.hostname; 获取域名: xxx.com  window.location.href; 获取整个url地址:http://xxx.com/xxx.html  window.location.pathname; 获取域名后面请求路径url: /xxx/xxx.html  window.location.port 获取端口比如 8080 ———————————————— 原文链接:https://blog.csdn.net/u012834688/article/details/125640832 
  • [问题求助] 坐席二次开发问题
    【问题来源】    汉口银行    【问题简要】    座席二次开发问题【问题类别】    CC-Gateway【AICC解决方案版本】    AICC版本:AICC 22.200    UAP版本:UAP9600 V100R005C00SPC113    CTI版本:  ICD V300R008C25spc012【期望解决时间】    尽快【问题现象描述】您好,二次开发CCGateway接续接口需要使用轮询接口做会话保活。这个是在坐席前端轮询调用接口:https://ip:port/agentgateway/resource/agentevent/{agentid}?calldata=XXX做会话保持吗?
  • [技术干货] javaScript获取键值对的三种方法?keys 、entire.....
    Javascript获取数据对象里面的键key和value大家都知道在后端接口的数据处理后在返回是一件非常重要的事情,根据返回的方法不同,返回的格式也就有所区别,比如通过json的数据格式来返回,再或者通过键值对的方式来返回等等,所以为了更好的让大家理解和区别开键值对是怎么来工作的,这里介绍几种方法让大家更好的了解。方法一:使用 keys值的方法来获取对象的键值key方法二:使用entries方法来获取对象的键值key方法三:使用for循环获取对象的键值key三种方法都可以获取到数组中的key值,同时对象是键值的集合,我们需要一个键值来获取对象中的值是多少,键值对在java、javascrtipt中都有着广泛的应用。那问题来了,如果我们在检索值的过程中只要有他的键值就可以 但是我们如果没有key是多少呢?方法一介绍:Object.keys()Object.keys()使用函数返回对象键的数组值是多少我们以javascript为例,javascript对象作为参数传递给了该函数,如果我们在输出一个数组的时候他的键值对的顺序是不会随机排列的,而是和原始的对象中顺序相同,通用我们将数组传递给给了 对象的键值对,它也会通过数组的索引作为输出返回该结果,并且返回的参数中同样带有索引,所以keys的函数会返回这些索引的数组。代码演示:var zeyuli1 = ["1", "2", "3"];var zeyuli2 = { 0: "1", 4: "0", 2: "3", 3: "0"};var zeyuli3 = { "id": "1", "name": "2", "old": "3"};console.log(Object.keys(zeyuli1));console.log(Object.keys(zeyuli2));console.log(Object.keys(zeyuli3));大家可以手动练习一下看看结果是多少呢?方法二介绍:Object.entries(OBJ)Object.entries(OBJ)的方法运用起来要比keys的函数要灵活的多方法也多的多,在整个对象划分为每个不同的数组时候用key和value形式来表示键值对的形成,使用方法二我们可以得到一个对象中的所有键值对,这就是和方法一的本质区别。语法:Object.entries(OBJ)参数:和方法一的本质区别就是 Object.entries(OBJ)的参数是OBJ注意:和方法一的函数同样具备对象的属性顺序。代码演示:var  zeyuli1 = { "id": "1", "name": "2", "old": "24"};console.log(JSON.stringify(Object.entries(zeyuli1)));方法三介绍:for循环的使用遍历大家都知道经常使用到for循环,那么如何使用for才能让数组都经历一次对象遍历呢?学过的小伙伴都知道 使用的是for和in的固定搭配,使用i来保存对象的key值,而object[i]保存与对象中的key相对应的值。代码展示如下:var a = { "id": "1", "name": "1", "old": {"name": "2", "appearance": "3"}};for(let i in a){  console.log(i);  console.log(a[i]);}使用的嵌套结构中也可以用console.log(i)来获取键值。
  • [技术干货] JS 防抖与节流
    滚动对页面渲染的影响web页面渲染会经历如下步骤:js加载 —> style加载 —> layout(确定布局) —> paint(页面绘制) —> composite(组合控件)网页生成的时候,至少会渲染(Layout+Paint)一次。用户访问的过程中,还会不断重新的重排(reflow)和重绘(repaint),用户scroll行为和resize行为会导致页面不断的进行重新渲染,如果间隔频繁,容易造成浏览器卡帧。为什么滚动scroll、窗口resize需要优化滚动事件的应用很频繁:图片懒加载、下滑自动加载数据、侧边浮动导航栏等。在绑定scroll、resize事件时,但它发生的时候,它被触发的频率非常高,间隔很近。在日常开发的页面中,事件中涉及到的大量的位置计算、DOM操作、元素重绘等等这些都无法在下一个scroll事件出发前完成的情况下,浏览器会卡帧。防抖与节流在进行窗口的resize、scroll、输出框内容校验等操纵的时候,如果事件处理函数调用的频率无限制,会加重浏览器的负担,导致浏览器卡顿。为了用户更好的体验,就可以采用防抖(debounce)和节流(throttle)的方式来减少调用的频率,减轻浏览器的负担。防抖Debounce使用情景有些场景事件触发的频率过高(mousemove onkeydown onkeyup onscroll)回调函数执行的频率过高也会有卡顿现象。 可以一段时间过后进行触发去除无用操作原理当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。代码示例以下是一个简单的JavaScript代码示例,它实现了一个基本的防抖功能:function debounce(func, delay) { let timer = null; return function() { const context = this; const args = arguments; clearTimeout(timer); timer = setTimeout(function() { func.apply(context, args); }, delay); } }这个代码定义了一个名为debounce的函数,它接受两个参数:要防抖的函数和防抖的时间间隔。当调用debounce函数时,它会返回一个新的函数,这个新函数将被用来代替原来的函数。每次调用新函数时,它会清除之前设置的定时器,并创建一个新的定时器。如果在指定时间间隔内再次调用新函数,定时器将被清除并重新设置。只有在指定时间间隔内没有再次调用新函数时,定时器才会在最后一次调用后触发,并调用原始函数。这个防抖函数可以用于限制某些操作的执行频率,例如在用户连续输入时进行搜索操作。这可以减少不必要的网络请求和计算资源消耗。节流Throttle使用情景图片懒加载ajax数据请求加载原理当持续触发事件时,保证一定时间段内只调用一次事件处理函数。代码示例以下是一个简单的JavaScript代码示例,它实现了一个基本的节流功能:function throttle(func, delay) { let timer = null; return function() { const context = this; const args = arguments; if (!timer) { timer = setTimeout(function() { func.apply(context, args); timer = null; }, delay); } } }这个代码定义了一个名为throttle的函数,它接受两个参数:要节流的函数和节流的时间间隔。当调用throttle函数时,它会返回一个新的函数,这个新函数将被用来代替原来的函数。每次调用新函数时,它会检查是否已经设置了一个定时器。如果没有,它会创建一个新的定时器,并在指定的时间间隔后调用原始函数。如果已经设置了定时器,则新函数将不执行任何操作。这个节流函数可以用于限制某些操作的执行频率,例如在用户滚动页面时更新页面元素的位置。
  • [技术干货] this.$toast() 了解一下?
    在平时的开发过程中,我们总是先写好一个组件,然后在需要的页面中用 import 引入即可,但如果是下面这种类型的组件呢 上面这种类型的浮层提示有一个很大的特点,就是使用频率特别高,几乎每个页面都会用到它,于是乎我们就要在每个页面中去引入该组件,并且在每个页面都得通过一个变量来控制它的显隐,这显然不是我们想要的。。。那我们想要的是什么样呢?用过一些 UI 框架的同学们应该知道有这样一种用法:this.$toast({ duration: 3000, content: '这是一条消息提示'});复制代码没错,就是这么简单的一句话就万事大吉了(就是用 js 调用组件而已啦)。那这种效果究竟是怎么实现的呢?今天就让我们来(手把手 )一探究竟吧!前置知识不知道小伙伴们有没有用过 Vue.extend() 这个东东,反正我是很少碰过,印象不深,所以这里我们先来短暂了解一下 Vue.extend() 主要是用来干嘛的。先来个官方说明(不多的,坚持下):  没怎么看懂?没关系,不重要,你只要记住(加少许理解)以下用法即可:// 导入以往的普通组件import Main from './main.vue';// 用 Vue.extend 创建组件的模板(构造函数)let mainConstructor = Vue.extend(Main);// 实例化组件let instance = new mainConstructor();// 挂载到相应的元素上instance.$mount('#app');复制代码不知道你看懂没有,上面的 Vue.extend(Main) 就是一个基于 main.vue 的组件模板(构造函数),instance 是实例化的组件,$mount() 是手动挂载的意思。其中 Vue.extend() 和 $mount() 就是我们通过 js 调用、渲染并挂载组件的精髓所在,相当于早前的 createElement 和 appendChild,有异曲同工之效。这个点需要我们好好熟悉一下,所以你可以先停下来屡屡思路。补充一下:$mount() 里面如果没有参数,说明组件只是渲染了但还没有挂载到页面上,如果有正确的(元素)参数则直接挂载到元素下面。写一个 toast 组件js 调用归调用,最原始的组件还是要有的,只是我们不通过 import 来引入到页面中而已。ok,我们就以最开始的那个 toast 图片来简单写一下这个 vue 组件(message 和 alert 也是一样的)。这里就直接上代码啦,毕竟它的结构简单到爆了,也不是本章节的重点:<!-- main.vue --><template> <div class="toast"> <p>服务器错误,请稍后重试</p> </div></template><script>export default { name: "Toast", mounted() { setTimeout(() => { // 3s 后通过父级移除子元素的方式来移除该组件实例和 DOM 节点 this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, 3000); }};</script><style lang="scss" scoped>.toast { display: flex; align-items: center; justify-content: center; position: fixed; top: 0; bottom: 0; left: 0; right: 0; color: #fff; z-index: 9999; background: transparent; > p { padding: 12px 22px; font-size: 18px; border-radius: 4px; background: rgba(17, 17, 17, 0.7); }}</style>复制代码上面的内容想必大家应该都能看懂,所以这里就直接讲下面的重点了。写一个 main.js我们在 main.vue 的同级目录下新建一个 main.js 文件。我们先瞟一眼文件内容(也不多,已经是个最简版了):// main.jsimport Vue from "vue"; // 引入 Vue 是因为要用到 Vue.extend() 这个方法import Main from "./main.vue"; // 引入刚才的 toast 组件let ToastConstructor = Vue.extend(Main); // 这个在前面的前置知识内容里面有讲到let instance;const Toast = function() { instance = new ToastConstructor().$mount(); // 渲染组件 document.body.appendChild(instance.$el); // 挂载到 body 下};export default Toast;复制代码上面的代码暴露了一个 Toast 函数。为什么要暴露一个函数呢?原因很简单:你想想,我们最终是不是要根据 this.$toast() 来调用一个组件,说白了,通过 js 调用,本质就是调用一个 函数。也就是说 this.$toast() 就是执行了上面代码中导出的 export default Toast,也就是执行了 Toast 函数(const Toast = function() {}),所以当我们调用 this.$toast() 的时候其实就是执行了 Toast() 函数。而 Toast() 函数只做了一件事情:就是通过手动挂载的方式把组件挂载到 body 下面。补充一下:一般来说我们常见的是 $mount("#app"),也就是把组件挂载到 #app 下面,<router-view /> 也包含在 #app 中,但是我们这种 toast 提示是放在 body 下面的,也就是说它不受 #app 和 <router-view /> 的管控,所以当我们切换页面(路由)的时候,这个 toast 组件是不会跟着立马消失的,这点要注意哦。这里顺便给个组件的目录结构开始调用调用方式很简单,首先我们在入口文件 main.js(和上面不是同一个) 里加上两行代码,这样我们就能在需要的地方直接用 js 调用它了,如下图所示:  然后在页面中测试一下,就像下面这样子:  运行一下代码:  嗯,挺好,小有成就的 feel 。支持可传参数别急,我们好像还漏了点什么。。。对了,现在还不支持传参呢,直接调用 this.$toast() 就只能显示————服务器错误,请稍后重试(这下全都是后端的锅了)。但我们可是个有追求的前端,不能局限于此,所以现在让我们来尝试增加下两个可配置参数,这里拿 duration 和 content 举个栗子。首先我们要修改 main.vue 组件里面的内容(其实没啥大变化),就像下面这样:<!-- main.vue 可配置版 --><template> <div class="toast"> <p>{{ content }}</p> </div></template><script>// 主要就改了 dataexport default { name: "Toast", data() { return { content: "", duration: 3000 }; }, mounted() { setTimeout(() => { this.$destroy(true); this.$el.parentNode.removeChild(this.$el); }, this.duration); }};</script>复制代码上面的代码应该算是浅显易懂了,接下来我们看下 main.js 里面改了啥:// main.js 可配置版import Vue from "vue";import Main from "./main.vue";let ToastConstructor = Vue.extend(Main);let instance;const Toast = function(options = {}) { // 就改了这里,加了个 options 参数 instance = new ToastConstructor({ data: options // 这里的 data 会传到 main.vue 组件中的 data 中,当然也可以写在 props 里 }); document.body.appendChild(instance.$mount().$el);};export default Toast;复制代码其实 main.js 也没多大变化,就是在函数里面加了个参数。要注意的是 new ToastConstructor({ data: options }) 中的 data 就是 main.vue 组件中的 data,不是随随便便取的字段名,传入的 options 会和组件中的 data 合并(Vue 的功劳)。em。。。是的,就这么简单,现在让我们继续来调用一下它:<script>export default { methods: { showToast() { this.$toast({ content: "哈哈哈哈,消失的贼快", duration: 500 }); } }};</script>复制代码运行一下就可以看到:  当然,这还没完,我们继续添加个小功能点。。支持 this.$toast.error()这里我们打算支持 this.$toast.error() 和 this.$toast.success() 这两种方式,所以我们第一步还是要先去修改一下 main.vue 文件的内容(主要就是根据 type 值来修改组件的样式),就像下面这样:<!--main.vue--><template> <div class="toast" :class="type ? `toast--${type}` : ''"> <p>{{ content }}</p> </div></template><script>export default { ... data() { return { type: "", content: "", duration: 3000 }; }, ...};</script><style lang="scss" scoped>.toast { ... &--error p { background: rgba(255, 0, 0, 0.5); } &--success p { background: rgba(0, 255, 0, 0.5); }}</style>复制代码其次,this.$toast.error() 其实就等价于 Toast.error(),所以我们现在的目的就是要给 Toast 函数扩充方法,也比较简单,就先看代码再解释吧:// main.jsconst Toast = function(options = {}) { ...};// 以下就是在 Toast 函数中拓展 ["success", "error"] 这两个方法["success", "error"].forEach(type => { Toast[type] = options => { options.type = type; return Toast(options); };});export default Toast;复制代码我们可以看到 Toast.error() 和 Toast.success() 最终还是调用 Toast(options) 这个函数,只不过在调用之前需要多做一步处理,就是将 ["success", "error"] 作为一个 type 参数给合并进 options 里面再传递,仅此而已。那就试试效果吧:<script>export default { methods: { showToast() { this.$toast({ content: "这是正常的" }); }, showErrorToast() { this.$toast.error({ content: "竟然失败了" }); }, showSuccessToast() { this.$toast.success({ content: "居然成功了" }); } }};</script>复制代码 大赞无疆,大。赞。。无。。。疆。。。。。结语至此,一个通过 js 调用的简单 toast 组件就搞定啦,短短的几行代码还是挺考验 js 功底的。当然这只是个超简易版的 demo,显然不够完善和健壮,所以我们可以在此基础上扩充一下,比如当 duration <= 0 的时候,我们让这个 toast 一直显示,然后扩展一个 close 方法来关闭等等之类的。不过还是那句老话,实践才是检验真理的唯一标准。纸上得来终觉浅,绝知此事要躬行。step by step, day day up !作者:尤水就下链接:https://juejin.cn/post/6844903825711562766
  • [技术干货] JS 判断数组是否包含某个元素方法集合
    1、JavaScript indexOf() 方法 定义和用法 indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。 语法 stringObject.indexOf(searchvalue,fromindex) 参数    描述 searchvalue    必需。规定需检索的字符串值。 fromindex    可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。 说明 该方法将从头到尾地检索字符串 stringObject,看它是否含有子串 searchvalue。开始检索的位置在字符串的 fromindex 处或字符串的开头(没有指定 fromindex 时)。如果找到一个 searchvalue,则返回 searchvalue 的第一次出现的位置。stringObject 中的字符位置是从 0 开始的。 提示和注释 注释:indexOf() 方法对大小写敏感! 注释:如果要检索的字符串值没有出现,则该方法返回 -1。 2、jQuery.inArray()方法 定义和用法 $.inArray() 函数用于在数组中查找指定值,并返回它的索引值(如果没有找到,则返回-1) 提示:源数组不会受到影响,过滤结果只反映在返回的结果数组中。 语法 $.inArray( value, array [, fromIndex ] ) 参数    描述 value    任意类型 用于查找的值。 array    Array类型 指定被查找的数组。 fromIndex    可选。Number类型 指定从数组的指定索引位置开始查找,默认为 0 3、JavaScript find() 方法 定义和用法 find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。 find() 方法为数组中的每个元素都调用一次函数执行:  当数组中的元素在测试条件时返回 true 时, find() 返回符合条件的元素,之后的值不会再调用执行函数。 如果没有符合条件的元素返回 undefined  注意:find() 对于空数组,函数是不会执行的。 注意:find() 并没有改变数组的原始值。 注意:JavaScript 版本为ECMAScript 6 用法  array.find(function(currentValue, index, arr),thisValue) 参数    描述 function    必需。数组每个元素需要执行的函数。 currentValue    必需。当前元素 函数参数 index    可选。当前元素的索引值函数参数 arr    可选。当前元素所属的数组对象函数参数 thisValue    可选。 传递给函数的值一般用 “this” 值。如果这个参数为空, “undefined” 会传递给 “this” 值 返回值 返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回 undefined。 4、JavaScript findIndex() 方法 定义和用法 findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。 findIndex() 方法为数组中的每个元素都调用一次函数执行:  当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。 如果没有符合条件的元素返回 -1  注意:find() 对于空数组,函数是不会执行的。 注意:find() 并没有改变数组的原始值。 注意:JavaScript 版本为ECMAScript 6 用法 array.findIndex(function(currentValue, index, arr), thisValue) 参数    描述 function    必需。数组每个元素需要执行的函数。 currentValue    必需。当前元素 函数参数 index    可选。当前元素的索引值函数参数 arr    可选。当前元素所属的数组对象函数参数 thisValue    可选。 传递给函数的值一般用 “this” 值。如果这个参数为空, “undefined” 会传递给 “this” 值 返回值 返回符合测试条件的第一个数组元素索引,如果没有符合条件的则返回 -1。 5、for 循环和 if 判断var arr = [1, 5, 10, 15];/* 传统for */for(let i=0; i<arr.length; i++) { if(arr[i] === 查找值) { //则包含该元素 }}/* for...of */for(v of arr) { if(v === 查找值) { //则包含该元素 }}/* forEach */arr.forEach(v=>{ if(v === 查找值) { //则包含该元素 }})———————————————— 原文链接:https://blog.csdn.net/github_38336924/article/details/83089873 
总条数:74 到第
上滑加载中