• [技术干货] 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 
  • 使用canvas制作炫彩背景【转】
    1.定义canvas标签:1<canvas id="canvas"></canvas>2.开始js部分,先定义变量:123456789101112131415161718/* 获取画布 */var canvas = document.querySelector("#canvas");var ctx = canvas.getContext('2d');/* 定义一个字符串数组,后面字符串会从里随机选值 */var text = "SAFAF1D56FLK43F7PHM76VC9XNJ23";/* 定义 w为窗口宽度,h为窗体高度 */var w=window.innerWidth;var h=window.innerHeight;/* 设置len为20,其为背景里每条字符串的长度 */var len = 20;/* 设置num为100,窗口一共显示100条字符串 */var num = 100;/* 定义数组,里面存取每条字符串的字符与位置 */var arr=[];/* 画布宽等于窗口宽 */canvas.width=window.innerWidth;/* 画布高等于窗口高 */canvas.height=window.innerHeight;3.初始化字符串数组,先给每条字符串位置,字符先不给:123456789101112/* 初始化字符串数组 */        for(let i=0;i<num;i++){            /* 用.push方法给arr数组添加值 */            arr.push({                /* 字符先为空 */                str:[],                /* x轴位置为窗口宽度乘上一个0带1的随机数 */                x: parseInt(w*Math.random()),                /* y轴位置为窗口高度乘上一个0带1的随机数,再减个150把,可以为负数 */                y: parseInt(h*Math.random()-150)            })        }4.绘制每条字符串:123456789101112131415161718192021222324252627282930/* 绘制每条字符串 */       function txt(){           /* 给个循环,共绘制num条 */           for(let i=0;i<num;i++){               /* 设置变量letter为当前arr数组里的第i条字符串 */               var letter = arr[i];               /* 让字符串的字符为空 */               letter.str = [];               /* 给个循环,一个字符一个字符的拼接成字符串 */               for(let k=0;k<len;k++){                   /* 随机选取text里的一个字符 */                   letter.str.push(text[Math.floor(Math.random() * text.length)]);               }               /* 再来循环,开始绘制渲染字符串的每个字符 */               for(let j=0;j<len;j++){                   if(j==len-1){                       /* 第一个字符为白色 */                       ctx.fillStyle = `rgb(255, 255, 255)`;                   }else{                       /* 后面的为绿色,慢慢变不透明 */                       ctx.fillStyle = `rgba(0, 255, 21,${j*0.15})`;                   }                        /* 渲染字符 */                    ctx.font = "20px FangSong";                    ctx.fillText(letter.str[j],letter.x,letter.y+j*15);               }           }           /* 调用更新 */           move();       }5.更新字符串:12345678910111213141516/* 让字符串移动,若某字符串出了可视区,则重新生成 */       function move(){           /* 来个循环,给全部字符串更新位置 */           for(let j=0;j<num;j++){               /* y轴位置加3 */               arr[j].y=arr[j].y+3;               /* 如果改字符已经走出窗口了重新赋值 */               if(arr[j].y>=h){                   arr[j]={                       str:[],                x: parseInt(w*Math.random()),                y: parseInt(h*Math.random()-150)                   }               }           }       }6.设置动画过程:12345678setInterval(function(){            /* 清屏 */           ctx.clearRect(0,0,w,h);           /* 渲染 */           move();           /* 更新 */           txt();        },50); 7.在窗口大小改变时,设置canvas画布能实时铺满屏幕:123456789101112131415/* 绑定窗口大小发生改变事件,重新绘制字符串数组,让canvas随时铺满浏览器可视区 */        window.onresize=resizeCanvas;        function resizeCanvas(){            w=canvas.width=window.innerWidth;            h=canvas.height=window.innerHeight;            /* 重新给全部字符串赋值 */            for(let j=0;j<num;j++){                    arr[j]={                        str:[],                 x: parseInt(w*Math.random()),                 y: parseInt(h*Math.random()-150)                    }                }            }               resizeCanvas();转载自https://www.jb51.net/article/279297.htm
  • [技术干货] 两个函数相互调用如何防止死循环【转】
    最近碰到了一个问题,就是两个函数相互调用遭遇死循环的问题,想了半天终于想出了一个算法破解,姑且叫它熵递减算法。问题的抽象代码如下1234567891011/**  methodA 和 methodB 循环调用,是死循环* */function methodA() {    console.log('A的事情');    methodB();}function methodB() {    console.log('B的事情');    methodA();}不论调用哪个方法,都会产生死循环。我想要的效果是:如果触发A方法时,也执行一下B方法,到此为止不再循环下去,反之亦然。因此,必须能判断方法是主动发起的,还是被动的。抽象代码如下:12345678910111213141516171819/**  解决问题的关键在于,判断方法是主动发起的,还是被动的* */function methodA() {    console.log('A的事情');    if('A是主动的'){        methodB();    }else{        // 不再调用下去    }}function methodB() {    console.log('B的事情');    if('B是主动的'){        methodA();    }else{        // 不再调用下去    }}解法,就是熵递减算法,如下1234567891011121314151617181920212223/** 熵递减算法* */var pairMethodStep = 2;function methodA() {    pairMethodStep --;    console.log('A的事情');    if(pairMethodStep === 1){        methodB();    }else{        pairMethodStep = 2;    }}function methodB() {    pairMethodStep --;    console.log('B的事情');    if(pairMethodStep === 1){        methodA();    }else{        pairMethodStep = 2;    }}熵递减算法的说明:给一个全局变量,叫做总步数pairMethodStep ,初始值为2。任一个方法执行时,做完自己的事情后,把pairMethodStep减成1。然后,判断此时的pairMethodStep,如果是1, 就调用另一个方法;如果是0了,就不再继续调用了,而是把pairMethodStep恢复成2。我们分析一下代码执行的过程。主动的方法执行前,pairMethodStep的值是2,它做完自己的事后,把pairMethodStep的值变成了1,紧跟着就会执行被动的方法;被动的方法执行前,pairMethodStep的值是1,被动的方法做完自己的事情后,把pairMethodStep的值减成了0,不会再调用另一个方法了(不会发生死循环了),而仅仅是把pairMethodStep还原成2。目的达到。熵递减算法,能完美地解决两个函数相互调用的问题。js函数互相调用碰到的问题项目场景两个函数互相调用时(当一个系统比较大时,尤其是涉及到一些复杂的算法时,很有可能会碰到死循环的情况发生,造成系统的CPU飙升)12345678        function a1() {            console.log("a1");            b1();        }        function b1() {            console.log("b1");            a1();        }问题描述会进入死循环原因分析就类似for循环,或者递归函数如果没有退出条件就会一直执行解决方案1234567891011121314151617181920        let flagNum = 1;          function a1() {            flagNum--;            console.log("a1");            if (flagNum === 0) {                b1();            } else {                flagNum = 1;            }        }        function b1() {            flagNum--;            console.log("b1");            if (flagNum === 0) {                a1();            } else {                flagNum = 1;            }        }总结以上为个人经验,希望能给大家一个参考转载自https://www.jb51.net/article/279298.htm
  • [技术干货] JS内存泄漏的原因及解决办法
    什么是内存泄漏?程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能:变慢,延迟大等 ,重则导致进程崩溃。我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。内存泄漏的识别方法1、使用快捷键 F12 或者 Ctrl+Shift+J 打开 Chrome 浏览器的「开发者工具」。2、选择 Performance(老版为Timeline) 选项卡,在 Capture 选项中,只勾选 Memory。3、设置完成后,点击最左边的 Record 按钮,然后就可以访问网页了。4、打开一个网站,例如:www.taobao.com,当网页加载完成后,点击 Stop,等待分析结果。5、然后在 ChartView 上寻找内存急速下降的部分,查看对应的 EventLog,可以从中找到 GC 的日志。具体过程如下图所示:内存泄露的常见原因及处理方式常见原因:1. 意外的全局变量下面代码中变量bar在foo函数内,但是bar并没有声明.JS就会默认将它变为全局变量,这样在页面关闭之前都不会被释放.function foo(){ bar=2 console.log('bar没有被声明!')}b 没被声明,会变成一个全局变量,在页面关闭之前不会被释放.使用严格模式可以避免.2. dom清空时,还存在引用很多时候,为了方便存取,经常会将 DOM 结点暂时存储到数据结构中.但是在不需要该DOM节点时,忘记解除对它的引用,则会造成内存泄露.var element = { shotCat: document.getElementById('shotCat')};document.body.removeChild(document.getElementById('shotCat'));// 如果element没有被回收,这里移除了 shotCat 节点也是没用的与此类似情景还有: DOM 节点绑定了事件, 但是在移除的时候没有解除事件绑定,那么仅仅移除 DOM 节点也是没用的3. 定时器中的内存泄漏var someResource = getData();setInterval(function() { var node = document.getElementById('Node'); if(node) { node.innerHTML = JSON.stringify(someResource)); }}, 1000);如果没有清除定时器,那么 someResource 就不会被释放,如果刚好它又占用了较大内存,就会引发性能问题. 但是 setTimeout ,它计时结束后它的回调里面引用的对象占用的内存是可以被回收的. 当然有些场景 setTimeout 的计时可能很长, 这样的情况下也是需要纳入考虑的.4. 不规范地使用闭包例如下面的例子: 相互循环引用.这是经常容易犯的错误,并且有时也不容易发现.function foo() { var a = {}; function bar() { console.log(a); }; a.fn = bar; return bar; };bar和a形成了相互循环引用.可能有人说bar里不使用console.log(a)不就没有引用了吗就不会造成内存泄露了.NONONO,bar作为一个闭包,即使它内部什么都没有,foo中的所有变量都还是隐式地被 bar所引用。 即使bar内什么都没有还是造成了循环引用,那真正的解决办法就是,不要将a.fn = bar.避免策略:减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收(即赋值为null);注意程序逻辑,避免“死循环”之类的 ;避免创建过多的对象 原则:不用了的东西要记得及时归还。减少层级过多的引用
总条数:53 到第
上滑加载中