Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 久久精品高清,正在播放露脸一区,又大又粗好舒服好爽视频

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          js 常用簡(jiǎn)寫技巧(干貨滿滿)

          js 常用簡(jiǎn)寫技巧(干貨滿滿)

          享一些自己常用的js簡(jiǎn)寫技巧,長(zhǎng)期更新,會(huì)著重挑選一些實(shí)用的簡(jiǎn)寫技巧,使自己的代碼更簡(jiǎn)潔優(yōu)雅~

          這里只會(huì)收集一些大多數(shù)人不知道的用法,但是確實(shí)能提高自己的編碼技巧,像ES6那些基礎(chǔ)的簡(jiǎn)寫語法或者是三目運(yùn)算符代替if else那些我覺得是基礎(chǔ),沒必要寫在這里浪費(fèi)精力。

          注意本篇內(nèi)容涉及到的語法從ES6到ES11不等,具體使用需要考慮項(xiàng)目兼容性問題!

          另外推薦一個(gè)只用一行代碼實(shí)現(xiàn)一個(gè)方法的實(shí)用網(wǎng)站1loc.dev,github

          If-Else 用 || 或 ?? 運(yùn)算符進(jìn)行簡(jiǎn)化

          邏輯或操作符||,這里要注意的是0和''也會(huì)認(rèn)為是false

          如果||前面的值是0 '' false null undefined NaN其中的任意一種,則直接返回||后面的值

          function(obj){
            var a=obj || {}
          }
          // 等價(jià)于=>>
          function(obj){
            var a;
            if(
              obj===0 || 
              obj==="" || 
              obj===false || 
              obj===null || 
              obj===undefined ||
              isNaN(obj)
            ){
               a={}
             } else {
               a=obj;
            }
          }
          


          空值合并操作符??如果沒有定義左側(cè)返回右側(cè)。如果是,則返回左側(cè)。

          這種方法非常實(shí)用,有時(shí)候僅僅只是想判斷一個(gè)字段有沒有值,而不是把空字符串或者0也當(dāng)做false處理

          function(obj){
            var a=obj ?? {}
          }
          // 等價(jià)于=>>
          function(obj){
            var a;
            if(
              obj===null || 
              obj===undefined
            ){
               a={}
             } else {
               a=obj;
            }
          }
          


          輸入框非空的判斷(有時(shí)候不想把0當(dāng)成false可以用此方法。比如分?jǐn)?shù)0也是個(gè)值,這種情況就不能認(rèn)為是false)

          if(value !==null && value !==undefined && value !==''){}
          // 等價(jià)于==>
          if((value ?? '') !==''){}
          


          includes的正確使用姿勢(shì)

          在上面邏輯或操作符||代碼段里有一個(gè)if判斷比較長(zhǎng),這時(shí)候就可以用includes去簡(jiǎn)化代碼

          if(
            obj===0 || 
            obj==="" || 
            obj===false || 
            obj===null || 
            obj===undefined
          ){
            // ...
          }
          
          // 使用includes簡(jiǎn)化
          if([0, '', false, null, undefined].includes(obj)){
            // ...
          }
          


          防止崩潰的可選鏈(?.)

          可選鏈操作符?. 如果訪問未定義的屬性,則會(huì)產(chǎn)生錯(cuò)誤。這就是可選鏈的用武之地。 在未定義屬性時(shí)使用可選鏈運(yùn)算符,undefined將返回而不是錯(cuò)誤。這可以防止你的代碼崩潰。

          const student={
            name: "lwl",
            address: {
              state: "New York"
            },
          }
          
          // 一層一層判斷
          console.log(student && student.address && student.address.ZIPCode) // 輸出:undefined
          // 使用可選鏈操作符
          console.log(student?.address?.ZIPCode) // 輸出:undefined
          


          可選鏈運(yùn)算符也可以用于方法調(diào)用。如果方法存在,它將被調(diào)用,否則將返回 undefined。例如:

          const obj={
            foo() {
              console.log('Hello from foo!')
            }
          }
          
          obj.foo?.() // 輸出:'Hello from foo!'
          obj.bar?.() // 輸出:undefined,因?yàn)?bar 方法不存在
          


          同樣,數(shù)組也可以使用。例如:

          const arr=[1, 2, 3, 4]
          
          console.log(arr[0]) // 輸出:1
          console.log(arr[4]) // 輸出:undefined
          
          // 使用可選鏈運(yùn)算符
          console.log(arr?.[0]) // 輸出:1
          console.log(arr?.[4]) // 輸出:undefined
          console.log(arr?.[0]?.toString()) // 輸出:'1'
          
          // 多維數(shù)組也可以
          const arr2=[[[1]]]
          console.log(arr2?.[0]?.[0]?.[0]) // 輸出:1
          console.log(arr2?.[0]?.[1]?.[0]) // 輸出:undefined
          


          邏輯空賦值(??=)

          邏輯空賦值??=邏輯空賦值運(yùn)算符(x ??=y)僅在 x 是 nullish (null 或 undefined) 時(shí)對(duì)其賦值。

          const a={ duration: 50 };
          
          a.duration ??=10;
          console.log(a.duration);
          // expected output: 50
          
          a.speed ??=25;
          console.log(a.speed);
          // expected output: 25
          


          快速生成1-10的數(shù)組

          生成0-9,利用了數(shù)組的下標(biāo)值。推薦使用方法二或方法三,比較靈活,改成(v, k)=> k + 1就是1-10

          // 方法一
          const arr1=[...new Array(10).keys()]
          // 方法二
          const arr2=Array.from(Array(10), (v, k)=> k)
          // 方法三
          const arr3=Array.from({length: 10}, (v, k)=> k)
          


          生成1-10,通過map的特性

          const arr1=[...Array(10)].map((v, i)=> i + 1)
          // 下面這兩個(gè)和上面生成0-9的方式一樣
          const arr2=Array.from(Array(10), (v, k)=> k + 1)
          const arr3=Array.from({length: 10}, (v, k)=> k + 1)
          


          快速生成10個(gè)0的數(shù)組

          const arr=new Array(10).fill(0)
          


          快速生成10個(gè)[]的數(shù)組(二維數(shù)組)

          注意: 二維數(shù)組不能直接寫成new Array(10).fill([])(也就是fill方法不能傳引用類型的值,[]換成new Array()也不行),因?yàn)閒ill里傳入引用類型值會(huì)導(dǎo)致每一個(gè)數(shù)組都指向同一個(gè)地址,改變一個(gè)數(shù)據(jù)的時(shí)候其他數(shù)據(jù)也會(huì)隨之改變,詳見mdn官方說明

          // 錯(cuò)誤寫法
          const arr=new Array(10).fill([]) // 注意這是錯(cuò)誤寫法,不要這么寫
          // 正確寫法
          const arr=new Array(10).fill().map(()=> new Array())
          


          數(shù)組降維

          你是否還在用遞歸給一個(gè)多維數(shù)組降維?如果是,那你應(yīng)該知道一下es6的 flat() 方法。

          如果不確定需要降維的數(shù)組有多深,可以傳入最大值作為參數(shù)Infinity,默認(rèn)值深度為1

          const arr=[1, [2, [3, 4], 5], 6]
          const flatArr=arr.flat(Infinity) // 輸出 [1, 2, 3, 4, 5, 6]
          


          你是否在使用map的時(shí)候想要對(duì)數(shù)組降維?大概像這樣:

          const arr=[1, 2, 3, 4]
          const result=arr.map(v=> [v, v * 2]).flat()
          console.log(result); // 輸出 [1, 2, 2, 4, 3, 6, 4, 8]
          


          其實(shí)js也提供了更簡(jiǎn)便的方法,那就是flatMap(),可以改成這樣:

          const result=arr.flatMap(v=> [v, v * 2])
          


          Set集合實(shí)踐:數(shù)組去重、交集、并集、差集

          js// 1.數(shù)組去重 
          let arr1=[1, 3, 2, 1, 4, 2, 1] 
          let result1=[...new Set(arr1)] 
          console.log(result1) // [1, 3, 2, 4] 
          
          // 2.交集 
          let arr2=[4, 3, 4, 7] 
          let result2=[...new Set(arr1)].filter(v=> new Set(arr2).has(v)) 
          console.log(result2) // [3, 4] 
          
          // 3.并集 
          let result3=[...new Set([...arr1, ...arr2])] 
          console.log(result3) // [1, 3, 2, 4, 7] 
          
          // 4.差集 
          let result4=[...new Set(arr1)].filter(v=> !(new Set(arr2).has(v))) 
          console.log(result4) // [1, 2]
          


          在沒有第三個(gè)變量的情況下交換兩個(gè)變量

          在 JavaScript 中,你可以使用解構(gòu)從數(shù)組中拆分值。這可以應(yīng)用于交換兩個(gè)變量而無需第三個(gè)

          比較簡(jiǎn)單,es6語法

          let x=1;
          let y=2;
          
          // 交換變量
          [x, y]=[y, x];
          


          將對(duì)象的值收集到數(shù)組中

          用于Object.values()將對(duì)象的所有值收集到一個(gè)新數(shù)組中

          const info={ name: "Matt", country: "Finland", age: 35 };
          
          // LONGER FORM
          let data=[];
          for (let key in info) {
            data.push(info[key]);
          }
          
          // SHORTHAND
          const data=Object.values(info);
          


          指數(shù)運(yùn)算符(用的不多)

          你Math.pow()習(xí)慣把一個(gè)數(shù)字提高到一個(gè)冪嗎?你知道你也可以使用**運(yùn)算符嗎?

          雖然可以簡(jiǎn)寫,不過我還是建議寫成Math.pow()方法,代碼更有語義化。

          注意:**運(yùn)算符要求操作數(shù)為數(shù)值類型,不過在js里也能正常運(yùn)行。

          Math.pow(2, 3); // 輸出: 8 
          2 ** 3; // 輸出: 8 
          
          Math.pow(4, 0.5); // 輸出: 2 
          4 ** 0.5; // 輸出: 2 
          
          Math.pow(3, -2); // 輸出: 0.1111111111111111 
          3 ** -2; // 輸出: 0.1111111111111111 
          
          Math.pow('2', '3'); // 輸出: 8 (參數(shù)被自動(dòng)轉(zhuǎn)換為數(shù)字) 
          '2' ** '3'; // js中輸出: 8,其他語言可能報(bào)錯(cuò)
          


          Math.floor() 簡(jiǎn)寫(用的不多)

          向下取整Math.floor()并不是什么新鮮事。但是你知道你也可以使用~~運(yùn)算符嗎?

          同上雖然可以簡(jiǎn)寫,不過我還是建議寫成Math.floor()方法,代碼更有語義化。

          注意:對(duì)于正數(shù)而言兩者都是直接去掉小數(shù)位,但對(duì)于負(fù)數(shù)來說Math.floor()是向下取整,~~依然是只去掉小數(shù)位,整數(shù)位不變。 請(qǐng)看下面輸出結(jié)果:

          Math.floor(3.14); // 輸出: 3 
          Math.floor(5.7); // 輸出: 5 
          Math.floor(-2.5); // 輸出: -3 
          Math.floor(10); // 輸出: 10
          
          ~~3.14; // 輸出: 3 
          ~~5.7; // 輸出: 5 
          ~~(-2.5); // 輸出: -2 
          ~~10; // 輸出: 10
          


          逗號(hào)運(yùn)算符(,)

          逗號(hào), )運(yùn)算符對(duì)它的每個(gè)操作數(shù)從左到右求值,并返回最后一個(gè)操作數(shù)的值。這讓你可以創(chuàng)建一個(gè)復(fù)合表達(dá)式,其中多個(gè)表達(dá)式被評(píng)估,復(fù)合表達(dá)式的最終值是其成員表達(dá)式中最右邊的值。這通常用于為 for 循環(huán)提供多個(gè)參數(shù)。

          這里只說一下函數(shù)return的時(shí)候用逗號(hào)運(yùn)算符簡(jiǎn)化代碼的技巧,其他用法請(qǐng)直接點(diǎn)擊查看官方文檔。

          舉一個(gè)簡(jiǎn)單的例子:

          // 簡(jiǎn)化前
          const result=arr=> {
            arr.push('a')
            return arr
          }
          console.log(result([1,2])) // 輸出:[1, 2, 'a']
          


          這段代碼需要返回修改后的數(shù)組,不能直接return arr.push('a'),因?yàn)閜ush的返回值是修改后數(shù)組的長(zhǎng)度,這時(shí)候可以用逗號(hào)運(yùn)算符簡(jiǎn)化成一行代碼。

          // 簡(jiǎn)化后
          const result=arr=> (arr.push('a'), arr)
          console.log(result([1,2])) // 輸出:[1, 2, 'a']
          


          Array.map()的簡(jiǎn)寫

          比如想要拿到接口返回的特定字段的值,可以用解構(gòu)賦值和對(duì)象的簡(jiǎn)寫方法對(duì)map方法簡(jiǎn)寫,詳細(xì)解釋請(qǐng)移步j(luò)s map方法應(yīng)用場(chǎng)景 處理對(duì)象數(shù)組。

          比如接口返回?cái)?shù)據(jù),此時(shí)如果只想要數(shù)據(jù)里的id和name,就可以用下面的簡(jiǎn)寫方式。

          // 接口返回?cái)?shù)據(jù)
          res=[{
            id: 1,
            name: 'zhangsan',
            age: 16,
            gender: 0
          }, {
            id: 2,
            name: 'lisi',
            age: 20,
            gender: 1
          }]
          
          // 第一種方法:箭頭函數(shù)、 解構(gòu)賦值
          const data=res.map(({id, name})=> ({id, name}))
          // 第二種方法:箭頭函數(shù)、返回對(duì)象(相對(duì)更容易理解)
          const data=res.map(v=> ({id: v.id, name: v.name}))
          


          文字排序

          我們知道數(shù)組方法sort()默認(rèn)是按照UTF-16碼元值升序排序

          我們可以使用charCodeAt()方法獲取UTF-16碼元

          對(duì)于中文或者其他語言按照拼音排序(字典順序排序)則需要用到localeCompare()方法

          ['張三', '李四', '趙五', '王二', '陳六'].sort()
          // 輸出:['張三', '李四', '王二', '趙五', '陳六']
          ['張三', '李四', '趙五', '王二', '陳六'].sort((a, b)=> a.localeCompare(b, 'zh-Hans-CN'))
          // 輸出:['陳六', '李四', '王二', '張三', '趙五']
          
          ['apple', 'Banana', 'cherry', 'Date', 'apricot', 'Banana'].sort()
          // 輸出:['Banana', 'Banana', 'Date', 'apple', 'apricot', 'cherry']
          ['apple', 'Banana', 'cherry', 'Date', 'apricot', 'Banana'].sort((a, b)=> a.localeCompare(b, 'en'))
          // 輸出:['apple', 'apricot', 'Banana', 'Banana', 'cherry', 'Date']
          


          • 'zh' 表示主語言標(biāo)記,代表中文。
          • 'Hans' 是腳本子標(biāo)記,表示簡(jiǎn)體漢字。
          • 'CN' 是區(qū)域子標(biāo)記,表示中國(guó)。

          如果您不提供locales參數(shù)(第二個(gè)參數(shù)),localeCompare方法通常會(huì)默認(rèn)使用瀏覽器或操作系統(tǒng)的當(dāng)前語言環(huán)境

          at()方法獲取數(shù)組最后一位值

          // 獲取arr的最后一位值
          const arr=[1, 2, 3, 4, 5]
          // 一般寫法
          const last=arr[arr.length - 1]
          // 二般寫法
          const last=arr.slice(-1)[0]
          // 終極寫法
          const last=arr.at(-1)
          



          作者:lwlcode
          鏈接:https://juejin.cn/post/7236664417308524581

          知道瀏覽器和服務(wù)端是通過 HTTP 協(xié)議進(jìn)行數(shù)據(jù)傳輸?shù)模?HTTP 協(xié)議又是純文本協(xié)議,那么瀏覽器在得到服務(wù)端傳輸過來的 HTML 字符串,是如何解析成真實(shí)的 DOM 元素的呢,也就是我們常說的生成 DOM Tree,最近了解到狀態(tài)機(jī)這樣一個(gè)概念,于是就萌生一個(gè)想法,實(shí)現(xiàn)一個(gè) innerHTML 功能的函數(shù),也算是小小的實(shí)踐一下。

          函數(shù)原型

          我們實(shí)現(xiàn)一個(gè)如下的函數(shù),參數(shù)是 DOM 元素和 HTML 字符串,將 HTML 字符串轉(zhuǎn)換成真實(shí)的 DOM 元素且 append 在參數(shù)一傳入的 DOM 元素中。

          function html(element, htmlString) {
           // 1. 詞法分析
           // 2. 語法分析
           // 3. 解釋執(zhí)行
          }
          復(fù)制代碼
          

          在上面的注釋我已經(jīng)注明,這個(gè)步驟我們分成三個(gè)部分,分別是詞法分析、語法分析和解釋執(zhí)行。

          詞法分析

          詞法分析是特別重要且核心的一部分,具體任務(wù)就是:把字符流變成 token 流。

          詞法分析通常有兩種方案,一種是狀態(tài)機(jī),一種是正則表達(dá)式,它們是等效的,選擇你喜歡的就好。我們這里選擇狀態(tài)機(jī)。

          首先我們需要確定 token 種類,我們這里不考慮太復(fù)雜的情況,因?yàn)槲覀冎粚?duì)原理進(jìn)行學(xué)習(xí),不可能像瀏覽器那樣有強(qiáng)大的容錯(cuò)能力。除了不考慮容錯(cuò)之外,對(duì)于自閉合節(jié)點(diǎn)、注釋、CDATA 節(jié)點(diǎn)暫時(shí)均不做考慮。

          接下來步入主題,假設(shè)我們有如下節(jié)點(diǎn)信息,我們會(huì)分出哪些 token 來呢。

          <p class="a" data="js">測(cè)試元素</p>
          復(fù)制代碼
          

          對(duì)于上述節(jié)點(diǎn)信息,我們可以拆分出如下 token

          • 開始標(biāo)簽:<p
          • 屬性標(biāo)簽:class="a"
          • 文本節(jié)點(diǎn):測(cè)試元素
          • 結(jié)束標(biāo)簽:</p>

          狀態(tài)機(jī)的原理,將整個(gè) HTML 字符串進(jìn)行遍歷,每次讀取一個(gè)字符,都要進(jìn)行一次決策(下一個(gè)字符處于哪個(gè)狀態(tài)),而且這個(gè)決策是和當(dāng)前狀態(tài)有關(guān)的,這樣一來,讀取的過程就會(huì)得到一個(gè)又一個(gè)完整的 token,記錄到我們最終需要的 tokens 中。

          萬事開頭難,我們首先要確定起初可能處于哪種狀態(tài),也就是確定一個(gè) start 函數(shù),在這之前,對(duì)詞法分析類進(jìn)行簡(jiǎn)單的封裝,具體如下

          function HTMLLexicalParser(htmlString, tokenHandler) {
           this.token=[];
           this.tokens=[];
           this.htmlString=htmlString
           this.tokenHandler=tokenHandler
          }
          復(fù)制代碼
          

          簡(jiǎn)單解釋下上面的每個(gè)屬性

          • token:token 的每個(gè)字符
          • tokens:存儲(chǔ)一個(gè)個(gè)已經(jīng)得到的 token
          • htmlString:待處理字符串
          • tokenHandler:token 處理函數(shù),我們每得到一個(gè) token 時(shí),就已經(jīng)可以進(jìn)行流式解析

          我們可以很容易的知道,字符串要么以普通文本開頭,要么以<開頭,因此 start 代碼如下

          HTMLLexicalParser.prototype.start=function(c) {
           if(c==='<') {
           this.token.push(c)
           return this.tagState
           } else {
           return this.textState(c)
           }
          }
          復(fù)制代碼
          

          start處理的比較簡(jiǎn)單,如果是<字符,表示開始標(biāo)簽或結(jié)束標(biāo)簽,因此我們需要下一個(gè)字符信息才能確定到底是哪一類 token,所以返回tagState函數(shù)去進(jìn)行再判斷,否則我們就認(rèn)為是文本節(jié)點(diǎn),返回文本狀態(tài)函數(shù)。

          接下來分別展開tagState和textState函數(shù)。tagState根據(jù)下一個(gè)字符,判斷進(jìn)入開始標(biāo)簽狀態(tài)還是結(jié)束標(biāo)簽狀態(tài),如果是/表示是結(jié)束標(biāo)簽,否則是開始標(biāo)簽,textState用來處理每一個(gè)文本節(jié)點(diǎn)字符,遇到<表示得到一個(gè)完整的文本節(jié)點(diǎn) token,代碼如下

          HTMLLexicalParser.prototype.tagState=function(c) {
           this.token.push(c)
           if(c==='/') {
           return this.endTagState
           } else {
           return this.startTagState
           }
          }
          HTMLLexicalParser.prototype.textState=function(c) {
           if(c==='<') {
           this.emitToken('text', this.token.join(''))
           this.token=[]
           return this.start(c)
           } else {
           this.token.push(c)
           return this.textState
           }
          }
          復(fù)制代碼
          

          這里初次見面的函數(shù)是emitToken、startTagState和endTagState。

          emitToken用來將產(chǎn)生的完整 token 存儲(chǔ)在 tokens 中,參數(shù)是 token 類型和值。

          startTagState用來處理開始標(biāo)簽,這里有三種情形

          • 如果接下來的字符是字母,則認(rèn)定依舊處于開始標(biāo)簽態(tài)
          • 遇到空格,則認(rèn)定開始標(biāo)簽態(tài)結(jié)束,接下來是處理屬性了
          • 遇到>,同樣認(rèn)定為開始標(biāo)簽態(tài)結(jié)束,但接下來是處理新的節(jié)點(diǎn)信息

          endTagState用來處理結(jié)束標(biāo)簽,結(jié)束標(biāo)簽不存在屬性,因此只有兩種情形

          • 如果接下來的字符是字母,則認(rèn)定依舊處于結(jié)束標(biāo)簽態(tài)
          • 遇到>,同樣認(rèn)定為結(jié)束標(biāo)簽態(tài)結(jié)束,但接下來是處理新的節(jié)點(diǎn)信息

          邏輯上面說的比較清楚了,代碼也比較簡(jiǎn)單,看看就好啦

          HTMLLexicalParser.prototype.emitToken=function(type, value) {
           var res={
           type,
           value
           }
           this.tokens.push(res)
           // 流式處理
           this.tokenHandler && this.tokenHandler(res)
          }
          HTMLLexicalParser.prototype.startTagState=function(c) {
           if(c.match(/[a-zA-Z]/)) {
           this.token.push(c.toLowerCase())
           return this.startTagState
           }
           if(c===' ') {
           this.emitToken('startTag', this.token.join(''))
           this.token=[]
           return this.attrState
           }
           if(c==='>') {
           this.emitToken('startTag', this.token.join(''))
           this.token=[]
           return this.start
           }
          }
          HTMLLexicalParser.prototype.endTagState=function(c) {
           if(c.match(/[a-zA-Z]/)) {
           this.token.push(c.toLowerCase())
           return this.endTagState
           }
           if(c==='>') {
           this.token.push(c)
           this.emitToken('endTag', this.token.join(''))
           this.token=[]
           return this.start
           }
          }
          復(fù)制代碼
          

          最后只有屬性標(biāo)簽需要處理了,也就是上面看到的attrState函數(shù),也處理三種情形

          • 如果是字母、單引號(hào)、雙引號(hào)、等號(hào),則認(rèn)定為依舊處于屬性標(biāo)簽態(tài)
          • 如果遇到空格,則表示屬性標(biāo)簽態(tài)結(jié)束,接下來進(jìn)入新的屬性標(biāo)簽態(tài)
          • 如果遇到>,則認(rèn)定為屬性標(biāo)簽態(tài)結(jié)束,接下來開始新的節(jié)點(diǎn)信息

          代碼如下

          HTMLLexicalParser.prototype.attrState=function(c) {
           if(c.match(/[a-zA-Z'"=]/)) {
           this.token.push(c)
           return this.attrState
           }
           if(c===' ') {
           this.emitToken('attr', this.token.join(''))
           this.token=[]
           return this.attrState
           }
           if(c==='>') {
           this.emitToken('attr', this.token.join(''))
           this.token=[]
           return this.start
           }
          }
          復(fù)制代碼
          

          最后我們提供一個(gè)parse解析函數(shù),和可能用到的getOutPut函數(shù)來獲取結(jié)果即可,就不啰嗦了,上代碼

          HTMLLexicalParser.prototype.parse=function() {
           var state=this.start;
           for(var c of this.htmlString.split('')) {
           state=state.bind(this)(c)
           }
          }
          HTMLLexicalParser.prototype.getOutPut=function() {
           return this.tokens
          }
          復(fù)制代碼
          

          接下來簡(jiǎn)單測(cè)試一下,對(duì)于<p class="a" data="js">測(cè)試并列元素的</p><p class="a" data="js">測(cè)試并列元素的</p>HTML 字符串,輸出結(jié)果為

          看上去結(jié)果很 nice,接下來進(jìn)入語法分析步驟

          語法分析

          首先們需要考慮到的情況有兩種,一種是有多個(gè)根元素的,一種是只有一個(gè)根元素的。

          我們的節(jié)點(diǎn)有兩種類型,文本節(jié)點(diǎn)和正常節(jié)點(diǎn),因此聲明兩個(gè)數(shù)據(jù)結(jié)構(gòu)。

          function Element(tagName) {
           this.tagName=tagName
           this.attr={}
           this.childNodes=[]
          }
          function Text(value) {
           this.value=value || ''
          }
          復(fù)制代碼
          

          目標(biāo):將元素建立起父子關(guān)系,因?yàn)檎鎸?shí)的 DOM 結(jié)構(gòu)就是父子關(guān)系,這里我一開始實(shí)踐的時(shí)候,將 childNodes 屬性的處理放在了 startTag token 中,還給 Element 增加了 isEnd 屬性,實(shí)屬愚蠢,不但復(fù)雜化了,而且還很難實(shí)現(xiàn)。仔細(xì)思考 DOM 結(jié)構(gòu),token 也是有順序的,合理利用棧數(shù)據(jù)結(jié)構(gòu),這個(gè)問題就變的簡(jiǎn)單了,將 childNodes 處理放在 endTag 中處理。具體邏輯如下

          • 如果是 startTag token,直接 push 一個(gè)新 element
          • 如果是 endTag token,則表示當(dāng)前節(jié)點(diǎn)處理完成,此時(shí)出棧一個(gè)節(jié)點(diǎn),同時(shí)將該節(jié)點(diǎn)歸入棧頂元素節(jié)點(diǎn)的 childNodes 屬性,這里需要做個(gè)判斷,如果出棧之后棧空了,表示整個(gè)節(jié)點(diǎn)處理完成,考慮到可能有平行元素,將元素 push 到 stacks。
          • 如果是 attr token,直接寫入棧頂元素的 attr 屬性
          • 如果是 text token,由于文本節(jié)點(diǎn)的特殊性,不存在有子節(jié)點(diǎn)、屬性等,就認(rèn)定為處理完成。這里需要做個(gè)判斷,因?yàn)槲谋竟?jié)點(diǎn)可能是根級(jí)別的,判斷是否存在棧頂元素,如果存在直接壓入棧頂元素的 childNodes 屬性,不存在 push 到 stacks。

          代碼如下

          function HTMLSyntacticalParser() {
           this.stack=[]
           this.stacks=[]
          }
          HTMLSyntacticalParser.prototype.getOutPut=function() {
           return this.stacks
          }
          // 一開始搞復(fù)雜了,合理利用基本數(shù)據(jù)結(jié)構(gòu)真是一件很酷炫的事
          HTMLSyntacticalParser.prototype.receiveInput=function(token) {
           var stack=this.stack
           if(token.type==='startTag') {
           stack.push(new Element(token.value.substring(1)))
           } else if(token.type==='attr') {
           var t=token.value.split('='), key=t[0], value=t[1].replace(/'|"/g, '')
           stack[stack.length - 1].attr[key]=value
           } else if(token.type==='text') {
           if(stack.length) {
           stack[stack.length - 1].childNodes.push(new Text(token.value))
           } else {
           this.stacks.push(new Text(token.value))
           }
           } else if(token.type==='endTag') {
           var parsedTag=stack.pop()
           if(stack.length) {
           stack[stack.length - 1].childNodes.push(parsedTag)
           } else {
           this.stacks.push(parsedTag)
           }
           }
          }
          復(fù)制代碼
          

          簡(jiǎn)單測(cè)試如下:

          沒啥大問題哈

          解釋執(zhí)行

          對(duì)于上述語法分析的結(jié)果,可以理解成 vdom 結(jié)構(gòu)了,接下來就是映射成真實(shí)的 DOM,這里其實(shí)比較簡(jiǎn)單,用下遞歸即可,直接上代碼吧

          function vdomToDom(array) {
           var res=[]
           for(let item of array) {
           res.push(handleDom(item))
           }
           return res
          }
          function handleDom(item) {
           if(item instanceof Element) {
           var element=document.createElement(item.tagName)
           for(let key in item.attr) {
           element.setAttribute(key, item.attr[key])
           }
           if(item.childNodes.length) {
           for(let i=0; i < item.childNodes.length; i++) {
           element.appendChild(handleDom(item.childNodes[i]))
           }
           }
           return element
           } else if(item instanceof Text) {
           return document.createTextNode(item.value)
           }
          }
          復(fù)制代碼
          

          實(shí)現(xiàn)函數(shù)

          上面三步驟完成后,來到了最后一步,實(shí)現(xiàn)最開始提出的函數(shù)

          function html(element, htmlString) {
           // parseHTML
           var syntacticalParser=new HTMLSyntacticalParser()
           var lexicalParser=new HTMLLexicalParser(htmlString, syntacticalParser.receiveInput.bind(syntacticalParser))
           lexicalParser.parse()
           var dom=vdomToDom(syntacticalParser.getOutPut())
           var fragment=document.createDocumentFragment()
           dom.forEach(item=> {
           fragment.appendChild(item)
           })
           element.appendChild(fragment)
          }
          復(fù)制代碼
          

          三個(gè)不同情況的測(cè)試用例簡(jiǎn)單測(cè)試下

          html(document.getElementById('app'), '<p class="a" data="js">測(cè)試并列元素的</p><p class="a" data="js">測(cè)試并列元素的</p>')
          html(document.getElementById('app'), '測(cè)試<div>你好呀,我測(cè)試一下沒有深層元素的</div>')
          html(document.getElementById('app'), '<div class="div"><p class="p">測(cè)試一下嵌套很深的<span class="span">p的子元素</span></p><span>p同級(jí)別</span></div>')
          復(fù)制代碼
          

          聲明:簡(jiǎn)單測(cè)試下都沒啥問題,本次實(shí)踐的目的是對(duì) DOM 這一塊通過詞法分析和語法分析生成 DOM Tree 有一個(gè)基本的認(rèn)識(shí),所以細(xì)節(jié)問題肯定還是存在很多的。

          總結(jié)

          其實(shí)在了解了原理之后,這一塊代碼寫下來,并沒有太大的難度,但卻讓我很興奮,有兩個(gè)成果吧

          • 了解并初步實(shí)踐了一下狀態(tài)機(jī)
          • 數(shù)據(jù)結(jié)構(gòu)的魅力

          這篇文章中,作者將分享 12 個(gè)非常有用的 JavaScript 技巧,可以幫助你寫出簡(jiǎn)潔且高性能的代碼。

          1. 過濾唯一值

          ES6 引入了 Set 對(duì)象和延展(spread)語法…,我們可以用它們來創(chuàng)建一個(gè)只包含唯一值的數(shù)組。

          const array=[1, 1, 2, 3, 5, 5, 1]
          const uniqueArray=[...new Set(array)];
          console.log(uniqueArray); // Result: [1, 2, 3, 5]
          

          在 ES6 之前,獲得同樣的數(shù)組需要更多的代碼!

          這個(gè)技巧可以支持包含原始類型的數(shù)組:undefined、null、boolean、string 和 number。但如果你的數(shù)組包含了對(duì)象、函數(shù)或其他嵌套數(shù)組,就不能使用這種方法了。

          2. 在循環(huán)中緩存數(shù)組長(zhǎng)度

          在我們學(xué)習(xí)使用 for 循環(huán)時(shí),一般建議使用這種結(jié)構(gòu):

          for (let i=0; i < array.length; i++){
           console.log(i);
          }
          

          在使用這種方式時(shí),for 循環(huán)的每次迭代都會(huì)重復(fù)計(jì)算數(shù)組長(zhǎng)度。

          有時(shí)候這個(gè)會(huì)很有用,但在大多數(shù)情況下,如果能夠緩存數(shù)組的長(zhǎng)度會(huì)更好,這樣只需要計(jì)算一次就夠了。我們可以把數(shù)組長(zhǎng)度復(fù)制給一個(gè)叫作 length 的變量,例如:

          for (let i=0, length=array.length; i < length; i++){
           console.log(i);
          }
          

          這段代碼和上面的差不多,但從性能方面來看,即使數(shù)組變得很大,也不需要花費(fèi)額外的運(yùn)行時(shí)重復(fù)計(jì)算 array.length。

          3. 短路求值

          使用三元運(yùn)算符可以很快地寫出條件語句,例如:

          x > 100 ? 'Above 100' : 'Below 100';
          x > 100 ? (x > 200 ? 'Above 200' : 'Between 100-200') : 'Below 100';
          

          但有時(shí)候三元運(yùn)算符仍然很復(fù)雜,我們可以使用邏輯運(yùn)算符 && 和||來替代,讓代碼更簡(jiǎn)潔一些。這種技巧通常被稱為“短路求值”。

          假設(shè)我們想要返回兩個(gè)或多個(gè)選項(xiàng)中的一個(gè),使用 && 可以返回第一個(gè) false。如果所有操作數(shù)的值都是 true,將返回最后一個(gè)表達(dá)式的值。

          let one=1, two=2, three=3;
          console.log(one && two && three); // Result: 3
          console.log(0 && null); // Result: 0
          

          使用||可以返回第一個(gè) true。如果所有操作數(shù)的值都是 false,將返回最后一個(gè)表達(dá)式的值。

          let one=1, two=2, three=3;
          console.log(one || two || three); // Result: 1
          console.log(0 || null); // Result: null
          

          示例 1

          假設(shè)我們想要返回一個(gè)變量的 length,但又不知道變量的類型。

          我們可以使用 if/else 來檢查 foo 是否是一個(gè)可接受的類型,但這樣會(huì)讓代碼變得很長(zhǎng)。這個(gè)時(shí)候可以使用短路求值:

          return (foo || []).length;
          

          對(duì)于上述兩種情況,如果變量 foo 具有 length 屬性,這個(gè)屬性的值將被返回,否則將返回 0。

          示例 2

          你是否曾經(jīng)在訪問嵌套對(duì)象屬性時(shí)遇到過問題?你可能不知道對(duì)象或某個(gè)子屬性是否存在,所以經(jīng)常會(huì)碰到讓你頭疼的錯(cuò)誤。

          假設(shè)我們想要訪問 this.state 中的一個(gè)叫作 data 的屬性,但 data 卻是 undefined 的。在某些情況下調(diào)用 this.state.data 會(huì)導(dǎo)致 App 無法運(yùn)行。為了解決這個(gè)問題,我們可以使用條件語句:

          if (this.state.data) {
           return this.state.data;
          } else {
           return 'Fetching Data';
          }
          

          但這樣似乎有點(diǎn)啰嗦,而||提供了更簡(jiǎn)潔的解決方案:

          return (this.state.data || 'Fetching Data');
          

          4. 轉(zhuǎn)換成布爾值

          除了標(biāo)準(zhǔn)的布爾值 true 和 false,在 JavaScript 中,所有的值要么是“真值”要么是“假值”。

          在 JavaScript 中,除了 0、“”、null、undefined、NaN 和 false 是假值之外,其他的都是真值。

          我們可以使用! 云算法來切換 true 和 false。

          const isTrue=!0;
          const isFalse=!1;
          const alsoFalse=!!0;
          console.log(true); // Result: true
          console.log(typeof true); // Result: "boolean"
          

          5. 轉(zhuǎn)換成字符串

          要快速將數(shù)字轉(zhuǎn)換成字符串,我們可以使用 + 運(yùn)算符,然后在后面跟上一個(gè)空字符串。

          const val=1 + "";
          console.log(val); // Result: "1"
          console.log(typeof val); // Result: "string"
          

          6. 轉(zhuǎn)換成數(shù)字

          要把字符串轉(zhuǎn)成數(shù)字,也可以使用 + 運(yùn)算符。

          let int="15";
          int=+int;
          console.log(int); // Result: 15
          console.log(typeof int); Result: "number"
          

          也可以使用這種方式將布爾值轉(zhuǎn)成數(shù)字,例如:

          console.log(+true); // Return: 1
          console.log(+false); // Return: 0
          

          在某些情況下,+ 運(yùn)算符會(huì)被解析成連接操作,而不是加法操作。對(duì)于這種情況,可以使用兩個(gè)波浪號(hào):~~。

          一個(gè)波浪號(hào)表示按位取反操作,例如,~15 等于 -16。

          const int=~~"15"
          console.log(int); // Result: 15
          console.log(typeof int); Result: "number"
          

          使用兩個(gè)波浪號(hào)可以再次取反,因?yàn)?-(-n-1)=n+1-1=n,所以~-16 等于 15。

          7. 快速冪運(yùn)算

          從 ES7 開始,可以使用 ** 進(jìn)行冪運(yùn)算,比使用 Math.power(2,3) 要快得多。

          console.log(2 ** 3); // Result: 8
          

          但要注意不要把這個(gè)運(yùn)算符于 ^ 混淆在一起了,^ 通常用來表示指數(shù)運(yùn)算,但在 JavaScript 中,^ 表示位異或運(yùn)算。

          在 ES7 之前,可以使用位左移運(yùn)算符 << 來表示以 2 為底的冪運(yùn)算:

          // 以下表達(dá)式是等效的:
          Math.pow(2, n);
          2 << (n - 1);
          2**n;
          

          例如,2 << 3=16 等同于 2 ** 4=16。

          8. 快速取整

          我們可以使用 Math.floor()、Math.ceil() 或 Math.round() 將浮點(diǎn)數(shù)轉(zhuǎn)換成整數(shù),但有另一種更快的方式,即使用位或運(yùn)算符 |。

          console.log(23.9 | 0); // Result: 23
          console.log(-23.9 | 0); // Result: -23
          

          | 的實(shí)際行為取決于操作數(shù)是正數(shù)還是負(fù)數(shù),所以在使用這個(gè)運(yùn)算符時(shí)要確保你知道操作數(shù)是正是負(fù)。

          如果 n 是正數(shù),那么 n|0 向下取整,否則就是向上取整。它會(huì)移除小數(shù)部分,也可以使用~~ 達(dá)到同樣的效果。

          移除整數(shù)尾部數(shù)字

          | 運(yùn)算符也可以用來移除整數(shù)的尾部數(shù)字,這樣就不需要像下面這樣:

          let str="1553"; 
          Number(str.substring(0, str.length - 1));
          

          相反,我們可以這樣:

          console.log(1553 / 10 | 0) // Result: 155
          console.log(1553 / 100 | 0) // Result: 15
          console.log(1553 / 1000 | 0) // Result: 1
          

          9. 自動(dòng)類綁定

          在 ES6 中,我們可以使用箭頭進(jìn)行隱式綁定,這樣可以為類的構(gòu)造器省下一些代碼,并跟一些重復(fù)出現(xiàn)的表達(dá)式說再見,比如 this.myMethod=this.myMethod.bind(this)。

          import React, { Component } from React;
          export default class App extends Compononent {
           constructor(props) {
           super(props);
           this.state={};
           }
          myMethod=()=> {
           // This method is bound implicitly!
           }
          render() {
           return (
           <>
           <div>
           {this.myMethod()}
           </div>
           </>
           )
           }
          };
          

          10. 截取數(shù)組

          如果你想從一個(gè)數(shù)組尾部移除某些元素,可以使用一種比 splice() 更快的方法。

          例如,如果你知道初始數(shù)組的大小,可以像下面這樣重新定義它的 length 屬性:

          let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
          array.length=4;
          console.log(array); // Result: [0, 1, 2, 3]
          

          這顯然是一種更簡(jiǎn)潔的解決方案。不過,我發(fā)現(xiàn) slice() 的運(yùn)行速度更快,所以,如果你更看重速度,可以像下面這樣:

          let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
          array=array.slice(0, 4);
          console.log(array); // Result: [0, 1, 2, 3]
          

          11. 獲取數(shù)組最后的元素

          數(shù)組的 slice() 方法可以接受負(fù)整數(shù),并從數(shù)組的尾部開始獲取元素。

          let array=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
          console.log(array.slice(-1)); // Result: [9]
          console.log(array.slice(-2)); // Result: [8, 9]
          console.log(array.slice(-3)); // Result: [7, 8, 9]
          

          12. 格式化 JSON

          你之前可能使用過 JSON.stringify,但你是否知道它還可以用來給 JSON 添加縮進(jìn)?

          stringify() 方法可以接受兩個(gè)額外的參數(shù),一個(gè)是函數(shù)(形參為 replacer),用于過濾要顯示的 JSON,另一個(gè)是空格個(gè)數(shù)(形參為 space)。

          space 可以是一個(gè)整數(shù),表示空格的個(gè)數(shù),也可以是一個(gè)字符串(比如’\t’表示制表符),這樣得到的 JSON 更容易閱讀。

          console.log(JSON.stringify({ alpha: 'A', beta: 'B' }, null, '\t'));
          // Result:
          // '{
          // "alpha": A,
          // "beta": B
          // }'
          

          英文原文:https://medium.com/@bretcameron/12-javascript-tricks-you-wont-find-in-most-tutorials-a9c9331f169d


          主站蜘蛛池模板: 亚洲视频在线一区二区| 久久国产精品一区免费下载| 2022年亚洲午夜一区二区福利| 日本无卡码免费一区二区三区| 国产在线精品观看一区| 亚洲一区二区三区影院| 中日韩精品无码一区二区三区| 夜色阁亚洲一区二区三区| 色偷偷av一区二区三区| 一区二区三区四区在线观看视频 | 97一区二区三区四区久久| 色综合视频一区二区三区 | 欧美亚洲精品一区二区| 中文字幕一区二区三区在线播放 | 日韩人妻无码一区二区三区久久| 波多野结衣AV一区二区三区中文 | 中文字幕亚洲综合精品一区| 无码少妇一区二区浪潮av| 亚洲Av无码国产一区二区 | 国产精品 一区 在线| 天堂va视频一区二区| 精品乱码一区二区三区四区| 国产午夜精品一区二区三区不卡| 免费精品一区二区三区在线观看| 久久久久人妻精品一区二区三区| 成人精品视频一区二区三区| 欧洲精品一区二区三区在线观看 | 国产视频福利一区| 精品国产一区二区三区色欲| 骚片AV蜜桃精品一区| 亚洲国产一区二区三区青草影视 | 久久免费区一区二区三波多野| 国产另类ts人妖一区二区三区| 人成精品视频三区二区一区| 丰满岳妇乱一区二区三区| 一区二区乱子伦在线播放| 亚欧在线精品免费观看一区| 99精品国产一区二区三区2021| 国产精品一区二区综合| 中文字幕一区二区三区视频在线| 国产自产在线视频一区|