整合營銷服務商

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

          免費咨詢熱線:

          JavaScript中的forEach,你踩過哪些坑

          JavaScript中的forEach,你踩過哪些坑?請避開這些常見誤區

          JavaScript的世界里,forEach是我們常用的數組遍歷方法之一。大多數開發者都熟悉它的基礎用法,但你知道嗎?在處理異步操作時,forEach可能會讓你掉進一些意想不到的“坑”。這篇文章將帶你深入了解forEach的特性和局限,揭示一些你可能不知道的使用技巧和解決方案。無論你是前端新手,還是經驗豐富的開發者,都能從中學到有用的知識,幫助你在實際項目中避開那些隱藏的陷阱。準備好了嗎?讓我們一探究竟!

          先聊聊什么是forEach?

          forEach是數組對象的一個原型方法,它會為數組中的每個元素執行一次給定的回調函數,并且總是返回undefined。不過需要注意的是,類似arguments這樣的類數組對象是沒有forEach方法的哦。

          基本語法

          arr.forEach(callback(currentValue [, index [, array]])[, thisArg])

          別被這復雜的語法嚇到,我們來逐個拆解。

          參數詳解

          1. callback:對每個元素執行的回調函數,它可以接受1到3個參數。
          • currentValue:當前處理的元素,必選。
          • index:當前處理元素的索引,可選。
          • array:正在操作的原數組對象,可選。
          1. thisArg:執行回調函數時this的值,默認為全局對象,可選。

          1、forEach() 方法不支持處理異步函數

          在JavaScript中,forEach() 是一個同步方法,不支持處理異步函數。如果你在 forEach 中執行一個異步函數,forEach 不會等待異步函數完成,而是會立即處理下一個元素。這意味著如果你在 forEach 中使用異步函數,異步任務的執行順序是無法保證的。

          示例代碼

          async function test() {
              let arr=[3, 2, 1];
              arr.forEach(async item=> {
                  const res=await mockAsync(item);
                  console.log(res);
              });
              console.log('end');
          }
          
          function mockAsync(x) {
              return new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                      resolve(x);
                  }, 1000 * x);
              });
          }
          test();

          預期結果:

          3
          2
          1
          end

          實際結果:

          end
          1
          2
          3

          這個例子中,雖然我們希望按順序輸出 3, 2, 1end,但實際結果是 end 先輸出,然后才是 1, 2, 3。這是因為 forEach 不等待異步操作完成。

          解決方法:使用 for...of 循環和 async/await

          為了解決這個問題,我們可以使用 for...of 循環和 async/await 關鍵字來確保異步操作按順序完成。

          示例代碼

          async function test() {
              let arr=[3, 2, 1];
              for (let item of arr) {
                  const res=await mockAsync(item);
                  console.log(res);
              }
              console.log('end');
          }
          
          function mockAsync(x) {
              return new Promise((resolve, reject)=> {
                  setTimeout(()=> {
                      resolve(x);
                  }, 1000 * x);
              });
          }
          test();

          輸出結果:

          3
          2
          1
          end

          在這個例子中,我們使用 for...of 循環代替 forEach 方法,通過在循環內部使用 await 關鍵字,確保每個異步操作完成后才處理下一個元素,從而實現了按順序輸出。

          2、異步函數中的錯誤無法被捕獲

          除了不能處理異步函數外,forEach還有另一個重要的限制:它無法捕獲異步函數中的錯誤。這意味著即使異步函數在執行過程中拋出錯誤,forEach 仍然會繼續進行下一個元素的處理,而不會對錯誤進行處理。這種行為可能會導致程序出現意外的錯誤和不穩定性。

          3、無法中斷或跳過forEach循環

          除了無法處理異步函數和捕獲錯誤之外,forEach還有一個限制:它不支持使用break或continue語句來中斷或跳過循環。如果你需要在循環中途退出或跳過某個元素,應該使用其他支持這些語句的方法,例如for循環。

          示例代碼

          let arr=[1, 2, 3];
          try {
              arr.forEach(item=> {
                  if (item===2) {
                      throw('error');
                  }
                  console.log(item);
              });
          } catch(e) {
              console.log('e:', e);
          }
          
          // 輸出結果:
          // 1
          // e: error

          在這個例子中,我們嘗試通過拋出異常來中斷forEach循環。雖然這種方法在某些情況下有效,但并不是優雅或推薦的做法。

          更好的解決方案:使用 for...of 循環

          相比之下,for...of 循環更靈活,可以使用break和continue語句來控制循環的執行。

          示例代碼

          let arr=[1, 2, 3];
          for (let item of arr) {
              if (item===2) {
                  break; // 中斷循環
              }
              console.log(item);
          }
          
          // 輸出結果:
          // 1

          在這個例子中,當遇到元素2時,循環會被中斷,從而避免輸出2和3。

          4、無法刪除自身元素并重置索引

          在forEach中,我們無法控制索引的值,它只是盲目地遞增直到超過數組的長度并退出循環。因此,刪除自身元素以重置索引也是不可能的。來看一個簡單的例子:

          示例代碼

          let arr=[1, 2, 3, 4];
          arr.forEach((item, index)=> {
              console.log(item); // 輸出: 1 2 3 4
              index++;
          });

          在這個例子中,forEach遍歷數組 arr,輸出每個元素的值。雖然我們嘗試在循環內部遞增 index,但這并不會影響forEach的內部機制。forEach中的索引是自動管理的,并且在每次迭代時都會自動遞增。

          為什么無法刪除元素并重置索引?

          在forEach中,索引的值是由forEach方法內部控制的。即使我們手動修改索引變量,也不會影響forEach的遍歷行為。更具體地說,當我們試圖在forEach內部刪除元素時,forEach不會重新計算索引,這會導致一些元素被跳過,或者某些情況下出現未定義的行為。

          例如,如果我們嘗試刪除當前元素:

          錯誤示范

          let arr=[1, 2, 3, 4];
          arr.forEach((item, index)=> {
              if (item===2) {
                  arr.splice(index, 1); // 嘗試刪除元素2
              }
              console.log(item); // 輸出: 1 2 4
          });
          console.log(arr); // 輸出: [1, 3, 4]

          在這個例子中,當我們刪除元素2時,forEach并不會重置或調整索引,因此它繼續處理原數組中的下一個元素。這導致元素3被跳過,因為原來的元素3現在變成了元素2的位置。

          當元素 2 被刪除后,原數組變為 [1, 3, 4],forEach會繼續按照原索引順序進行,因此輸出 1, 2, 4,而元素 3 被跳過了。這是因為元素 3 在 2 被刪除后移動到了索引 1 的位置,而forEach的索引已經移動到 2,所以直接輸出了刪除后的索引 2 位置的新元素 4。

          更好的解決方案:使用for循環

          let arr=[1, 2, 3, 4];
          for (let i=0; i < arr.length; i++) {
              if (arr[i]===2) {
                  arr.splice(i, 1); // 刪除元素2
                  i--; // 調整索引
              } else {
                  console.log(arr[i]); // 輸出: 1 3 4
              }
          }
          console.log(arr); // 輸出: [1, 3, 4]

          5、this 關鍵字的作用域問題

          在forEach方法中,this關鍵字指的是調用該方法的對象。然而,當我們使用常規函數或箭頭函數作為參數時,this關鍵字的作用域可能會出現問題。在箭頭函數中,this關鍵字指的是定義該函數的對象;而在常規函數中,this關鍵字指的是調用該函數的對象。為了確保this關鍵字的正確作用域,我們可以使用bind方法來綁定函數的作用域。以下是一個說明this關鍵字作用域問題的例子:

          示例代碼

          const obj={
            name: "Alice",
            friends: ["Bob", "Charlie", "Dave"],
            printFriends: function () {
              this.friends.forEach(function (friend) {
                console.log(this.name + " is friends with " + friend);
              });
            },
          };
          obj.printFriends();

          在這個例子中,我們定義了一個名為obj的對象,里面有一個printFriends方法。我們使用forEach方法遍歷friends數組,并使用常規函數來打印每個朋友的名字和obj對象的name屬性。然而,運行這段代碼時,輸出如下:

          undefined is friends with Bob
          undefined is friends with Charlie
          undefined is friends with Dave

          這是因為在forEach方法中使用常規函數時,該函數的作用域不是調用printFriends方法的對象,而是全局作用域。因此,無法訪問obj對象的屬性。

          使用bind方法解決

          為了解決這個問題,我們可以使用bind方法來綁定函數的作用域,將其綁定到obj對象。下面是一個使用bind方法解決問題的例子:

          示例代碼

          const obj={
            name: "Alice",
            friends: ["Bob", "Charlie", "Dave"],
            printFriends: function () {
              this.friends.forEach(
                function (friend) {
                  console.log(this.name + " is friends with " + friend);
                }.bind(this) // 使用bind方法綁定函數的作用域
              );
            },
          };
          obj.printFriends();

          運行這段代碼,輸出如下:

          Alice is friends with Bob
          Alice is friends with Charlie
          Alice is friends with Dave

          通過使用bind方法綁定函數的作用域,我們可以正確地訪問obj對象的屬性。

          使用箭頭函數解決

          另一個解決方案是使用箭頭函數。由于箭頭函數沒有自己的this,它會繼承其當前作用域的this。因此,在箭頭函數中,this關鍵字指的是定義該函數的對象。

          示例代碼

          const obj={
            name: "Alice",
            friends: ["Bob", "Charlie", "Dave"],
            printFriends: function () {
              this.friends.forEach((friend)=> {
                console.log(this.name + " is friends with " + friend);
              });
            },
          };
          obj.printFriends();

          運行這段代碼,輸出如下:

          Alice is friends with Bob
          Alice is friends with Charlie
          Alice is friends with Dave

          使用箭頭函數,我們可以確保this關鍵字指向正確的對象,從而正確訪問對象的屬性。

          6、forEach 的性能低于 for 循環

          forEach 方法雖然使用方便,但在性能方面卻遜色于傳統的 for 循環。原因在于 forEach 的函數簽名包含參數和上下文,使得其性能低于 for 循環。

          為什么 for 循環更快?

          1. 簡單實現:for 循環的實現最為簡單,沒有額外的函數調用和上下文處理。
          2. 減少函數調用棧:forEach 方法每次迭代都會調用一次回調函數,增加了函數調用棧的開銷。
          3. 上下文處理:forEach 方法需要處理函數的上下文和參數,這些操作都會消耗額外的時間和資源。

          7、跳過已刪除或未初始化的項

          forEach方法在遍歷數組時會跳過未初始化的值和已刪除的值。這可能會導致一些意想不到的行為。

          跳過未初始化的值

          在數組中,如果某些值未初始化,forEach會直接跳過這些值。來看下面這個例子:

          const array=[1, 2, /* 空 */, 4];
          let num=0;
          
          array.forEach((ele)=> {
            console.log(ele);
            num++;
          });
          
          console.log("num:", num);
          
          // 輸出結果:
          // 1
          // 2
          // 4
          // num: 3

          在這個例子中,數組中的第三個元素未初始化,forEach直接跳過了它。因此,雖然數組的長度是4,但實際被遍歷的元素只有3個。

          跳過已刪除的值

          當在forEach循環中刪除數組元素時,forEach會跳過這些已刪除的值。來看下面這個例子:

          const words=['one', 'two', 'three', 'four'];
          words.forEach((word)=> {
            console.log(word);
            if (word==='two') {
              words.shift(); // 刪除數組中的第一個元素 'one'
            }
          });
          
          // 輸出結果:
          // one
          // two
          // four
          
          console.log(words); // ['two', 'three', 'four']

          在這個例子中,當遍歷到元素 'two' 時,執行了 words.shift(),刪除了數組中的第一個元素 'one'。由于數組元素向前移動,元素 'three' 被跳過,forEach 直接處理新的第三個元素 'four'。

          8、不會改變原數組

          當調用forEach方法時,它不會改變原數組,即它被調用的數組。然而,傳遞的回調函數可能會改變數組中的對象。

          示例代碼1

          const array=[1, 2, 3, 4]; 
          array.forEach(ele=> { ele=ele * 3 }) 
          console.log(array); // [1, 2, 3, 4]

          在這個例子中,forEach方法并沒有改變原數組。雖然在回調函數中對每個元素進行了乘3的操作,但這些操作并沒有反映在原數組中。

          如果希望通過forEach改變原數組,需要直接修改數組元素的值,而不是簡單地對元素進行賦值。

          示例代碼

          const numArr=[33, 4, 55];
          numArr.forEach((ele, index, arr)=> {
              if (ele===33) {
                  arr[index]=999;
              }
          });
          console.log(numArr);  // [999, 4, 55]

          在這個例子中,我們通過forEach方法直接修改了數組中的元素,從而改變了原數組。

          示例代碼2

          const changeItemArr=[{
              name: 'wxw',
              age: 22
          }, {
              name: 'wxw2',
              age: 33
          }];
          changeItemArr.forEach(ele=> {
              if (ele.name==='wxw2') {
                  ele={
                      name: 'change',
                      age: 77
                  };
              }
          });
          console.log(changeItemArr); // [{name: "wxw", age: 22}, {name: "wxw2", age: 33}]

          在這個例子中,嘗試對數組中的對象進行替換操作,但這種方式并不會改變原數組中的對象。

          解決方案:通過索引改變數組中的對象

          為了正確替換數組中的對象,可以通過索引來直接修改數組中的對象。

          示例代碼

          const allChangeArr=[{
              name: 'wxw',
              age: 22
          }, {
              name: 'wxw2',
              age: 33
          }];
          allChangeArr.forEach((ele, index, arr)=> {
              if (ele.name==='wxw2') {
                  arr[index]={
                      name: 'change',
                      age: 77
                  };
              }
          });
          console.log(allChangeArr); // [{name: "wxw", age: 22}, {name: "change", age: 77}]

          在這個例子中,通過索引直接修改數組中的對象,從而實現了對原數組的修改。

          結束

          總結一下,forEach雖然方便,但在一些特定場景下,使用傳統的for循環或其他遍歷方法可能更適合你的需求。比如,當你需要精確控制循環流程、處理異步操作或是修改原數組時,for循環往往能提供更高的靈活性和性能。

          使用for循環的時候可以使用break 或者return語句來結束for循環(return直接結束函數),但是如果使用forEach循環如何跳出循環呢?

          聽說視頻配文檔更容易理解,??

          1. 嘗試使用break 和return
          2. MDN給出的官方解釋
          3. 探究為什么break和return不行
          4. 如何變通跳出forEach循環
          5. MDN官方推薦的方法
          6. 其他方法

          嘗試使用break 和return

          首先嘗試一使用return語句----木有效果

          [1,2,3,4,5].forEach(item=>{
           if(item===2){
           return
           }
           console.log(item);
          })
          

          在嘗試一下使用break語句----報錯

          [1,2,3,4,5].forEach(item=>{
           if(item===2){
           break
           }
           console.log(item);
          })
          

          MDN給出的官方解釋

          為什么會出現這樣的情況?先看一下官方文檔的說明。MDN文檔上明確說明forEach循環是不可以退出的。

          引自MDN

          There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool.

          注意: 沒有辦法中止或者跳出 forEach() 循環,除了拋出一個異常。如果你需要這樣,使用 forEach() 方法是錯誤的。

          若你需要提前終止循環,你可以使用:

          簡單循環

          for...of 循環

          Array.prototype.every()

          Array.prototype.some()

          Array.prototype.find()

          Array.prototype.findIndex()


          探究為什么break和return不行

          先看看為什么return沒有效果,break報錯,forEach的實現方式用代碼表示出來可以寫成如下的結構

          const arr=[1, 2, 3, 4, 5];
          for (let i=0; i < arr.length; i++) {
           const rs=(function(item) {
           console.log(item);
           if (item > 2) return false;
           })(arr[i])
          }
          

          使用return語句相當于在每個自執行函數中將返回值復制給rs,但是實際對整個函數并沒有影響。而使用break語句報錯是因為再JS的解釋器中break語句是不可以出現在函數體內的。


          如何變通跳出forEach循環

          MDN官方推薦的方法

          every在碰到return false的時候,中止循環。some在碰到return ture的時候,中止循環。
          var a=[1, 2, 3, 4, 5]
          a.every(item=>{
           console.log(item); //輸出:1,2
           if (item===2) {
           return false
           } else {
           return true
           }
          })
          var a=[1, 2, 3, 4, 5]
          a.some(item=> {
           console.log(item); //輸出:1,2
           if (item===2) {
           return true
           } else {
           return false
           }
          })
          

          其他方法

          1.使用for循環或者for in 循環代替

          2.使用throw拋出異常

          try {
           [1, 2, 3, 4, 5].forEach(function(item) {
           if (item===2) throw item;
           console.log(item);
           });
          } catch (e) {}
          

          3.使用判斷跑空循環

          var tag;
          [1, 2, 3, 4, 5].forEach(function(item){
           if(!tag){
           console.log(item);
           if(item===2){
           tag=true;
           }
           }
          })
          

          這樣做有兩個問題,第一個問題,全局增加了一個tag變量,第二個問題,表面上看是終止了forEach循環,但是實際上循環的次數并沒有改變,只是在不滿足條件的時候callback什么都沒執行而已,先來解決第一個問題,如何刪除全局下新增的tag變量 。實際上forEach還有第二個參數,表示callback的執行上下文,也就是在callback里面this對應的值。因此我們可以講上下文設置成空對象,這個對象自然沒有tag屬性,因此訪問this.tag的時候會得到undefined

          [1, 2, 3, 4, 5].forEach(function(item){
           if(!this.tag){
           console.log(item);
           if(item===2){
           this.tag=true;
           }
           }
          },{})
          

          4.修改索引

          var array=[1, 2, 3, 4, 5]
          array.forEach(item=>{
           if (item==2) {
           array=array.splice(0);
           }
           console.log(item);
          })
          

          講解:

          forEach的執行細節

          1.遍歷的范圍在第一次執行callback的時候就已經確定,所以在執行過程中去push內容,并不會影響遍歷的次數,這和for循環有很大區別,下面的兩個案例一個會造成死循環一個不會

          var arr=[1,2,3,4,5]
          //會造成死循環的代碼
          for(var i=0;i<arr.length;i++){
           arr.push('a')
          }
          //不會造成死循環
          arr.forEach(item=>arr.push('a'))
          

          2.如果已經存在的值被改變,則傳遞給 callback 的值是 forEach 遍歷到他們那一刻的值。

          var arr=[1,2,3,4,5];
          arr.forEach((item,index)=>{
           console.log(`time ${index}`)
           arr[index+1]=`${index}a`;
           console.log(item)
          })
          

          3.已刪除的項不會被遍歷到。如果已訪問的元素在迭代時被刪除了(例如使用 shift()),之后的元素將被跳過。

          var arr=[1,2,3,4,5];
          arr.forEach((item,index)=>{
           console.log(item)
           if(item===2){
           arr.length=index;
           }
          })
          

          在滿足條件的時候將后面的值截掉,下次循環的時候照不到對應的值,循環就結束了,但是這樣操作會破壞原始的數據,因此我們可以使用一個小技巧,即將數組從0開始截斷,然后重新賦值給數組也就是array=array.splice(0)


          本期教程資料請點擊更多下載,提取碼: 558x

          良心教程,歡迎關注評論,或者訪問我們的官網http://www.bingshangroup.com

          和我們交流。

          當時就直接回他:“不能,我做不到。”

          結果呢,這句話就像按了快進鍵,面試官突然宣布面試結束。

          心里那個郁悶啊,我就問面試官:“這有啥不對嗎?難道真的有辦法在JavaScript中讓forEach歇菜嗎?”

          還沒等他回我,我就開始自我解惑,說出了我認為forEach不能停的理由。

          我這樣回答對嗎?

          我的小伙伴們,猜猜看,下面這段代碼會打印出什么數字?

          會只打印一個數字,還是一串數字?

          正確答案是,它會打印出‘0’、‘1’、‘2’、‘3’。

          const array=[ -3, -2, -1, 0, 1, 2, 3 ]
          
          array.forEach((it)=> {
            if (it >=0) {
              console.log(it)
              return // or break
            }
          })


          是的!我把這代碼拿給面試官看,但他還是堅持認為JavaScript的forEach循環是可以停的。

          哦天啊,開什么國際玩笑呢。

          為啥這樣?

          要讓他信服,我就得再來一次forEach的模擬。

          Array.prototype.forEach2=function (callback, thisCtx) {
            if (typeof callback !=='function') {
              throw `${callback} is not a function`
            }
          
            const length=this.length
          
            let i=0
            while (i < length) {
              if (this.hasOwnProperty(i)) {
                // Note here:Each callback function will be executed once
                callback.call(thisCtx, this[ i ], i, this)
              }
              i++
            }
          }

          確實,當我們用forEach遍歷數組時,每個元素都要跑一遍回調函數,早退門都沒有。

          比如說,下面這段代碼里,就算func1遇到了break,控制臺還是會打印出‘2’。

          const func1=()=> {
            console.log(1)
            return
          }
          
          const func2=()=> {
            func1()
            console.log(2)
          }
          func2()
          


          停forEach的三種方法

          你很棒,但我得告訴你,至少有三種方法可以讓JavaScript里的forEach停止。

          1.拋個錯誤出來

          找到第一個大于或等于0的數字后,代碼就進入死胡同了。所以控制臺只會跟你說個0。

          const array=[ -3, -2, -1, 0, 1, 2, 3 ]
          
          try {
            array.forEach((it)=> {
              if (it >=0) {
                console.log(it)
                throw Error(`We've found the target element.`)
              }
            })
          } catch (err) {
            
          }


          哦!我的個神啊!我簡直不敢相信,都快說不出話來。

          2.把數組長度改成0

          別這么驚訝,面試官跟我說。

          咱們還可以通過把數組長度設置成0來讓forEach打卡下班。你也知道,數組沒了,forEach自然也就不跑了。

          const array=[ -3, -2, -1, 0, 1, 2, 3 ]
          
          array.forEach((it)=> {
            if (it >=0) {
              console.log(it)
              array.length=0
            }
          })
          

          哦!天哪,我的腦子都亂套了。

          3.用splice法,砍掉數組的元素

          這招和第二招一個味兒,如果能把目標元素后面的值都給刪了,forEach也就自動停工了。

          const array=[ -3, -2, -1, 0, 1, 2, 3 ]
          
          array.forEach((it, i)=> {
            if (it >=0) {
              console.log(it)
              // Notice the sinful line of code
              array.splice(i + 1, array.length - i)
            }
          })



          我瞪大了眼睛,真不想看這代碼。太傷眼了。

          最后我跟面試官說:“哎,可能你說的對,你確實找到了停forEach的方法,但要是用這種代碼,我覺得你們老板遲早得讓你走人。”

          或許咱們應該考慮用for循環或者some方法來解決問題。

          1. for

          const array=[ -3, -2, -1, 0, 1, 2, 3 ]
          
          for (let i=0, len=array.length; i < len; i++) {
            if (array[ i ] >=0) {
              console.log(array[ i ])
              break
            }
          }


          2. some

          const array=[ -3, -2, -1, 0, 1, 2, 3 ]
          
          array.some((it, i)=> {
            if (it >=0) {
              console.log(it)
              return true
            }
          })


          感謝您的閱讀!如果您對本文有任何疑問或想要分享您的看法,請隨時通過私信或在下方評論區與我交流。


          主站蜘蛛池模板: 3D动漫精品一区二区三区| 亚洲国产av一区二区三区| 一区二区在线免费视频| 国产精品乱码一区二区三| 99精品高清视频一区二区| 无码人妻精品一区二区三| 在线精品国产一区二区三区| 国产精品无码一区二区在线观一 | 无码国产精品久久一区免费| 国产电影一区二区| 在线精品国产一区二区三区| 无码国产精品一区二区免费式芒果| 无码人妻啪啪一区二区| 手机看片一区二区| 国产成人一区二区三区免费视频| 国内精品无码一区二区三区| 人妻av综合天堂一区| 亚洲色婷婷一区二区三区| 精品国产日韩亚洲一区91| 久久精品一区二区国产| 色狠狠色狠狠综合一区| 国产在线精品一区二区不卡麻豆| 亚洲欧美日韩一区二区三区| 中文字幕一区二区人妻性色| 成人精品视频一区二区三区尤物| 亚洲乱码一区二区三区在线观看| 日本韩国一区二区三区| 久久精品午夜一区二区福利| 亚洲片一区二区三区| 无码少妇一区二区浪潮免费| 日本一区二区不卡视频| 一区二区三区电影网| 韩国精品一区二区三区无码视频| 亚洲国产一区在线| 国产福利电影一区二区三区久久久久成人精品综合 | 国产精品区一区二区三在线播放 | 欧洲精品免费一区二区三区| 亚洲av无一区二区三区| 日韩AV无码久久一区二区| 精品久久一区二区三区| 岛国无码av不卡一区二区 |