乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      深入理解JavaScript系列(16):閉包(Closures)

       andorcba 2015-08-27

      介紹

      本章我們將介紹在JavaScript里大家經(jīng)常來討論的話題 —— 閉包(closure)。閉包其實(shí)大家都已經(jīng)談爛了。盡管如此,這里還是要試著從理論角度來討論下閉包,看看ECMAScript中的閉包內(nèi)部究竟是如何工作的。

      正如在前面的文章中提到的,這些文章都是系列文章,相互之間都是有關(guān)聯(lián)的。因此,為了更好的理解本文要介紹的內(nèi)容,建議先去閱讀第14章作用域鏈第12章變量對象。

      英文原文:http:///ecmascript/chapter-6-closures/

      概論

      在直接討論ECMAScript閉包之前,還是有必要來看一下函數(shù)式編程中一些基本定義。

      眾所周知,在函數(shù)式語言中(ECMAScript也支持這種風(fēng)格),函數(shù)即是數(shù)據(jù)。就比方說,函數(shù)可以賦值給變量,可以當(dāng)參數(shù)傳遞給其他函數(shù),還可以從函數(shù)里返回等等。這類函數(shù)有特殊的名字和結(jié)構(gòu)。

      定義

      A functional argument (“Funarg”) — is an argument which value is a function.
      函數(shù)式參數(shù)(“Funarg”) —— 是指值為函數(shù)的參數(shù)。

      例子:

      function exampleFunc(funArg) {
      funArg();
      }

      exampleFunc(function () {
      alert('funArg');
      });

      上述例子中funarg的實(shí)際參數(shù)其實(shí)是傳遞給exampleFunc的匿名函數(shù)。

      反過來,接受函數(shù)式參數(shù)的函數(shù)稱為高階函數(shù)(high-order function 簡稱:HOF)。還可以稱作:函數(shù)式函數(shù)或者偏數(shù)理或操作符。上述例子中,exampleFunc 就是這樣的函數(shù)。

      此前提到的,函數(shù)不僅可以作為參數(shù),還可以作為返回值。這類以函數(shù)為返回值的函數(shù)稱為帶函數(shù)值的函數(shù)(functions with functional value or function valued functions)。

      (function functionValued() {
      return function () {
      alert('returned function is called');
      };
      })()();

      可以以正常數(shù)據(jù)形式存在的函數(shù)(比方說:當(dāng)參數(shù)傳遞,接受函數(shù)式參數(shù)或者以函數(shù)值返回)都稱作 第一類函數(shù)(一般說第一類對象)。在ECMAScript中,所有的函數(shù)都是第一類對象。

      函數(shù)可以作為正常數(shù)據(jù)存在(例如:當(dāng)參數(shù)傳遞,接受函數(shù)式參數(shù)或者以函數(shù)值返回)都稱作第一類函數(shù)(一般說第一類對象)。

      在ECMAScript中,所有的函數(shù)都是第一類對象。

      接受自己作為參數(shù)的函數(shù),稱為自應(yīng)用函數(shù)(auto-applicative function 或者 self-applicative function):

      (function selfApplicative(funArg) {

      if (funArg && funArg === selfApplicative) {
      alert('self-applicative');
      return;
      }

      selfApplicative(selfApplicative);

      })();

      以自己為返回值的函數(shù)稱為自復(fù)制函數(shù)(auto-replicative function 或者 self-replicative function)。通常,“自復(fù)制”這個(gè)詞用在文學(xué)作品中:

      (function selfReplicative() {
      return selfReplicative;
      })();

      自復(fù)制函數(shù)的其中一個(gè)比較有意思的模式是讓僅接受集合的一個(gè)項(xiàng)作為參數(shù)來接受從而代替接受集合本身。

      // 接受集合的函數(shù)
      function registerModes(modes) {
      modes.forEach(registerMode, modes);
      }

      // 用法
      registerModes(['roster', 'accounts', 'groups']);

      // 自復(fù)制函數(shù)的聲明
      function modes(mode) {
      registerMode(mode); // 注冊一個(gè)mode
      return modes; // 返回函數(shù)自身
      }

      // 用法,modes鏈?zhǔn)秸{(diào)用
      modes('roster')('accounts')('groups')

      //有點(diǎn)類似:jQueryObject.addClass("a").toggle().removClass("b")

      但直接傳集合用起來相對來說,比較有效并且直觀。

      在函數(shù)式參數(shù)中定義的變量,在“funarg”激活時(shí)就能夠訪問了(因?yàn)榇鎯ι舷挛臄?shù)據(jù)的變量對象每次在進(jìn)入上下文的時(shí)候就創(chuàng)建出來了):

      function testFn(funArg) {
      // funarg激活時(shí), 局部變量localVar可以訪問了
      funArg(10); // 20
      funArg(20); // 30

      }

      testFn(function (arg) {
      var localVar = 10;
      alert(arg + localVar);
      });

      然而,我們從第14章知道,在ECMAScript中,函數(shù)是可以封裝在父函數(shù)中的,并可以使用父函數(shù)上下文的變量。這個(gè)特性會引發(fā)funarg問題。

      Funarg問題

      面向堆棧的編程語言中,函數(shù)的局部變量都是保存在棧上的,每當(dāng)函數(shù)激活的時(shí)候,這些變量和函數(shù)參數(shù)都會壓入到該堆棧上。

      當(dāng)函數(shù)返回的時(shí)候,這些參數(shù)又會從棧中移除。這種模型對將函數(shù)作為函數(shù)式值使用的時(shí)候有很大的限制(比方說,作為返回值從父函數(shù)中返回)。絕大部分情況下,問題會出現(xiàn)在當(dāng)函數(shù)有自由變量的時(shí)候。

      自由變量是指在函數(shù)中使用的,但既不是函數(shù)參數(shù)也不是函數(shù)的局部變量的變量

      例子:

      function testFn() {

      var localVar = 10;

      function innerFn(innerParam) {
      alert(innerParam + localVar);
      }

      return innerFn;
      }

      var someFn = testFn();
      someFn(20); // 30

      上述例子中,對于innerFn函數(shù)來說,localVar就屬于自由變量。

      對于采用面向棧模型來存儲局部變量的系統(tǒng)而言,就意味著當(dāng)testFn函數(shù)調(diào)用結(jié)束后,其局部變量都會從堆棧中移除。這樣一來,當(dāng)從外部對innerFn進(jìn)行函數(shù)調(diào)用的時(shí)候,就會發(fā)生錯(cuò)誤(因?yàn)閘ocalVar變量已經(jīng)不存在了)。

      而且,上述例子在面向棧實(shí)現(xiàn)模型中,要想將innerFn以返回值返回根本是不可能的。因?yàn)樗彩莟estFn函數(shù)的局部變量,也會隨著testFn的返回而移除。

      還有一個(gè)問題是當(dāng)系統(tǒng)采用動態(tài)作用域,函數(shù)作為函數(shù)參數(shù)使用的時(shí)候有關(guān)。

      看如下例子(偽代碼):

      var z = 10;

      function foo() {
      alert(z);
      }

      foo(); // 10 – 使用靜態(tài)和動態(tài)作用域的時(shí)候

      (function () {

      var z = 20;
      foo(); // 10 – 使用靜態(tài)作用域, 20 – 使用動態(tài)作用域

      })();

      // 將foo作為參數(shù)的時(shí)候是一樣的
      (function (funArg) {

      var z = 30;
      funArg(); // 10 – 靜態(tài)作用域, 30 – 動態(tài)作用域

      })(foo);

      我們看到,采用動態(tài)作用域,變量(標(biāo)識符)的系統(tǒng)是通過變量動態(tài)棧來管理的。因此,自由變量是在當(dāng)前活躍的動態(tài)鏈中查詢的,而不是在函數(shù)創(chuàng)建的時(shí)候保存起來的靜態(tài)作用域鏈中查詢的。

      這樣就會產(chǎn)生沖突。比方說,即使Z仍然存在(與之前從棧中移除變量的例子相反),還是會有這樣一個(gè)問題: 在不同的函數(shù)調(diào)用中,Z的值到底取哪個(gè)呢(從哪個(gè)上下文,哪個(gè)作用域中查詢)?

      上述描述的就是兩類funarg問題 —— 取決于是否將函數(shù)以返回值返回(第一類問題)以及是否將函數(shù)當(dāng)函數(shù)參數(shù)使用(第二類問題)。

      為了解決上述問題,就引入了 閉包的概念。

      閉包

      閉包是代碼塊和創(chuàng)建該代碼塊的上下文中數(shù)據(jù)的結(jié)合。

      讓我們來看下面這個(gè)例子(偽代碼):

      var x = 20;

      function foo() {
      alert(x); // 自由變量"x" == 20
      }

      // 為foo閉包
      fooClosure = {
      call: foo // 引用到function
      lexicalEnvironment: {x: 20} // 搜索上下文的上下文
      };

      上述例子中,“fooClosure”部分是偽代碼。對應(yīng)的,在ECMAScript中,“foo”函數(shù)已經(jīng)有了一個(gè)內(nèi)部屬性——創(chuàng)建該函數(shù)上下文的作用域鏈。

      “l(fā)exical”通常是省略的。上述例子中是為了強(qiáng)調(diào)在閉包創(chuàng)建的同時(shí),上下文的數(shù)據(jù)就會保存起來。當(dāng)下次調(diào)用該函數(shù)的時(shí)候,自由變量就可以在保存的(閉包)上下文中找到了,正如上述代碼所示,變量“z”的值總是10。

      定義中我們使用的比較廣義的詞 —— “代碼塊”,然而,通常(在ECMAScript中)會使用我們經(jīng)常用到的函數(shù)。當(dāng)然了,并不是所有對閉包的實(shí)現(xiàn)都會將閉包和函數(shù)綁在一起,比方說,在Ruby語言中,閉包就有可能是: 一個(gè)過程對象(procedure object), 一個(gè)lambda表達(dá)式或者是代碼塊。

      對于要實(shí)現(xiàn)將局部變量在上下文銷毀后仍然保存下來,基于棧的實(shí)現(xiàn)顯然是不適用的(因?yàn)榕c基于棧的結(jié)構(gòu)相矛盾)。因此在這種情況下,上層作用域的閉包數(shù)據(jù)是通過 動態(tài)分配內(nèi)存的方式來實(shí)現(xiàn)的(基于“堆”的實(shí)現(xiàn)),配合使用垃圾回收器(garbage collector簡稱GC)和 引用計(jì)數(shù)(reference counting)。這種實(shí)現(xiàn)方式比基于棧的實(shí)現(xiàn)性能要低,然而,任何一種實(shí)現(xiàn)總是可以優(yōu)化的: 可以分析函數(shù)是否使用了自由變量,函數(shù)式參數(shù)或者函數(shù)式值,然后根據(jù)情況來決定 —— 是將數(shù)據(jù)存放在堆棧中還是堆中。

      ECMAScript閉包的實(shí)現(xiàn)

      討論完理論部分,接下來讓我們來介紹下ECMAScript中閉包究竟是如何實(shí)現(xiàn)的。這里還是有必要再次強(qiáng)調(diào)下:ECMAScript只使用靜態(tài)(詞法)作用域(而諸如Perl這樣的語言,既可以使用靜態(tài)作用域也可以使用動態(tài)作用域進(jìn)行變量聲明)。

      var x = 10;

      function foo() {
      alert(x);
      }

      (function (funArg) {

      var x = 20;

      // 變量"x"在(lexical)上下文中靜態(tài)保存的,在該函數(shù)創(chuàng)建的時(shí)候就保存了
      funArg(); // 10, 而不是20

      })(foo);

      技術(shù)上說,創(chuàng)建該函數(shù)的父級上下文的數(shù)據(jù)是保存在函數(shù)的內(nèi)部屬性 [[Scope]]中的。如果你還不了解什么是[[Scope]],建議你先閱讀第14章, 該章節(jié)對[[Scope]]作了非常詳細(xì)的介紹。如果你對[[Scope]]和作用域鏈的知識完全理解了的話,那對閉包也就完全理解了。

      根據(jù)函數(shù)創(chuàng)建的算法,我們看到 在ECMAScript中,所有的函數(shù)都是閉包,因?yàn)樗鼈兌际窃趧?chuàng)建的時(shí)候就保存了上層上下文的作用域鏈(除開異常的情況) (不管這個(gè)函數(shù)后續(xù)是否會激活 —— [[Scope]]在函數(shù)創(chuàng)建的時(shí)候就有了):

      var x = 10;

      function foo() {
      alert(x);
      }

      // foo是閉包
      foo: <FunctionObject> = {
      [[Call]]: <code block of foo>,
      [[Scope]]: [
      global: {
      x: 10
      }
      ],
      ... // 其它屬性
      };

      如我們所說,為了優(yōu)化目的,當(dāng)一個(gè)函數(shù)沒有使用自由變量的話,實(shí)現(xiàn)可能不保存在副作用域鏈里。不過,在ECMA-262-3規(guī)范里任何都沒說。因此,正常來說,所有的參數(shù)都是在創(chuàng)建階段保存在[[Scope]]屬性里的。

      有些實(shí)現(xiàn)中,允許對閉包作用域直接進(jìn)行訪問。比如Rhino,針對函數(shù)的[[Scope]]屬性,對應(yīng)有一個(gè)非標(biāo)準(zhǔn)的 __parent__屬性,在第12章中作過介紹:

      var global = this;
      var x = 10;

      var foo = (function () {

      var y = 20;

      return function () {
      alert(y);
      };

      })();

      foo(); // 20
      alert(foo.__parent__.y); // 20

      foo.__parent__.y = 30;
      foo(); // 30

      // 可以通過作用域鏈移動到頂部
      alert(foo.__parent__.__parent__ === global); // true
      alert(foo.__parent__.__parent__.x); // 10

      所有對象都引用一個(gè)[[Scope]]

      這里還要注意的是:在ECMAScript中,同一個(gè)父上下文中創(chuàng)建的閉包是共用一個(gè)[[Scope]]屬性的。也就是說,某個(gè)閉包對其中[[Scope]]的變量做修改會影響到其他閉包對其變量的讀取:

      這就是說:所有的內(nèi)部函數(shù)都共享同一個(gè)父作用域

      var firstClosure;
      var secondClosure;

      function foo() {

      var x = 1;

      firstClosure = function () { return ++x; };
      secondClosure = function () { return --x; };

      x = 2; // 影響 AO["x"], 在2個(gè)閉包公有的[[Scope]]中

      alert(firstClosure()); // 3, 通過第一個(gè)閉包的[[Scope]]
      }

      foo();

      alert(firstClosure()); // 4
      alert(secondClosure()); // 3

      關(guān)于這個(gè)功能有一個(gè)非常普遍的錯(cuò)誤認(rèn)識,開發(fā)人員在循環(huán)語句里創(chuàng)建函數(shù)(內(nèi)部進(jìn)行計(jì)數(shù))的時(shí)候經(jīng)常得不到預(yù)期的結(jié)果,而期望是每個(gè)函數(shù)都有自己的值。

      var data = [];

      for (var k = 0; k < 3; k++) {
      data[k] = function () {
      alert(k);
      };
      }

      data[0](); // 3, 而不是0
      data[1](); // 3, 而不是1
      data[2](); // 3, 而不是2

      上述例子就證明了 —— 同一個(gè)上下文中創(chuàng)建的閉包是共用一個(gè)[[Scope]]屬性的。因此上層上下文中的變量“k”是可以很容易就被改變的。

      activeContext.Scope = [
      ... // 其它變量對象
      {data: [...], k: 3} // 活動對象
      ];

      data[0].[[Scope]] === Scope;
      data[1].[[Scope]] === Scope;
      data[2].[[Scope]] === Scope;

      這樣一來,在函數(shù)激活的時(shí)候,最終使用到的k就已經(jīng)變成了3了。如下所示,創(chuàng)建一個(gè)閉包就可以解決這個(gè)問題了:

      var data = [];

      for (var k = 0; k < 3; k++) {
      data[k] = (function _helper(x) {
      return function () {
      alert(x);
      };
      })(k); // 傳入"k"值
      }

      // 現(xiàn)在結(jié)果是正確的了
      data[0](); // 0
      data[1](); // 1
      data[2](); // 2

      讓我們來看看上述代碼都發(fā)生了什么?函數(shù)“_helper”創(chuàng)建出來之后,通過傳入?yún)?shù)“k”激活。其返回值也是個(gè)函數(shù),該函數(shù)保存在對應(yīng)的數(shù)組元素中。這種技術(shù)產(chǎn)生了如下效果: 在函數(shù)激活時(shí),每次“_helper”都會創(chuàng)建一個(gè)新的變量對象,其中含有參數(shù)“x”,“x”的值就是傳遞進(jìn)來的“k”的值。這樣一來,返回的函數(shù)的[[Scope]]就成了如下所示:

      data[0].[[Scope]] === [
      ... // 其它變量對象
      父級上下文中的活動對象AO: {data: [...], k: 3},
      _helper上下文中的活動對象AO: {x: 0}
      ];

      data[1].[[Scope]] === [
      ... // 其它變量對象
      父級上下文中的活動對象AO: {data: [...], k: 3},
      _helper上下文中的活動對象AO: {x: 1}
      ];

      data[2].[[Scope]] === [
      ... // 其它變量對象
      父級上下文中的活動對象AO: {data: [...], k: 3},
      _helper上下文中的活動對象AO: {x: 2}
      ];

      我們看到,這時(shí)函數(shù)的[[Scope]]屬性就有了真正想要的值了,為了達(dá)到這樣的目的,我們不得不在[[Scope]]中創(chuàng)建額外的變量對象。要注意的是,在返回的函數(shù)中,如果要獲取“k”的值,那么該值還是會是3。

      順便提下,大量介紹JavaScript的文章都認(rèn)為只有額外創(chuàng)建的函數(shù)才是閉包,這種說法是錯(cuò)誤的。實(shí)踐得出,這種方式是最有效的,然而,從理論角度來說,在ECMAScript中所有的函數(shù)都是閉包。

      然而,上述提到的方法并不是唯一的方法。通過其他方式也可以獲得正確的“k”的值,如下所示:

      var data = [];

      for (var k = 0; k < 3; k++) {
      (data[k] = function () {
      alert(arguments.callee.x);
      }).x = k; // 將k作為函數(shù)的一個(gè)屬性
      }

      // 結(jié)果也是對的
      data[0](); // 0
      data[1](); // 1
      data[2](); // 2

      Funarg和return

      另外一個(gè)特性是從閉包中返回。在ECMAScript中,閉包中的返回語句會將控制流返回給調(diào)用上下文(調(diào)用者)。而在其他語言中,比如,Ruby,有很多中形式的閉包,相應(yīng)的處理閉包返回也都不同,下面幾種方式都是可能的:可能直接返回給調(diào)用者,或者在某些情況下——直接從上下文退出。

      ECMAScript標(biāo)準(zhǔn)的退出行為如下:

      function getElement() {

      [1, 2, 3].forEach(function (element) {

      if (element % 2 == 0) {
      // 返回給函數(shù)"forEach"函數(shù)
      // 而不是返回給getElement函數(shù)
      alert('found: ' + element); // found: 2
      return element;
      }

      });

      return null;
      }

      然而,在ECMAScript中通過try catch可以實(shí)現(xiàn)如下效果:

      var $break = {};

      function getElement() {

      try {

      [1, 2, 3].forEach(function (element) {

      if (element % 2 == 0) {
      // // 從getElement中"返回"
      alert('found: ' + element); // found: 2
      $break.data = element;
      throw $break;
      }

      });

      } catch (e) {
      if (e == $break) {
      return $break.data;
      }
      }

      return null;
      }

      alert(getElement()); // 2

      理論版本

      這里說明一下,開發(fā)人員經(jīng)常錯(cuò)誤將閉包簡化理解成從父上下文中返回內(nèi)部函數(shù),甚至理解成只有匿名函數(shù)才能是閉包。

      再說一下,因?yàn)樽饔糜蜴?,使得所有的函?shù)都是閉包(與函數(shù)類型無關(guān): 匿名函數(shù),F(xiàn)E,NFE,F(xiàn)D都是閉包)。

      這里只有一類函數(shù)除外,那就是通過Function構(gòu)造器創(chuàng)建的函數(shù),因?yàn)槠鋄[Scope]]只包含全局對象。

      為了更好的澄清該問題,我們對ECMAScript中的閉包給出2個(gè)正確的版本定義:

      ECMAScript中,閉包指的是:

      1. 從理論角度:所有的函數(shù)。因?yàn)樗鼈兌荚趧?chuàng)建的時(shí)候就將上層上下文的數(shù)據(jù)保存起來了。哪怕是簡單的全局變量也是如此,因?yàn)楹瘮?shù)中訪問全局變量就相當(dāng)于是在訪問自由變量,這個(gè)時(shí)候使用最外層的作用域。
      2. 從實(shí)踐角度:以下函數(shù)才算是閉包:
        1. 即使創(chuàng)建它的上下文已經(jīng)銷毀,它仍然存在(比如,內(nèi)部函數(shù)從父函數(shù)中返回)
        2. 在代碼中引用了自由變量

      閉包用法實(shí)戰(zhàn)

      實(shí)際使用的時(shí)候,閉包可以創(chuàng)建出非常優(yōu)雅的設(shè)計(jì),允許對funarg上定義的多種計(jì)算方式進(jìn)行定制。如下就是數(shù)組排序的例子,它接受一個(gè)排序條件函數(shù)作為參數(shù):

      [1, 2, 3].sort(function (a, b) {
      ... // 排序條件
      });

      同樣的例子還有,數(shù)組的map方法是根據(jù)函數(shù)中定義的條件將原數(shù)組映射到一個(gè)新的數(shù)組中:

      [1, 2, 3].map(function (element) {
      return element * 2;
      }); // [2, 4, 6]

      使用函數(shù)式參數(shù),可以很方便的實(shí)現(xiàn)一個(gè)搜索方法,并且可以支持無限制的搜索條件:

      someCollection.find(function (element) {
      return element.someProperty == 'searchCondition';
      });

      還有應(yīng)用函數(shù),比如常見的forEach方法,將函數(shù)應(yīng)用到每個(gè)數(shù)組元素:

      [1, 2, 3].forEach(function (element) {
      if (element % 2 != 0) {
      alert(element);
      }
      }); // 1, 3

      順便提下,函數(shù)對象的 apply 和 call方法,在函數(shù)式編程中也可以用作應(yīng)用函數(shù)。 apply和call已經(jīng)在討論“this”的時(shí)候介紹過了;這里,我們將它們看作是應(yīng)用函數(shù) —— 應(yīng)用到參數(shù)中的函數(shù)(在apply中是參數(shù)列表,在call中是獨(dú)立的參數(shù)):

      (function () {
      alert([].join.call(arguments, ';')); // 1;2;3
      }).apply(this, [1, 2, 3]);

      閉包還有另外一個(gè)非常重要的應(yīng)用 —— 延遲調(diào)用:

      var a = 10;
      setTimeout(function () {
      alert(a); // 10, after one second
      }, 1000);

      還有回調(diào)函數(shù)

      //...
      var x = 10;
      // only for example
      xmlHttpRequestObject.onreadystatechange = function () {
      // 當(dāng)數(shù)據(jù)就緒的時(shí)候,才會調(diào)用;
      // 這里,不論是在哪個(gè)上下文中創(chuàng)建
      // 此時(shí)變量“x”的值已經(jīng)存在了
      alert(x); // 10
      };
      //...

      還可以創(chuàng)建封裝的作用域來隱藏輔助對象:

      var foo = {};

      // 初始化
      (function (object) {

      var x = 10;

      object.getX = function _getX() {
      return x;
      };

      })(foo);

      alert(foo.getX()); // 獲得閉包 "x" – 10

      總結(jié)

      本文介紹了更多關(guān)于ECMAScript-262-3的理論知識,而我認(rèn)為,這些基礎(chǔ)的理論有助于理解ECMAScript中閉包的概念。如果有任何問題,我回在評論里回復(fù)大家。

      其它參考

      同步與推薦

      本文已同步至目錄索引:深入理解JavaScript系列

      深入理解JavaScript系列文章,包括了原創(chuàng),翻譯,轉(zhuǎn)載等各類型的文章,如果對你有用,請推薦支持一把,給大叔寫作的動力。

        本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多