<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          理解閉包與內(nèi)存泄漏

          共 4280字,需瀏覽 9分鐘

           ·

          2021-02-06 12:21

          作者:JS Even JS
          來(lái)源:SegmentFault 思否社區(qū)




          一、閉包的定義


          閉包,是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中變量的函數(shù)。從定義上我們可以知道,閉包是函數(shù),并且是被另一個(gè)函數(shù)包裹的函數(shù)。所以需要用一個(gè)函數(shù)去包裹另一個(gè)函數(shù),即在函數(shù)內(nèi)部定義函數(shù)被包裹的函數(shù)則稱為包函數(shù),包裹的函數(shù)(外部的函數(shù))則為閉包函數(shù)提供了一個(gè)閉包作用域,所以形成的閉包作用域的名稱為外部函數(shù)的名稱。


          我們先來(lái)看一個(gè)常見(jiàn)的閉包例子,如:


          let?foo;
          function?outer()?{?//?outer函數(shù)內(nèi)部為閉包函數(shù)提供一個(gè)閉包作用域(outer)
          ????let?bar?=?"bar";
          ????let?inner?=?function()?{
          ????????console.log(bar);
          ????????debugger;?//?打一個(gè)debuuger斷點(diǎn),以便查看閉包作用域
          ????????console.log("inner?function?run.");
          ????}
          ????return?inner;
          }
          foo?=?outer();?//?執(zhí)行外部函數(shù)返回內(nèi)部函數(shù)
          foo();?//?執(zhí)行內(nèi)部函數(shù)


          我們?cè)跒g覽器上執(zhí)行該段代碼后,會(huì)停在斷點(diǎn)位置,此時(shí)我們可以看到形成的閉包作用域如圖所示



          從圖中我們可以看到,形成的閉包作用域名稱為外部的outer函數(shù)提供的作用域,閉包作用域內(nèi)有一個(gè)變量bar可以被閉包函數(shù)訪問(wèn)到。




          二、形成閉包的條件


          從上面的閉包例子在,看起來(lái)形成的閉包的條件就是,一個(gè)函數(shù)被另一個(gè)函數(shù)包裹,并且返回這個(gè)被包裹的函數(shù)供外部持有。其實(shí),閉包函數(shù)是否被外部變量持有并不重要,形成閉包的必要條件就是,閉包函數(shù)(被包裹的函數(shù))中必須要使用到外部函數(shù)中的變量。


          function?outer()?{?//?outer函數(shù)內(nèi)部為閉包函數(shù)提供一個(gè)閉包作用域(outer)
          ????let?bar?=?"bar";
          ????let?inner?=?function()?{
          ????????console.log(bar);
          ????????debugger;
          ????????console.log("inner?function?run.");
          ????}
          ????inner();?//?直接在外部函數(shù)中執(zhí)行閉包函數(shù)inner
          }
          outer();


          我們稍微修改一下上面的例子,外部函數(shù)outer不將內(nèi)部函數(shù)inner返回,而是直接在outer內(nèi)執(zhí)行。



          從執(zhí)行結(jié)果可以看到,仍然形成了閉包,所以說(shuō)這個(gè)被包裹的閉包函數(shù)是否被外部持有并不是形成閉包的條件。


          function?outer()?{?//?outer函數(shù)內(nèi)部為閉包函數(shù)提供一個(gè)閉包作用域(outer)
          ????let?bar?=?"bar";
          ????let?inner?=?function()?{
          ????????//?console.log(bar);?//?注釋該行,內(nèi)部inner函數(shù)不再使用外部outer函數(shù)中的變量
          ????????debugger;
          ????????console.log("inner?function?run.");
          ????}
          ????inner();?//?直接在外部函數(shù)中執(zhí)行閉包函數(shù)inner
          }
          outer();


          我們?cè)傩薷囊幌律厦娴睦?,將console.log(bar)這行代碼注釋掉,這樣inner函數(shù)中將不再使用外部outer函數(shù)中的變量。



          從執(zhí)行結(jié)果上可以看到,沒(méi)有形成閉包。所以形成閉包的必要條件就是,被包裹的閉包函數(shù)必須使用外部函數(shù)中的變量。


          當(dāng)然上面的結(jié)論也太過(guò)絕對(duì)了些,因?yàn)?strong>外部函數(shù)可以同時(shí)包裹多個(gè)閉包函數(shù),也就是說(shuō),(外部)函數(shù)內(nèi)部定義了多個(gè)函數(shù),這種情況下,就不需要每個(gè)閉包函數(shù)都使用到外部函數(shù)中的變量,因?yàn)?strong>閉包作用域是內(nèi)部所有閉包函數(shù)共享的,只要有一個(gè)內(nèi)部函數(shù)使用到了外部函數(shù)中的變量即可形成閉包。


          function?outer()?{?//?outer函數(shù)內(nèi)部為閉包函數(shù)提供一個(gè)閉包作用域(outer)
          ????let?bar?=?"bar";
          ????let?unused?=?function()?{
          ????????console.log(bar);?//?再創(chuàng)建一個(gè)閉包函數(shù),并在其中使用外部函數(shù)中的變量
          ????}
          ????let?inner?=?function()?{
          ????????//?console.log(bar);?//?注釋該行,內(nèi)部inner函數(shù)不再使用外部outer函數(shù)中的變量
          ????????debugger;
          ????????console.log("inner?function?run.");
          ????}
          ????inner();?//?直接在外部函數(shù)中執(zhí)行閉包函數(shù)inner
          }
          outer();


          我們繼續(xù)修改一下上面的例子,在outer函數(shù)內(nèi)部再創(chuàng)建一個(gè)unused函數(shù),這個(gè)函數(shù)只是定義但不會(huì)執(zhí)行,同時(shí)unused函數(shù)內(nèi)部使用了外部outer函數(shù)中的變量,inner函數(shù)仍然不使用外部outer函數(shù)中的變量。



          從執(zhí)行結(jié)果可以看到,又形成了閉包。所以形成的閉包條件就是,存在內(nèi)部函數(shù)中使用外部函數(shù)中定義的變量。




          三、內(nèi)存泄漏


          內(nèi)存泄漏常常與閉包緊緊聯(lián)系在一起,很容易讓人誤以為閉包就會(huì)導(dǎo)致內(nèi)存泄漏。其實(shí)閉包只是讓內(nèi)存常駐,而濫用閉包才會(huì)導(dǎo)致內(nèi)存泄漏


          內(nèi)存泄漏,從廣義上說(shuō)就是,內(nèi)存在使用完畢之后,對(duì)于不再要的內(nèi)存沒(méi)有及時(shí)釋放或者無(wú)法釋放。不再需要的內(nèi)存使用完畢之后肯定需要釋放掉,否則這個(gè)塊內(nèi)存就浪費(fèi)掉了,相當(dāng)于內(nèi)存泄漏了。但是在實(shí)際中,往往不會(huì)通過(guò)判斷該內(nèi)存或變量是否不再需要使用來(lái)判斷。因?yàn)閮?nèi)存測(cè)試工具很難判斷該內(nèi)存是否不再需要。所以我們通常會(huì)重復(fù)多次執(zhí)行某段邏輯鏈路,然后每隔一段時(shí)間進(jìn)行一次內(nèi)存dump,然后判斷內(nèi)存是否存在不斷增長(zhǎng)的趨勢(shì),如果存在,則可用懷疑存在內(nèi)存泄漏的可能。




          四、內(nèi)存dump


          瀏覽器中抓取內(nèi)存的dump相對(duì)來(lái)說(shuō)簡(jiǎn)單些,直接通過(guò)谷歌瀏覽器的調(diào)試工具找到memory對(duì)應(yīng)的tab頁(yè)面,然后點(diǎn)擊Load即可開(kāi)始抓取內(nèi)存dump,如:



          在NodeJS中,我們也可以通過(guò)引入heapdump來(lái)抓取內(nèi)存dump,直接通過(guò)npm安裝heapdump模塊即可


          >?npm?install?heapdump


          安裝完成之后,即可直接在應(yīng)用程序中使用了,用法非常簡(jiǎn)單,如:


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump

          //?應(yīng)用code部分

          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump


          應(yīng)用程序執(zhí)行完成后,會(huì)在應(yīng)用根目錄中生成start.heapsnapshot和end.heapsnapshot兩個(gè)內(nèi)存dump文件,我們可以通過(guò)判斷兩個(gè)文件的大小變化來(lái)判斷是否存在內(nèi)存泄漏。


          當(dāng)然并不是說(shuō)內(nèi)存dump文件的大小不斷增大就存在內(nèi)存泄漏,如果應(yīng)用的訪問(wèn)量確實(shí)在一直增大,那么內(nèi)存曲線只增不減也屬于正常情況,我們只能根據(jù)具體情況判斷是否存在內(nèi)存泄漏的可能。




          五、常見(jiàn)的內(nèi)存泄漏


          ① 閉包循環(huán)引用


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump
          let?foo?=?null;
          function?outer()?{
          ????let?bar?=?foo;
          ????function?unused()?{?//?未使用到的函數(shù)
          ????????console.log(`bar?is?${bar}`);
          ????}

          ????foo?=?{?//?給foo變量重新賦值
          ????????bigData:?new?Array(100000).join("this_is_a_big_data"),?//?如果這個(gè)對(duì)象攜帶的數(shù)據(jù)非常大,將會(huì)造成非常大的內(nèi)存泄漏
          ????????inner:?function()?{
          ????????????console.log(`inner?method?run`);
          ????????}
          ????}
          }
          for(let?i?=?0;?i?????outer();
          }
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump


          在這個(gè)例子中,執(zhí)行了1000次outer函數(shù),start.heapsnapshot文件的大小為2.4M,而end.heapsnapshot文件的大小為4.1M,所以可能存在內(nèi)存泄漏。


          前面講解閉包的過(guò)程中,我們已經(jīng)可以知道outer函數(shù)內(nèi)部是存在閉包的,因?yàn)閛uter函數(shù)內(nèi)部定義了unused和inner兩個(gè)函數(shù),雖然inner函數(shù)中沒(méi)有使用到outer函數(shù)中的變量,但是unused函數(shù)內(nèi)部使用到了outer函數(shù)中的bar變量,故形成閉包,inner函數(shù)也會(huì)共享outer函數(shù)提供的閉包作用域。


          由于閉包的存在,bar變量不能釋放,即相當(dāng)于inner函數(shù)隱式持有了bar變量,所以存在...-->foo-->inner-->bar-->foo(賦值給bar的foo,即上一次的foo)...。


          這里inner隱式持有bar變量怎么理解呢?因?yàn)閕nner是一個(gè)閉包函數(shù),可以使用outer提供的閉包作用域中的bar變量,由于閉包的關(guān)系,bar變量不能釋放,所以bar變量一直在內(nèi)存中,而bar變量又指向了上一次賦值給bar的foo對(duì)象,所以會(huì)存在這樣一個(gè)引用關(guān)系。


          那怎么解決呢?由于bar變量常駐內(nèi)存不能釋放,所以我們可以在outer函數(shù)執(zhí)行完畢的時(shí)候手動(dòng)釋放,即將bar變量置為null,這樣之前賦值給bar的foo對(duì)象就沒(méi)有被其他變量引用了,就會(huì)被回收了。


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump
          let?foo?=?null;
          function?outer()?{
          ????let?bar?=?foo;
          ????function?unused()?{?//?未使用到的函數(shù)
          ????????console.log(`bar?is?${bar}`);
          ????}

          ????foo?=?{?//?給foo變量重新賦值
          ????????bigData:?new?Array(100000).join("this_is_a_big_data"),?//?如果這個(gè)對(duì)象攜帶的數(shù)據(jù)非常大,將會(huì)造成非常大的內(nèi)存泄漏
          ????????inner:?function()?{
          ????????????console.log(`inner?method?run`);
          ????????}
          ????}
          ????bar?=?null;?//?手動(dòng)釋放bar變量,解除bar變量對(duì)上一次foo對(duì)象的引用
          }
          for(let?i?=?0;?i?????outer();
          }
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump


          手動(dòng)釋放bar變量是一種相對(duì)比較好的解決方式。關(guān)鍵在于要解除閉包解除bar變量對(duì)上一次foo變量的引用。所以我們可以讓unused方法內(nèi)不使用bar變量,或者將bar變量的定義放在一個(gè)塊級(jí)作用域中,如:


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump
          let?foo?=?null;
          function?outer()?{
          ????{?//?將bar變量定義在一個(gè)塊級(jí)作用域內(nèi),這樣outer函數(shù)中就沒(méi)有定義變量了,自然inner也不會(huì)形成閉包
          ????????let?bar?=?foo;
          ????????function?unused()?{?//?未使用到的函數(shù)
          ????????????console.log(`bar?is?${bar}`);
          ????????}
          ????}

          ????foo?=?{?//?給foo變量重新賦值
          ????????bigData:?new?Array(100000).join("this_is_a_big_data"),?//?如果這個(gè)對(duì)象攜帶的數(shù)據(jù)非常大,將會(huì)造成非常大的內(nèi)存泄漏
          ????????inner:?function()?{
          ????????????console.log(`inner?method?run`);
          ????????}
          ????}
          }
          for(let?i?=?0;?i?????outer();
          }
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump


          ② 重復(fù)注冊(cè)事件,比如頁(yè)面一進(jìn)入就重復(fù)注冊(cè)1000個(gè)同名事件(一次模擬每次進(jìn)入頁(yè)面都注冊(cè)一次事件)


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump
          const?events?=?require('events');
          class?Page?extends?events.EventEmitter?{
          ????onShow()?{
          ????????for?(let?i?=?0;?i?????????????this.on("ok",?()?=>?{
          ????????????????console.log("on?ok?signal.");
          ????????????});
          ????????}
          ????}
          ????onDestory()?{
          ????????
          ????}
          }
          let?page?=?new?Page();
          page.setMaxListeners(0);?//?設(shè)置可以注冊(cè)多個(gè)同名事件
          page.onShow();
          page.onDestory();
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump


          這個(gè)例子中Page頁(yè)面一進(jìn)入就會(huì)同時(shí)注冊(cè)1000個(gè)同名的ok事件,start.heapsnapshot文件的大小為2.4M,而end.heapsnapshot文件的大小為2.5M,所以可能存在內(nèi)存泄漏。


          解決方式就是,在頁(yè)面離開(kāi)的時(shí)候移除所有事件,或者在頁(yè)面創(chuàng)建的時(shí)候僅注冊(cè)一次事件,如:


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump
          const?events?=?require('events');
          class?Page?extends?events.EventEmitter?{
          ????onCreate()?{
          ????????this.on("ok",?()?=>?{?//?僅在頁(yè)面創(chuàng)建的時(shí)候注冊(cè)一次事件,避免重復(fù)注冊(cè)事件
          ????????????console.log("on?ok?signal.");
          ????????});
          ????}
          ????onShow()?{
          ????????//?for?(let?i?=?0;?i?????????//?????this.on("ok",?()?=>?{
          ????????//?????????console.log("on?ok?signal.");
          ????????//?????});
          ????????//?}
          ????}
          ????onLeave()?{
          ????????this.removeAllListeners("ok");?//?或者在離開(kāi)頁(yè)面的時(shí)候移除所有ok事件
          ????}
          }
          let?page?=?new?Page();
          page.setMaxListeners(0);?//?設(shè)置可以注冊(cè)多個(gè)同名事件
          page.onCreate();
          page.onShow();
          page.onLeave();
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump



          ③ 意外的全局變量,這是我們常常簡(jiǎn)單的內(nèi)存泄漏例子,實(shí)際上內(nèi)存工具很難判斷意外的全局變量是否存在內(nèi)存泄漏,除非應(yīng)用程序不斷的往這個(gè)全局變量中加入數(shù)據(jù),否則對(duì)于一個(gè)恒定不變的意外全局變量?jī)?nèi)存測(cè)試工具是無(wú)法判斷出是否存在內(nèi)存泄漏的,所以我們盡量不要隨意使用全局變量來(lái)保存數(shù)據(jù)。


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump

          function?createBigData()?{
          ????const?bigData?=?[];
          ????for(let?j?=?0;?j?????????bigData.push(new?Array(10000).join("this_is_a_big_data"));
          ????}
          ????return?bigData;
          }

          function?fn()?{
          ????foo?=?createBigData();?//?意外的全局變量導(dǎo)致內(nèi)存泄漏
          }
          for?(let?j?=?0;?j?????fn();
          }
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump


          該例子執(zhí)行后,end.heapsnapshot文件的大小為2.5M也變成了2.5M,執(zhí)行fn函數(shù)的時(shí)候意外產(chǎn)生了一個(gè)全局變量foo,并賦值為了一個(gè)很大的數(shù)據(jù),如果foo變量用完后我們不再需要,那么我們就要主動(dòng)釋放,否則常駐內(nèi)存造成內(nèi)存泄漏,如果這個(gè)全局變量我們后續(xù)還需要使用到,那么就不算內(nèi)存泄漏。


          解決方法就是,將foo定義成局部變量,如:


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump

          function?createBigData()?{
          ????const?bigData?=?[];
          ????for(let?j?=?0;?j?????????bigData.push(new?Array(10000).join("this_is_a_big_data"));
          ????}
          ????return?bigData;
          }

          function?fn()?{
          ????//?foo?=?createBigData();?//?意外的全局變量導(dǎo)致內(nèi)存泄漏
          ????const?foo?=?createBigData();?//?將foo定義為局部變量,避免內(nèi)存泄漏
          }
          for?(let?j?=?0;?j?????fn();
          }
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump



          ④ 事件未及時(shí)銷毀


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump
          const?events?=?require('events');
          function?createBigData()?{
          ????const?bigData?=?[];
          ????for(let?j?=?0;?j?????????bigData.push(new?Array(100000).join("this_is_a_big_data"));
          ????}
          ????return?bigData;
          }

          class?Page?extends?events.EventEmitter?{
          ????onCreate()?{
          ????????const?data?=?createBigData();
          ????????this.handler?=?()?=>?{
          ????????????this.update(data);
          ????????}
          ????????this.on("ok",?this.handler);
          ????}

          ????update(data)?{
          ????????console.log("開(kāi)始更新數(shù)據(jù)了");?//?接收到ok信號(hào),可以開(kāi)始更新數(shù)據(jù)了
          ????}

          ????onDestory()?{
          ???????
          ????}
          }
          let?page?=?new?Page();
          page.onCreate();
          page.onDestory();
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump


          此例中頁(yè)面onCreate的時(shí)候會(huì)注冊(cè)一個(gè)ok事件,事件處理函數(shù)為this.handler,this.handler的定義會(huì)形成一個(gè)閉包,導(dǎo)致data無(wú)法釋放,從而內(nèi)存溢出。


          解決辦法就是移除事件并清空this.handler,因?yàn)閠his.handler這個(gè)閉包函數(shù)被兩個(gè)變量持有,一個(gè)是page對(duì)象的handler屬性持有,另一個(gè)是事件處理器由于注冊(cè)事件后被事件處理器所持有。所以需要釋放this.handler并且移除事件監(jiān)聽(tīng)。


          const?heapdump?=?require('heapdump');
          heapdump.writeSnapshot('start.heapsnapshot');?//?記錄應(yīng)用開(kāi)始時(shí)的內(nèi)存dump
          const?events?=?require('events');
          function?createBigData()?{
          ????const?bigData?=?[];
          ????for(let?j?=?0;?j?????????bigData.push(new?Array(100000).join("this_is_a_big_data"));
          ????}
          ????return?bigData;
          }

          class?Page?extends?events.EventEmitter?{
          ????onCreate()?{
          ????????const?data?=?createBigData();
          ????????this.handler?=?()?=>?{
          ????????????this.update(data);
          ????????}
          ????????this.on("ok",?this.handler);
          ????}

          ????update(data)?{
          ????????console.log("開(kāi)始更新數(shù)據(jù)了");?//?接收到ok信號(hào),可以開(kāi)始更新數(shù)據(jù)了
          ????}

          ????onDestory()?{
          ????????this.removeListener("ok",?this.handler);?//?移除ok事件,解決事件處理器對(duì)this.handler閉包函數(shù)的引用
          ????????this.handler?=?null;?//解除page對(duì)象對(duì)this.handler閉包函數(shù)的引用
          ????}
          }
          let?page?=?new?Page();
          page.onCreate();
          page.onDestory();
          heapdump.writeSnapshot('end.heapsnapshot');?//?記錄應(yīng)用結(jié)束時(shí)的內(nèi)存dump



          解除page對(duì)象和事件處理器對(duì)象對(duì)this.handler閉包函數(shù)的引用后,this.handler閉包函數(shù)就會(huì)被釋放,從而解除閉包,data也會(huì)得到釋放。




          點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開(kāi)更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號(hào)后臺(tái)回復(fù)“?入群?”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          -?END -


          瀏覽 41
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲国产AV天堂 | 中文字幕在线网站 | 99在线播放 | 小早川怜子一区二区 | 欧美三级电影中文字幕 |