整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          CKEditor系列(三)粘貼操作是怎么完成的

          CKEditor系列(三)粘貼操作是怎么完成的

          上一篇文章CKEditor系列(二)事件系統是怎么實現的中,我們了解了CKEditor中事件系統的運行流程,我們先簡單回顧下:

          • 用戶注冊回調函數時可以指定優先級,值越小的優先級越高,默認是10
          • 系統會根據用戶的傳參組裝成系統規范的回調函數,供后續執行
          • 執行回調函數時可以將取消事件和阻止事件,不讓其它監聽該事件的回調函數執行。

          當插件希望對paste事件進行響應,一般有兩種方式可供選擇。

          直接監聽'paste'事件

          默認情況下,插件clipboard插件是監聽paste事件最多的。 我們可以看到里面多次出現類似這樣的代碼

          // plugins/clipboard/plugin.js
          editor.on( 'paste', function( evt ) {
          
          })

          我們可以看到里面有幾個優先級priority 為1回調

          處理粘貼圖片的場景

          將png、jpg、gif圖片的內容base64信息賦值給evt.data.dataValue

          editor.on( 'paste', function( evt ) {
              var dataObj=evt.data,
                  data=dataObj.dataValue,
                  dataTransfer=dataObj.dataTransfer;
          
              // If data empty check for image content inside data transfer. https://dev.ckeditor.com/ticket/16705
              // Allow both dragging and dropping and pasting images as base64 (#4681).
              if ( !data && isFileData( evt, dataTransfer ) ) {
                  var file=dataTransfer.getFile( 0 );
                  if ( CKEDITOR.tools.indexOf( supportedImageTypes, file.type ) !=-1 ) {
                      var fileReader=new FileReader();
          
                      // Convert image file to img tag with base64 image.
                      fileReader.addEventListener( 'load', function() {
                          evt.data.dataValue='<img src="' + fileReader.result + '" />';
                          editor.fire( 'paste', evt.data );
                      }, false );
          
                      // Proceed with normal flow if reading file was aborted.
                      fileReader.addEventListener( 'abort', function() {
                          // (#4681)
                          setCustomIEEventAttribute( evt );
                          editor.fire( 'paste', evt.data );
                      }, false );
          
                      // Proceed with normal flow if reading file failed.
                      fileReader.addEventListener( 'error', function() {
                          // (#4681)
                          setCustomIEEventAttribute( evt );
                          editor.fire( 'paste', evt.data );
                      }, false );
          
                      fileReader.readAsDataURL( file );
          
                      latestId=dataObj.dataTransfer.id;
          
                      evt.stop();
                  }
              }
          }, null, null, 1 );

          因為base64信息需要通過fileReader來處理:在圖片的load回調里面才能拿到,所以我們需要先執行evt.stop(),避免其它回調被執行了,然后在圖片load的回調里面重新觸發一直paste事件 editor.fire( 'paste', evt.data );,對應的aborterror也要觸發,避免因圖片失敗,導致其它回調都沒機會執行了。

          該回調會在下一輪paste回調執行中再次執行嗎?不會,因為該回調首次執行時evt.data.dataValue為空,下次執行時evt.data.dataValue已經被上次執行給賦值了,不會重復執行fileReader相關處理了。

          數據準備

          editor.on( 'paste', function( evt ) {
              // Init `dataTransfer` if `paste` event was fired without it, so it will be always available.
              if ( !evt.data.dataTransfer ) {
                  evt.data.dataTransfer=new CKEDITOR.plugins.clipboard.dataTransfer();
              }
          
              // If dataValue is already set (manually or by paste bin), so do not override it.
              if ( evt.data.dataValue ) {
                  return;
              }
          
              var dataTransfer=evt.data.dataTransfer,
                  // IE support only text data and throws exception if we try to get html data.
                  // This html data object may also be empty if we drag content of the textarea.
                  value=dataTransfer.getData( 'text/html' );
          
              if ( value ) {
                  evt.data.dataValue=value;
                  evt.data.type='html';
              } else {
                  // Try to get text data otherwise.
                  value=dataTransfer.getData( 'text/plain' );
          
                  if ( value ) {
                      evt.data.dataValue=editor.editable().transformPlainTextToHtml( value );
                      evt.data.type='text';
                  }
              }
          }, null, null, 1 );

          可以看到這個回調函數是主要是給evt.data增加dataTransferdataValue(如果已經被其它插件設置了就直接return出去)和type的,是做準備工作的,所以這個回調函數自然需要最先執行,優先級設置為1。

          看看第二個回調函數

          解決兼容性

          editor.on( 'paste', function( evt ) {
              var data=evt.data.dataValue,
                  blockElements=CKEDITOR.dtd.$block;
          
              // Filter webkit garbage.
              if ( data.indexOf( 'Apple-' ) > -1 ) {
                  // Replace special webkit's   with simple space, because webkit
                  // produces them even for normal spaces.
                  data=data.replace( /<span class="Apple-converted-space"> <\/span>/gi, ' ' );
          
                  // Strip <span> around white-spaces when not in forced 'html' content type.
                  // This spans are created only when pasting plain text into Webkit,
                  // but for safety reasons remove them always.
                  if ( evt.data.type !='html' ) {
                      data=data.replace( /<span class="Apple-tab-span"[^>]*>([^<]*)<\/span>/gi, function( all, spaces ) {
                          // Replace tabs with 4 spaces like Fx does.
                          return spaces.replace( /\t/g, '    ' );
                      } );
                  }
          
                  // This br is produced only when copying & pasting HTML content.
                  if ( data.indexOf( '<br class="Apple-interchange-newline">' ) > -1 ) {
                      evt.data.startsWithEOL=1;
                      evt.data.preSniffing='html'; // Mark as not text.
                      data=data.replace( /<br class="Apple-interchange-newline">/, '' );
                  }
          
                  // Remove all other classes.
                  data=data.replace( /(<[^>]+) class="Apple-[^"]*"/gi, '$1' );
              }
          
              // Strip editable that was copied from inside. (https://dev.ckeditor.com/ticket/9534)
              if ( data.match( /^<[^<]+cke_(editable|contents)/i ) ) {
                  var tmp,
                      editable_wrapper,
                      wrapper=new CKEDITOR.dom.element( 'div' );
          
                  wrapper.setHtml( data );
                  // Verify for sure and check for nested editor UI parts. (https://dev.ckeditor.com/ticket/9675)
                  while ( wrapper.getChildCount()==1 &&
                          ( tmp=wrapper.getFirst() ) &&
                          tmp.type==CKEDITOR.NODE_ELEMENT &&    // Make sure first-child is element.
                          ( tmp.hasClass( 'cke_editable' ) || tmp.hasClass( 'cke_contents' ) ) ) {
                      wrapper=editable_wrapper=tmp;
                  }
          
                  // If editable wrapper was found strip it and bogus <br> (added on FF).
                  if ( editable_wrapper )
                      data=editable_wrapper.getHtml().replace( /<br>$/i, '' );
              }
          
              if ( CKEDITOR.env.ie ) {
                  //   <p> -> <p> (br.cke-pasted-remove will be removed later)
                  data=data.replace( /^ (?: |\r\n)?<(\w+)/g, function( match, elementName ) {
                      if ( elementName.toLowerCase() in blockElements ) {
                          evt.data.preSniffing='html'; // Mark as not a text.
                          return '<' + elementName;
                      }
                      return match;
                  } );
              } else if ( CKEDITOR.env.webkit ) {
                  // </p><div><br></div> -> </p><br>
                  // We don't mark br, because this situation can happen for htmlified text too.
                  data=data.replace( /<\/(\w+)><div><br><\/div>$/, function( match, elementName ) {
                      if ( elementName in blockElements ) {
                          evt.data.endsWithEOL=1;
                          return '</' + elementName + '>';
                      }
                      return match;
                  } );
              } else if ( CKEDITOR.env.gecko ) {
                  // Firefox adds bogus <br> when user pasted text followed by space(s).
                  data=data.replace( /(\s)<br>$/, '$1' );
              }
          
              evt.data.dataValue=data;
          }, null, null, 3 );

          從上面的代碼很容易看出,主要是針對不同的瀏覽器做一下兼容性相關的處理,具體細節我們不用太關心

          針對不同粘貼源進行數據過濾

          editor.on( 'paste', function( evt ) {
              var dataObj=evt.data,
                  type=editor._.nextPasteType || dataObj.type,
                  data=dataObj.dataValue,
                  trueType,
                  // Default is 'html'.
                  defaultType=editor.config.clipboard_defaultContentType || 'html',
                  transferType=dataObj.dataTransfer.getTransferType( editor ),
                  isExternalPaste=transferType==CKEDITOR.DATA_TRANSFER_EXTERNAL,
                  isActiveForcePAPT=editor.config.forcePasteAsPlainText===true;
          
              // If forced type is 'html' we don't need to know true data type.
              if ( type=='html' || dataObj.preSniffing=='html' ) {
                  trueType='html';
              } else {
                  trueType=recogniseContentType( data );
              }
          
              delete editor._.nextPasteType;
          
              // Unify text markup.
              if ( trueType=='htmlifiedtext' ) {
                  data=htmlifiedTextHtmlification( editor.config, data );
              }
          
              // Strip presentational markup & unify text markup.
              // Forced plain text (dialog or forcePAPT).
              // Note: we do not check dontFilter option in this case, because forcePAPT was implemented
              // before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so
              // forcePAPT should have priority as it had before 4.5.
              if ( type=='text' && trueType=='html' ) {
                  data=filterContent( editor, data, filtersFactory.get( 'plain-text' ) );
              }
              // External paste and pasteFilter exists and filtering isn't disabled.
              // Or force filtering even for internal and cross-editor paste, when forcePAPT is active (#620).
              else if ( isExternalPaste && editor.pasteFilter && !dataObj.dontFilter || isActiveForcePAPT ) {
                  data=filterContent( editor, data, editor.pasteFilter );
              }
          
              if ( dataObj.startsWithEOL ) {
                  data='<br data-cke-eol="1">' + data;
              }
              if ( dataObj.endsWithEOL ) {
                  data +='<br data-cke-eol="1">';
              }
          
              if ( type=='auto' ) {
                  type=( trueType=='html' || defaultType=='html' ) ? 'html' : 'text';
              }
          
              dataObj.type=type;
              dataObj.dataValue=data;
              delete dataObj.preSniffing;
              delete dataObj.startsWithEOL;
              delete dataObj.endsWithEOL;
          }, null, null, 6 );

          這個主要是根據不同的typetrueType來對數據進行一些過濾操作

          插入粘貼數據

          粘貼的數據總得進入到編輯器吧,這就靠它了。

          editor.on( 'paste', function( evt ) {
              var data=evt.data;
              if ( data.dataValue ) {
                  editor.insertHtml( data.dataValue, data.type, data.range );
          
                  // Defer 'afterPaste' so all other listeners for 'paste' will be fired first.
                  // Fire afterPaste only if paste inserted some HTML.
                  setTimeout( function() {
                      editor.fire( 'afterPaste' );
                  }, 0 );
              }
          }, null, null, 1000 );

          這個就比較簡單了,但是也很重要,等paste事件系統的回調函數和用戶添加的回調函數執行完畢后,這個回調函數作為最后執行的(如果前面的回調函數沒有執行evt.stop()或者evt.cancel()),將evt.data.dataValue的值插入到編輯器中。

          我們可以再多看一下/plugins/clipboard/plugin.js文件,里面有個對工具欄增加粘貼按鈕,加上pasteCommand的操作

          {
          
              exec: function( editor, data ) {
              data=typeof data !=='undefined' && data !==null ? data : {};
          
              var cmd=this,
                  notification=typeof data.notification !=='undefined' ? data.notification : true,
                  forcedType=data.type,
                  keystroke=CKEDITOR.tools.keystrokeToString( editor.lang.common.keyboard,
                      editor.getCommandKeystroke( this ) ),
                  msg=typeof notification==='string' ? notification : editor.lang.clipboard.pasteNotification
                      .replace( /%1/, '<kbd aria-label="' + keystroke.aria + '">' + keystroke.display + '</kbd>' ),
                  pastedContent=typeof data==='string' ? data : data.dataValue;
          
              function callback( data, withBeforePaste ) {
                  withBeforePaste=typeof withBeforePaste !=='undefined' ? withBeforePaste : true;
          
                  if ( data ) {
                      data.method='paste';
          
                      if ( !data.dataTransfer ) {
                          data.dataTransfer=clipboard.initPasteDataTransfer();
                      }
          
                      firePasteEvents( editor, data, withBeforePaste );
                  } else if ( notification && !editor._.forcePasteDialog ) {
                      editor.showNotification( msg, 'info', editor.config.clipboard_notificationDuration );
                  }
          
                  // Reset dialog mode (#595).
                  editor._.forcePasteDialog=false;
          
                  editor.fire( 'afterCommandExec', {
                      name: 'paste',
                      command: cmd,
                      returnValue: !!data
                  } );
              }
          
              // Force type for the next paste. Do not force if `config.forcePasteAsPlainText` set to true or 'allow-word' (#1013).
              if ( forcedType && editor.config.forcePasteAsPlainText !==true && editor.config.forcePasteAsPlainText !=='allow-word' ) {
                  editor._.nextPasteType=forcedType;
              } else {
                  delete editor._.nextPasteType;
              }
          
              if ( typeof pastedContent==='string' ) {
                  callback( {
                      dataValue: pastedContent
                  } );
              } else {
                  editor.getClipboardData( callback );
              }
          }

          上面的callback會執行firePasteEvents,然后觸發paste事件。 如果pastedContent不是字符串的話,會先執行 editor.getClipboardData,該方法中有一個目前看到的優先級最好的paste回調

          editor.on( 'paste', onPaste, null, null, 0 );
          
          function onPaste( evt ) {
              evt.removeListener();
              evt.cancel();
              callback( evt.data );
          }

          onPaste方法里面會移除當前的回調函數,并取消掉后面未執行的paste回調,然后執行callback,也就是說它會觸發一輪新的paste回調函數執行。

          通過pasteTools插件來注冊paste回調

          {
              register: function(definition) {
                  if (typeof definition.priority !=='number')
                  {
                      definition.priority=10;
                  }
          
                  this.handlers.push(definition);
              },
              addPasteListener: function( editor ) {
                  editor.on( 'paste', function( evt ) {
                      var handlers=getMatchingHandlers( this.handlers, evt ),
                          filters,
                          isLoaded;
          
                      if ( handlers.length===0 ) {
                          return;
                      }
          
                      filters=getFilters( handlers );
          
                      isLoaded=loadFilters( filters, function() {
                          return editor.fire( 'paste', evt.data );
                      } );
          
                      if ( !isLoaded ) {
                          return evt.cancel();
                      }
          
                      handlePaste( handlers, evt );
                  }, this, null, 3 );
              }
          }
          ...
          function getMatchingHandlers( handlers, evt ) {
              return CKEDITOR.tools.array.filter( handlers, function( handler ) {
                  return handler.canHandle( evt );
              } ).sort( function( handler1, handler2 ) {
                  if ( handler1.priority===handler2.priority ) {
                      return 0;
                  }
          
                  return handler1.priority - handler2.priority;
              } );
          }
          
          function handlePaste( handlers, evt ) {
              var handler=handlers.shift();
          
              if ( !handler ) {
                  return;
              }
          
              handler.handle( evt, function() {
                  handlePaste( handlers, evt );
              } );
          }

          這個會把通過它注冊的回調函數放進自己的handlers里面,而不跟上面那些直接監聽paste放在一起,只有該組件自身才監聽paste事件,優先級為3。這等于是將通過pasteTools.register注冊的這一組回調全部按照了優先級為3的順序來執行了,當然,這一組的回調直接同樣按照優先級高低來執行,并且會根據其canHandle方法返回的值來過濾該回調是否執行,通過其handle來執行回調邏輯。

          通過對源碼的搜索,發現CKEditor大部分官方提供的對粘貼進行干預的插件都是通過pasteTools.register注冊的。

          總結

          通過對pasteTools插件的學習,我們可以對自己想做系統級事件和用戶級事件的分離的方式多一點啟發,我們假設editor.on('paste')這種模式是系統級別的,只允許系統級插件有這種操作,而用戶級插件不行,用戶級插件只能通過系統級插件pasteTools暴露出來的register來注冊,我們可以根據用戶級插件的canHandle方法來讓該插件只處理自己希望處理的那一部分。 類似的這種分離方法,也能更好地降低用戶級插件對整個系統的影響


          向以活好著稱的我給大家來點精華,不搞假大空,直接上實操,不用看懂,收藏就完了,以后你肯定會用到的。

          今天帶來的是前端開發中經常碰到的數字問題,解決方式有些過于粗暴,未來還會不斷美化更新。

          也歡迎大家關注我的Github(點擊原文鏈接),共同學習,共同提高。編者不才,如有問題,歡迎雅正,若有收獲,請盡情用star羞辱我。

          充分利用JavaScript自帶原生方法解決問題

          前端頁面和數據處理雖然看起來簡單,但是一個優秀的前端工程師對于細節的把控也是至關重要的,如何合理處理業務也體現前端工程師對性能優化的功力

          獲取數組最大值和最小值

          • 利用sort排序方法 針對數組進行排序,數組第一個和最后一個就是最大值和最小值
          let arr=[1,2,3,4,5,6,7];
          arr.sort(function (a, b) {
          	 return a-b;
          }); 
          //sort傳入一個排序函數,a-b為升序排序,b-a為降序排序
          let min=arr[0]; // 1
          let max=arr[arr.length - 1]; //7
          
          • 使用Math中的max/min方法 可以使用apply來實現。apply傳入的是一個數組
          let arr=[1,2,3,4,5,6,7];
          let max=Math.max.apply(null, arr);
          let min=Math.min.apply(null, arr);
          console.log(max, min) // 7,1
          

          浮點數取整

          • 丟棄小數部分,保留整數部分 parseInt(7/2)
          • 向上取整,有小數就整數部分加1 Math.ceil(7/2)
          • 四舍五入 Math.round(7/2)
          • 向下取整 Math.floor(7/2)
          • 注:都是JS內置對象

          數組去重

          • ES6
          arr2=[1,1,1,2,2,2,3,4]
          arr1=Array.from(new Set(arr2)) //arr1=[1,2,3,4]
          
          • push( )去重
          let arr3=[];  
          for(var i=0; i < arr.length; i++) {  
              (function(i) {  
                  if(arr3.indexOf(arr[i])==-1) { //不包含該值則返回-1  
                      arr3.push(arr[i]);  
                  }  
              }(i))  
          }  
          console.log(arr3); 
          //如果當前數組的第i項在當前數組中第一次出現的位置不是i,那么表示第i項是重復的,忽略掉。否則存入結果數組  
          let arr4=[arr[0]];  
          for(var i=1; i < arr.length; i++) {  
              (function(i) {  
                  if(arr.indexOf(arr[i])==i) {  
                      arr4.push(arr[i]);  
                  }  
              }(i))  
          }  
          console.log(arr4);  
          
          • sort( )去重
          let arrSort=arr.sort();  
          let arr5=[];  
          for(let i=0; i< arrSort.length; i++) {  
              if(arrSort[i] !=arrSort[i+1]) {  
                  arr5.push(arrSort[i]);  
              }  
          }  
          console.log(arr5);  
          
          • splice( )去重
          for(let i=0, len=arr6.length; i < len; i++) {  
              for(let j=i + 1; j < len; j++) {  
                  if(arr6[i]===arr6[j]) {  
                      arr6.splice(i,1);  
                      len--;  
                      j--;  
                  }  
              }  
          }  
          console.log(arr6); 
          

          用好 filter, map, every, find和其它 ES6 新增的高階遍歷函數

          • 將數組中的false值去掉
          const array=[3, 4, 5, 2, 3, undefined, null, 0, ""];
          const compact=arr=> arr.filter(Boolean);
          compact(array);
          //[3, 4, 5, 2, 3]
          
          • 判斷用戶是否全部是成年人
          const users=[
            { name: "Jim", age: 23 },
            { name: "Lily", age: 17 },
            { name: "Will", age: 35 }
          ];
          users.every(user=> user.age >=18);
          //false
          
          • 判斷用戶中是否有30歲以上的人
          const users=[
            { name: "Jim", age: 23 },
            { name: "Lily", age: 17 },
            { name: "Will", age: 35 }
          ];
          users.find(item=> item.age>30);
          //{name: "Will", age: 35}
          

          JS判斷點擊區域

          • 適用場景:顯示頁面彈窗時,用戶點擊頁面其他地方彈窗自動關閉.
          // HTML exitContent.vue
          <div id="facePanel">
          	<div v-show="showFace">這是個彈窗</div>
          </div>
          
          // JavaScript
          handleClick(e) {
          	let facePanel=document.getElementById('facePanel')
          	if(!facePanel.contains(e.target) && this.$refs.editContent.showFace){
          	  this.$refs.editContent.showFace=false
          	}
          },
          

          合理利用正則表達式解決問題

          <input> 標簽輸入限制

          • 文本框只能輸入數字代碼(小數點也不能輸入)
          <input onkeyup="this.value=this.value.replace(/\D/g,'')"  
          onafterpaste="this.value=this.value.replace(/\D/g,'')"> 
          
          • 只能輸入數字,能輸小數點.
          <input onkeyup="if(isNaN(value))execCommand('undo')" onafterpaste="if(isNaN(value))execCommand('undo')"> 
          <input name=txt1 onchange="
          	if(/\D/.test(this.value)){
          	alert('只能輸入數字');
          	this.value='';}"> 
          
          • 數字和小數點方法二
          <input type=text tvalue="" ovalue="" onkeypress="
          	if(!this.value.match(/^[\+\-]?\d*?\.?\d*?$/))this.value=this.t_value;
          		else this.tvalue=this.value;
          	if(this.value.match(/^(?:[\+\-]?\d+(?:\.\d+)?)?$/))this.ovalue=this.value" 
          		onkeyup="if(!this.value.match(/^[\+\-]?\d*?\.?\d*?$/))this.value=this.t_value;
          		else this.tvalue=this.value;
          	if(this.value.match(/^(?:[\+\-]?\d+(?:\.\d+)?)?$/))this.ovalue=this.value" 
          		onblur="if(!this.value.match(/^(?:[\+\-]?\d+(?:\.\d+)?|\.\d*?)?$/))this.value=this.o_value;
          		else{if(this.value.match(/^\.\d+$/))this.value=0+this.value;
          	if(this.value.match(/^\.$/))this.value=0;this.ovalue=this.value}">
          
          • 只能輸入字母和漢字
          <input onkeyup="value=value.replace(/[\d]/g,'') "
          		onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[\d]/g,''))" 
          		maxlength=10 name="Numbers"> 
          <input onkeyup="value=value.replace(/[^\a-zA-Z\u4E00-\u9FA5]/g,'')" 		onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^\a-zA-Z\u4E00-\u9FA5]/g,''))" 
          		maxlength=10 name="Numbers"> 
          
          • 只能輸入英文字母和數字,不能輸入中文
          <input onkeyup="value=value.replace(/[^\w\.\/]/ig,'')"> 
          
          • 只能輸入數字和英文
          <font color="Red">chun</font> 
          <input onKeyUp="value=value.replace(/[^\d|chun]/g,'')"> 
          
          • 小數點后只能有最多兩位(數字,中文都可輸入),不能輸入字母和運算符號:
          <input onKeyPress="
          	if((event.keyCode<48 || event.keyCode>57) && event.keyCode!=46 || /\.\d\d$/.test(value))
          	event.returnValue=false"> 
          
          • 小數點后只能有最多兩位(數字,字母,中文都可輸入),可以輸入運算符號:
          <input onkeyup="this.value=this.value.replace(/^(\-)*(\d+)\.(\d\d).*$/,'$1$2.$3')"> 
          
          • input的type設置為number后可以輸入e
          <input type='number' onkeypress='return( /[\d]/.test(String.fromCharCode(event.keyCode) ) )' />
          
          // 匹配郵箱
          let reg=/^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$
          
          // (新)匹配手機號
          let reg=/^1[0-9]{10}$/;
          
          // (舊)匹配手機號
          let reg=/^1[3|4|5|7|8][0-9]{9}$/;
          
          // 匹配8-16位數字和字母密碼的正則表達式
          let reg=/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{8,16}$/;
          
          // 匹配國內電話號碼 0510-4305211
          let reg=/\d{3}-\d{8}|\d{4}-\d{7}/;
          
          // 匹配身份證號碼
          let reg=/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
          
          // 匹配騰訊QQ號
          let reg=/[1-9][0-9]{4,}/;
          
          // 匹配ip地址
          let reg=/\d+\.\d+\.\d+\.\d+/;
          
          // 匹配中文
          let reg=/^[\u4e00-\u9fa5]*$/;
          

          JS判斷字符串是否全是空格

          let test="   \n   ";
          if(test.match(/^\s+$/)){
              console.log("all space or \\n")
          }
          if(test.match(/^[ ]+$/)){
              console.log("all space")
          }
          if(test.match(/^[ ]*$/)){
              console.log("all space or empty")
          }
          if(test.match(/^\s*$/)){
              console.log("all space or \\n or empty")
          }
          

          常用正則匹配

          • /^\d+$/  //非負整數(正整數 + 0)
          • /^[0-9]*[1-9][0-9]*$/  //正整數
          • /^((-\d+)|(0+))$/  //非正整數(負整數 + 0)
          • /^-[0-9]*[1-9][0-9]*$/  //負整數
          • /^-?\d+$/    //整數
          • let reg=/^([^0][0-9]+|0)$/; if (reg.test(params.xzd2BorrowRate)) { params.xzd2BorrowRate +='.0'; } //整數
          • /^\d+(\.\d+)?$/  //非負浮點數(正浮點數 + 0)
          • /^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$/  //正浮點數
          • /^((-\d+(\.\d+)?)|(0+(\.0+)?))$/  //非正浮點數(負浮點數 + 0)
          • /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/  //負浮點數
          • /^(-?\d+)(\.\d+)?$/  //浮點數
          • replace(/[^0-9]/ig, "") //只保留數字

          JS擴大checkbox的點擊區域

          • 試用場景:在表格中點擊 td 也能選中 ,或者在父節點也能點擊觸發事件
          • 做法:為 td 元素添加點擊事件,在點擊事件里面觸發checkbox的點擊事件:
          <td onclick="clickTd($(this))">
          	<input type="checkbox" name="task"  onclick="oncheckAll(event)">
          </td>
          
          function clickTd($t){
              $t.children("input").click();
          }
          
          • bug浮現:點擊checkbox的時候,因為頁面元素所在區域重疊,點擊事件向上傳遞,會首先觸發checkbox的點擊事件,然后觸發 td 的點擊事件,然后 td 的點擊事件會被再次觸發,就相當于checkbox點擊了兩次,所以需要 checkbox 的點擊事件執行完之后,讓該點擊事件停止傳遞。
          • 解決方案:checkbox的點擊事件里面加入攔截即可:
          function oncheckAll(event){
              event.stopPropagation();
          }
          

          計算時間戳間隔時間

          let date1=new Date();  //開始時間  
          let date2=new Date();    //結束時間  
          let date3=date2.getTime()-date1.getTime()  //時間差的毫秒數  
          ------------------------------------------------------------
          //計算出相差天數  
          var days=Math.floor(date3/(24*3600*1000))   
          //計算出小時數   
          var leave1=date3%(24*3600*1000)    //計算天數后剩余的毫秒數  
          var hours=Math.floor(leave1/(3600*1000))  
          //計算相差分鐘數  
          var leave2=leave1%(3600*1000)        //計算小時數后剩余的毫秒數  
          var minutes=Math.floor(leave2/(60*1000))  
          //計算相差秒數  
          var leave3=leave2%(60*1000)      //計算分鐘數后剩余的毫秒數  
          var seconds=Math.round(leave3/1000)  
          alert(" 相差 "+days+"天 "+hours+"小時 "+minutes+" 分鐘"+seconds+" 秒")
          

          JS提取字符串中連續的數字

          let str="013d1we22ewfa33rr4rwq0dsf00dsf9fas999";
          
          let getNum=function (Str,isFilter) {
             //用來判斷是否把連續的0去掉
              isFilter=isFilter || false;
              if (typeof Str==="string") {
                  // var arr=Str.match(/(0\d{2,})|([1-9]\d+)/g);
                  //"/[1-9]\d{1,}/g",表示匹配1到9,一位數以上的數字(不包括一位數).
                  //"/\d{2,}/g",  表示匹配至少二個數字至多無窮位數字
                  var arr=Str.match( isFilter ? /[1-9]\d{1,}/g : /\d{2,}/g);
                  console.log(arr);
                  return arr.map(function (item) {
                      //轉換為整數,
                      //但是提取出來的數字,如果是連續的多個0會被改為一個0,如000---->0,
                      //或者0開頭的連續非零數字,比如015,會被改為15,這是一個坑
                      // return parseInt(item);
                      //字符串,連續的多個0也會存在,不會被去掉
                      return item;
                  });
              } else {
                  return [];
              }
          }
          console.log(getNum(str));//默認不加1,即不會把提取出來的0去掉
          

          JS獲取項目根目錄

          function getRootPath(){
              //獲取當前網址,如: http://localhost:8083/uimcardprj/share/meun.jsp
              var curWwwPath=window.document.location.href;
              //獲取主機地址之后的目錄,如: uimcardprj/share/meun.jsp
              var pathName=window.document.location.pathname;
              var pos=curWwwPath.indexOf(pathName);
              //獲取主機地址,如: http://localhost:8083
              var localhostPaht=curWwwPath.substring(0,pos);
              //獲取帶"/"的項目名,如:/uimcardprj
              var projectName=pathName.substring(0,pathName.substr(1).indexOf('/')+1);
              return(localhostPaht+projectName);
          }
          

          JS判斷小數點后幾位

          • num.toString().split(".")[1].length

          JS截取數字小數點后N位

          • 利用math.round
          var original=28.453
          // 保留小數點后兩位
          var result=Math.round(original*100)/100;  //returns 28.45
          // 保留小數點后一位
          var result=Math.round(original*10)/10;  //returns 28.5
          
          • 利用toFixed
          var original=28.453
          // 保留小數點后兩位
          var result=original.toFixed(2); //returns 28.45
          // 保留小數點后一位
          var result=original.toFixed(1); //returns 28.5
          

          JS控制Input輸入數字和小數點后兩位

          function clearNoNum(value) {
              value=value.replace(/[^\d.]/g, "");  //清除“數字”和“.”以外的字符   
              value=value.replace(/\.{2,}/g, "."); //只保留第一個. 清除多余的   
              value=value.replace(".", "$#$").replace(/\./g, "").replace("$#$", ".");
              value=value.replace(/^(\-)*(\d+)\.(\d\d).*$/, '$1$2.$3'); //只能輸入兩個小數   
              if (value.indexOf(".") < 0 && value !="") { //以上已經過濾,此處控制的是如果沒有小數點,首位不能為類似于 01、02的金額  
                  value=parseFloat(value);
              }
          }
          

          JS校驗手機號和座機號的方法

          function $isPhoneAvailable(str) {
              let isMob=/^[1][3,4,5,6,7,8,9][0-9]{9}$/;
              let isPhone=/^([0-9]{3,4}-)?[0-9]{7,8}$/;
              if (isPhone.test(str) || isMob.test(str)) {
                  return true;
              }else {
                  return false;
              }
          }
          

          JS indexOf() 不區分大小寫實現

          • 使用 str.toLowerCase() 或者 str.toUpperCase() 將對比字符串全都轉換成 大 (小) 寫

          JS replace() 如何針對key進行替換

          • str.replace(new RegExp('要替換的keyword','g'), '替換后的內容')

          明節是我們追思故人的時節,

          疫情給今年清明帶來了一絲不一樣的意味。

          今天我們來聊聊清明節和國外緬懷故人的方式。


          Tomb Sweeping Day


          清明節的英文暗示了在這個節日我們本該做的事:

          • Tomb Sweeping Day: 清明節
          • Tomb: /tu?m/ n. 墳墓
          • Sweep: /swi?p/ v. 清掃

          如果對方對中國文化比較了解,也可以直接說:

          • Qingming Festival


          Solar term


          其實清明是二十四節氣之一:

          • Solar term: 節氣

          清明節的日期是這么算的:

          • It is the 15th day after the Spring Equinox, either 4 or 5 April in a given year. 它是春分后的第15天,是4月4號或者5號。


          To observe Qingming Festival


          別的節日可以慶祝(celebrate),但是清明節卻有其特殊性,只能用 observe:

          • To observe Qingming Festival
          • To observe Tomb Sweeping Day

          外國人雖然沒有在清明掃墓的習慣,也會在特定的日期去墓碑前緬懷故人:

          • To visit the cemetery: 去墓園

          雖然 graveyard 也有墓園的意思,但通常都和教堂掛鉤, cemetery 則指更普遍意義上的墓地。


          Filial piety


          不管去不去掃墓,重要的是要追思故人,通常是緬懷長輩,是傳達孝道的一種方式:

          • Filial piety: /?f?li?l ?pa??ti/ n. 孝道

          如果覺得這種說法太正式,可以說:

          • Respect/ honor your parents: 尊敬父母
          • Be big on family: 重視家庭
          • Family guys: 把家庭看得很重的人(一般指男性)


          Pass away


          雖然英文中 die 也可以形容去世,但是我們也可以選擇婉轉一點說這件悲痛的事:

          • Pass away: 去世
          • I lost someone. 我失去了某人。
          • Someone is in a better place. 某人去了更好的地方。
          • Rest in peace (RIP): 安息

          國外現在很多人不再稱葬禮為 funeral,而是用下面這個短語來換個角度看待死亡:

          • A celebration of life: 生命的儀式


          Green dumplings


          回到清明節,必不可少的中國傳統小吃就是:

          • Green dumplings/ Green rice balls: 青團

          很多來自中國的點心都被叫做 dumplings,對于青團你可以這么解釋:

          • It's made of glutinous rice and barley grass with red bean paste. 青團是豆沙餡兒的糯米艾草團子。

          當然每個地方的青團也可能會采用不同的原料。


          你和故人曾有過哪些美好的回憶?

          你想對故人說些什么?

          歡迎大家留言。


          主站蜘蛛池模板: 国产伦精品一区二区免费| 黄桃AV无码免费一区二区三区| 国产婷婷色一区二区三区深爱网| 农村人乱弄一区二区| 国内精品视频一区二区八戒| 精品无码人妻一区二区三区品| 日韩A无码AV一区二区三区| 亚洲性色精品一区二区在线| 亚洲av一综合av一区| 国产一区麻豆剧传媒果冻精品| 性盈盈影院免费视频观看在线一区| 亚洲电影一区二区三区| 国产一区二区三区免费| 亚洲无人区一区二区三区| 奇米精品一区二区三区在| 国产福利电影一区二区三区久久老子无码午夜伦不 | 国产一区二区三区乱码| 一区二区高清在线| 日韩社区一区二区三区| 色多多免费视频观看区一区| 日韩精品中文字幕视频一区| 免费高清在线影片一区| 午夜DV内射一区区| 免费人妻精品一区二区三区| 日本在线视频一区| 国产精品xxxx国产喷水亚洲国产精品无码久久一区| 波多野结衣久久一区二区| 99精品一区二区三区无码吞精| 国产一区二区成人| 国产在线观看一区二区三区四区| 免费观看日本污污ww网站一区| 色系一区二区三区四区五区| 一区二区三区杨幂在线观看 | 亚洲av无码一区二区三区观看| 欧洲无码一区二区三区在线观看| 亚洲国产一区在线观看| 久久精品免费一区二区喷潮| 国产亚洲福利一区二区免费看 | 中文字幕乱码亚洲精品一区| 女人18毛片a级毛片一区二区| 日本一区二区在线不卡|