摘要:對數(shù)組函數(shù)而言,相當(dāng)于產(chǎn)生了個閉包。關(guān)于對象在閉包中使用對象也會導(dǎo)致一些問題。不過,匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此其對象通常指向。由于聲明函數(shù)時與聲明函數(shù)時的值是不同的,因此閉包與閉包貌似將會表示各自不同的值。
這幾天看到閉包一章,從工具書到各路大神博客,都各自有著不同的理解,以下我將選擇性的抄(咳咳,當(dāng)然還是會附上自己理解的)一些大神們對閉包的原理及其使用文章,當(dāng)作是自己初步理解這一功能函數(shù)的過程吧。
首先先上鏈接:
簡書作者波同學(xué)的JS進階文章系列: 前端基礎(chǔ)進階系列 其他: JS秘密花園 javascript深入理解js閉包 阮一峰《JavaScript標(biāo)準(zhǔn)參考教程》 一不小心就做錯的JS閉包面試題還有一些也很不錯,但主要是以應(yīng)用為主,原理解釋沒有上面幾篇深入,不過作為閉包的拓展應(yīng)用其實也可以看一看;
JavaScript中的匿名函數(shù)及函數(shù)的閉包
紅皮書《JS高程》的閉包:閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)。創(chuàng)建閉包的常見方式,就是在一個函數(shù)內(nèi)部創(chuàng)建另一個函數(shù)。
從這句話我們知道:閉包是一個函數(shù)
function createComparisonFunction(propertyName) { return function(obj1,obj2) { var value1 = obj1[propertyName]; var value2 = obj2[propertyName]; if (value1 < value2) { return -1; } else if (value1 > value2) { return 1; } else { return 0; } }; }
這段代碼,我們能直接看出,共存在三個作用域,Global、createComparisonFunction、匿名函數(shù)funciton,因其JS的作用域鏈特性,后者能訪問自身及前者的作用域。而返回的匿名函數(shù)即使在其他地方被調(diào)用了,但它仍可以訪問變量propertyName。之所以還能夠訪問這個變量,是因為內(nèi)部函數(shù)的作用域鏈中包含createComparisonFunction的作用域。我們來深入了解一下,函數(shù)執(zhí)行時具體發(fā)生了什么?
當(dāng)?shù)谝粋€函數(shù)被調(diào)用時,會創(chuàng)建一個執(zhí)行環(huán)境(Execution Context,也叫執(zhí)行上下文)及相應(yīng)的作用域鏈,并把作用域鏈賦值給一個特殊的內(nèi)部屬性[[Scope]]。然后,使用this、arguments和其他命名參數(shù)的值來初始化函數(shù)的活動對象(Activation Object)。但在作用域鏈中,外部函數(shù)的活動對象處于第二位,外部函數(shù)的外部函數(shù)處于第三位,最后是全局執(zhí)行環(huán)境(Global Context)。
換一個栗子:
function createFunctions() { var result = new Array(); for (var i=0;i<10;i++) { result[i] = function() { return i; }; } return result; } var arr = createFunctions(); alert(arr[0]()); // 10 alert(arr[1]()); // 10
/這個函數(shù)返回一個函數(shù)數(shù)組。表面上看,似乎每個函數(shù)都應(yīng)該返回自己的索引值,位置0的函數(shù)返回0,位置1的函數(shù)返回1,以此類推。但但實際上,每個函數(shù)都返回10,為什么?
數(shù)組對象內(nèi)的匿名函數(shù)里的i是引用createFunctions作用域內(nèi)的,當(dāng)調(diào)用數(shù)組內(nèi)函數(shù)的時候,createFunctions函數(shù)早已執(zhí)行完畢。
這圖不傳也罷了,畫得忒丑了。
數(shù)組內(nèi)的閉包函數(shù)指向的i,存放在createFunctions函數(shù)的作用域內(nèi),確切的說,是在函數(shù)的變量對象里,for循環(huán)每次更新的i值,就是從它那兒來的。所以當(dāng)調(diào)用數(shù)組函數(shù)時,循環(huán)已經(jīng)完成,i也為循環(huán)后的值,都為10;
有人會問,那result[i]為什么沒有變?yōu)?0呢?
要知道,作用域的判定是看是否在函數(shù)內(nèi)的,result[i] = function.......是在匿名函數(shù)外,那它就還是屬于createFunctions的作用域內(nèi),那result[i]里的i就依然會更新
那么如何使結(jié)果變?yōu)槲覀兿胍哪兀恳彩峭ㄟ^閉包。
function createFunctions() { var result = []; for (var i=0;i<10;i++) { !function(i) { result[i] = function() {console.log(i)}; }(i); } return result; } var arr = createFunctions(); arr[0](); arr[1](); arr[2]();
function createFunctions() { var result = []; function fn(i) { result[i] = function() {console.log(i)} }; for (var i=0;i<10;i++) { fn(i); } return result; } var arr = createFunctions(); arr[0](); arr[1](); arr[2]();
var arr = []; function fn(i) { arr[i] = function() {console.log(i)} } function createFunctions() { for (var i=0;i<10;i++) { fn(i); } } fn(createFunctions()); arr[0](); arr[1](); arr[2]();
以第一種為例,通過一個立即調(diào)用函數(shù),將外函數(shù)當(dāng)前循環(huán)的i值作為實參傳入,并存放在立即調(diào)用函數(shù)的變量對象內(nèi),此時,這個函數(shù)立即調(diào)用函數(shù)和數(shù)組內(nèi)的匿名函數(shù)就相當(dāng)于一個閉包,數(shù)組的匿名函數(shù)引用了立即調(diào)用函數(shù)變量對象內(nèi)的i。當(dāng)createFuncions執(zhí)行完畢,里面的i值已經(jīng)是10了。但是由于閉包的特性,每個函數(shù)都有各自的i值對應(yīng)著。對數(shù)組函數(shù)而言,相當(dāng)于產(chǎn)生了10個閉包。
所以能看出,閉包也十分的占用內(nèi)存,只要閉包不執(zhí)行,那么變量對象就無法被回收,所以不是特別需要,盡量不使用閉包。
關(guān)于this對象在閉包中使用this對象也會導(dǎo)致一些問題。我們知道,this對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的;在全局對象中,this等于window,而當(dāng)函數(shù)被作為某個對象的方法調(diào)用時,this等于那個對象。不過,匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此其this對象通常指向window。但有時候由于編寫閉包的方式不同,這一點可能不會那么明顯。(當(dāng)然可以用call和apply)
var name = "The Window"; var obj = { name:"My Object", getName:function () { var bibao = function () { return this.name; }; return bibao; } }; alert(obj.getName()()); // The Window
先創(chuàng)建一個全局變量name,又創(chuàng)建一個包含name屬性的對象。這個對象包含一個方法——getName(),它返回一個匿名函數(shù),而匿名函數(shù)又返回this.name。由于getName()返回一個函數(shù),因此調(diào)用obj.getName()();就會立即調(diào)用它返回的函數(shù),結(jié)果就是返回一個字符串。然而,這個例子返回的字符串是"The Window",即全局name變量的值。為什么匿名函數(shù)沒有取得其波包含作用域(或外部作用域)的this對象呢?
每個函數(shù)調(diào)用時其活動對象都會自動取得兩個特殊變量:this和arguments。
內(nèi)部函數(shù)在搜索這兩個變量時,只會搜索到其活動對象為止,因此永遠(yuǎn)不可能直接訪問外部函數(shù)中的這兩個變量。不過,把外部作用域中的this對象保存在一個閉包能夠訪問到的變量里,就可以讓閉包訪問該對象了。
var name = "The Window"; var obj = { name:"My Object", getName:function () { var that = this; return function () { return that.name; }; } }; alert(obj.getName()());
this和arguments也存在同樣的問題,如果想訪問作用域中arguments對象,必須將該對象的引用保存到另一個閉包能夠訪問的變量中。
var name = "The Window"; var obj = { name:"My Object", getName:function (arg1,arg2) { var arg = []; arg[0] = arg1; arg[1] = arg2; function bibao() { return arg[0]+arg[1]; } return bibao; } }; alert(obj.getName(1,2)())obj.getName方法保存了其接收到的實參在它的變量對象上,并在執(zhí)行函數(shù)結(jié)束后沒有被回收,因為返回的閉包函數(shù)引用著obj.Name方法里的arg數(shù)組對象。使得外部變量成功訪問到了函數(shù)內(nèi)部作用域及其局部變量。
在幾種特殊情況下,this引用的值可能會意外的改變。
var name = "The Window"; var obj = { name:"My Object", getName:function () { return this.name; } };
這里的getName()只簡單的返回this.name的值。
var name = "The Window"; var obj = { name:"My Object", getName:function () { console.log(this.name); } }; obj.getName(); // "My Object" (obj.getName)(); // "My Object" (obj.getName = obj.getName)(); // "The Window"
第一個obj.getName函數(shù)作為obj對象的方法調(diào)用,則自然其this引用指向obj對象。
第二個,加括號將函數(shù)定義之后,作為函數(shù)表達(dá)式執(zhí)行調(diào)用,this引用指向不變。
第三個,括號內(nèi)先執(zhí)行了一條賦值語句,然后在調(diào)用賦值后的結(jié)果。相當(dāng)于重新定義了函數(shù),this引用的值不能維持,于是返回"The Window"。
用setTimeout結(jié)合循環(huán)考察閉包是一個很老的面試題了
// 利用閉包,修改下面的代碼,讓循環(huán)輸出的結(jié)果依次為1, 2, 3, 4, 5 for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log(i); }, i*1000 ); }setTimeout的執(zhí)行與我們平常的JS代碼執(zhí)行不一樣,這里需要提到一個隊列數(shù)據(jù)結(jié)構(gòu)執(zhí)行的概念。 關(guān)于setTimeout與循環(huán)閉包的思考題
個人理解:由于setTimeout函數(shù)的特殊性,須等其他非隊列結(jié)構(gòu)代碼執(zhí)行完畢后,這個setTimeout函數(shù)才會進入隊列執(zhí)行棧。
用chrome開發(fā)者工具分析這段代碼,可以先自己分析一次,看看依次彈出什么?setTimeout(function() { console.log(a); }, 0); var a = 10; console.log(b); console.log(fn); var b = 20; function fn() { setTimeout(function() { console.log("setTImeout 10ms."); }, 10); } fn.toString = function() { return 30; } console.log(fn); setTimeout(function() { console.log("setTimeout 20ms."); }, 20); fn();
答案:
設(shè)置斷點如圖所示,今天剛學(xué)Chrome的開發(fā)者工具,有哪些使用上的錯誤還請指出。
我分別給變量a、b、fn函數(shù)都設(shè)置了觀察,變量的值變化將會實時地在右上角中顯示,可以看到,在JS解釋器運行第一行代碼前,變量a、b就已經(jīng)存在了,而fn函數(shù)已經(jīng)完成了聲明。接下來我們繼續(xù)執(zhí)行。要注意:藍(lán)色部分說明這些代碼將在下一次操作中執(zhí)行,而不是已經(jīng)執(zhí)行完畢。
把第一個setTimeout函數(shù)執(zhí)行完畢后也沒有反應(yīng)。我給三個setTimeout內(nèi)的匿名函數(shù)也加上觀察選項,卻顯示不可使用。
所以,下一次執(zhí)行會發(fā)生什么?對console出b的值,但是b沒賦值,右上角也看到了,所以顯示undefined。
而console.log(fn)就是將fn函數(shù)函數(shù)體從控制臺彈出,要注意,console會隱式調(diào)用toString方法,這個會在后面講到。
現(xiàn)在第26行之前(不包括26行)的代碼都已略過,a,b變量也已得到賦值,繼續(xù)執(zhí)行。
重寫了toString方法前:
重寫后:
toString方法是Object所有,所有由它構(gòu)造的實例都能調(diào)用,現(xiàn)在這個方法被改寫并作為fn對象的屬性(方法)保留下來。
console會隱式調(diào)用toString方法,所以30行的console會彈出30;
繼續(xù)執(zhí)行,定義setTimeout函數(shù)也是什么沒有發(fā)生,知道調(diào)用fn前。
調(diào)用fn,是不是就會執(zhí)行setTimeout函數(shù)呢?其實沒有,我們可以看到call stack一欄已經(jīng)是fn的執(zhí)行棧了,但是依舊沒發(fā)生什么。
但是:
當(dāng)call stack里的環(huán)境都已退出,執(zhí)行棧里沒有任何上下文時,三個setTimeout函數(shù)就執(zhí)行了,那這三個時間戳函數(shù)那個先執(zhí)行,那個后執(zhí)行呢?由設(shè)定的延遲時間決定,這個延遲時間是相對于其他代碼執(zhí)行完畢的那一刻。
不信我們可以通過改變延遲時間重新試一次就知道了。
我們在看回原來的閉包代碼:
// 利用閉包,修改下面的代碼,讓循環(huán)輸出的結(jié)果依次為1, 2, 3, 4, 5 for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log(i); }, i*1000 ); }
先確認(rèn)一個問題,setTimeout函數(shù)里的匿名函數(shù)的i指向哪兒?對,是全局變量里的i。
setTimeout里的匿名函數(shù)執(zhí)行前,外部循環(huán)已經(jīng)結(jié)束,i值已經(jīng)更新為6,這時setTimeout調(diào)用匿名函數(shù),里面的i當(dāng)然都是6了。
我們需要創(chuàng)建一個能夠保存當(dāng)前i值的"盒子"給匿名函數(shù),使得匿名函數(shù)能夠引用新創(chuàng)建的父函數(shù)。
// 利用閉包,修改下面的代碼,讓循環(huán)輸出的結(jié)果依次為1, 2, 3, 4, 5 for (var i=1; i<=5; i++) { !function (i) { setTimeout( function timer() { console.log(i); }, i*1000 ); }(i); }
自調(diào)用函數(shù)就是那個"盒子"
關(guān)于《JavaScript編程全解》中的說明考慮這個函數(shù):
function f(arg) { var n = 123 + Number(arg); function g() {console.log("n is "+n);console.log("g is called");} n++; function gg() {console.log("n is "+n);console.log("g is called");} return [g,gg]; }
調(diào)用數(shù)組內(nèi)函數(shù)的console結(jié)果是什么?
var arr = f(1); arr[0](); // 對閉包g的調(diào)用 // "n is 125" "g is called" arr[1](); // 對閉包gg的調(diào)用 // "n is 125" "gg is called"
函數(shù)g與函數(shù)gg保持了各自含有局部變量n的執(zhí)行環(huán)境。由于聲明函數(shù)g時與聲明函數(shù)gg時的n值是不同的,因此閉包g與閉包gg貌似將會表示各自不同的n值。實際上兩者都將表示相同的值。因為它們引用了同一個對象。
即都是引用了,f函數(shù)執(zhí)行環(huán)境內(nèi)變量對象內(nèi)的n值。當(dāng)執(zhí)行f(1)的時候,n值就已經(jīng)更新為最后計算的值。
防范命名空間的污染 模塊:在JavaScript中,最外層代碼(函數(shù)之外)所寫的名稱(變量名與函數(shù)名)具有全局作用域,即所謂的全局變量與全局函數(shù)。JavaScript的程序代碼即使在分割為多個源文件后,也能相互訪問其全局名稱。在JavaScript的規(guī)范中不存在所謂的模塊的語言功能。
因此,對于客戶端JavaScript,如果在一個HTML文件中對多個JavaScript文件進行讀取,則他們相互的全局名稱會發(fā)生沖突。也就是說,在某個文件中使用的名稱無法同時在另一個文件中使用。即使在獨立開發(fā)中這也很不方便,在使用他們開發(fā)的庫之類時就更加麻煩了。
此外,全局變量還降低了代碼的可維護性。不過也不能就簡單下定論說問題只是由全局變量造成的。這就如同在Java這種語言規(guī)范并不支持全局變量的語言中,同樣可以很容易創(chuàng)建出和全局變量功能類似的變量。
從形式上看,在JavaScript中減少全局變量的數(shù)量的方法時很簡單的。首先我們按照下面的代碼這樣預(yù)設(shè)一下全局函數(shù)與全局變量。
// 全局函數(shù) function sum(a,b) { return Number(a)+Number(b); } // 全局變量 var position = {x:2,y:3}; // 借助通過對象字面量生成對象的屬性,將名稱封入對象的內(nèi)部。于是從形式上看,全局變量減少了 var MyModule = { sum:function (a,b) { return Number(a)+Number(b); }, position:{x:2,y:3} }; alert(MyModule.sum(3,3)); // 6 alert(MyModule.position.x); // 2
上面的例子使用對象字面量,不過也可以像下面這樣不使用對象字面量。
var MyModule = {}; // 也可以通過new表達(dá)式生成 MyModule.sum = function (a,b) {return Number(a)+Number(b);}; MyModule.position = {x:2,y:3};
這個例子中,我們將MyModule稱為模塊名。如果完全采用這種方式,對于1個文件來說,只需要一個模塊名就能消減全局變量的數(shù)量。當(dāng)然,模塊名之間仍然可能產(chǎn)生沖突,不過這一問題在其他程序設(shè)計語言中也是一個無法被避免的問題。
通過這種將名稱封入對象之中的方法,可以避免名稱沖突的問題。但是這并沒有解決全局名稱的另一個問題,也就是作用域過廣的問題。通過MyModule.position.x這樣一個較長的名稱,就可以從代碼的任意一處訪問該變量。
// 在此調(diào)用匿名函數(shù) // 由于匿名函數(shù)的返回值是一個函數(shù),所以變量sum是一個函數(shù) var sum = (function () { // 無法從函數(shù)外部訪問該名稱 // 實際上,這變成了一個私有變量 // 一般來說,在函數(shù)被調(diào)用之后該名稱就無法再被訪問 // 不過由于是在被返回的匿名函數(shù)中,所以仍可以繼續(xù)被使用 var p = {x:2,y:3}; // 同樣是一個從函數(shù)外無法被訪問的私有變量 // 將其命名為sum也可以。不過為了避免混淆,這里采用其他名稱 function sum_internal(a,b) { return Number(a)+Number(b); } // 只不過是為了使用上面的兩個名稱而隨意設(shè)計的返回值 return function (a,b) { alert("x = "+p.x); return sum_internal(a,b); } })(); console.log(sum(3,4)); // "x = 2" // "y"
上面的代碼可以抽象為下面這種形式的代碼。在利用函數(shù)作用域封裝名稱,以及閉包可以使名稱在函數(shù)調(diào)用結(jié)束后依然存在這兩個特性。這樣信息隱藏得以實現(xiàn)。
(function(){函數(shù)體})();
像上面這樣,當(dāng)場調(diào)用函數(shù)的代碼看起來或許有些奇怪。一般的做法是先在某處聲明函數(shù),之后在需要時調(diào)用。不過這種做法是JavaScript的一種習(xí)慣用法,加以掌握。
匿名函數(shù)的返回值是一個函數(shù),不過即使返回值不是函數(shù),也同樣能采用這一方法。比如返回一個對象字面量以實現(xiàn)信息隱藏的功能。
var obj = (function() { // 從函數(shù)外部無法訪問該名稱 // 實際上,這是一個私有變量 var p = {x:2,y:3}; // 這同樣是一個無法從函數(shù)外部訪問的私有函數(shù) function sum_internal(a,b) { return Number(a+b); } // 只不過為了使用上面的兩個名稱而隨意設(shè)計的返回值 return { sum:function (a,b) { return sum_internal(a,b); }, x:p.x }; })(); alert(obj.sum(3,4)); // 7 alert(obj.x); // 2閉包與類
利用函數(shù)作用域與閉包,可以實現(xiàn)訪問在控制,上一節(jié)中,模塊的函數(shù)在被聲明之后立即就對其調(diào)用,而是用了閉包的類則能夠在生成實例時調(diào)用。即便如此,著厚重那個做法在形式上仍然只是單純的函數(shù)生命。下面是一個通過閉包來對類進行定義的例子
// 用于生成實例的函數(shù) function myclass(x,y) { return {show:function () {alert(x+" | "+y)}}; } var obj = myclass(3,2); obj.show(); // 3 | 2
這里再舉一個具體的例子,一個實現(xiàn)了計數(shù)器功能的類。
這里重申一下:JavaScript的語言特性沒有"類"的概念。但這里的類指的是,實際上將會調(diào)用構(gòu)造函數(shù)的Function對象。此外在強調(diào)對象是通過調(diào)用構(gòu)造函數(shù)生成的時候,會將這些被生成的對象稱作對象實例以示區(qū)別。
表達(dá)式閉包JavaScript有一種自帶的增強功能,稱為支持函數(shù)型程序設(shè)計的表達(dá)式閉包(Expression closure)。
從語法結(jié)構(gòu)上看,表達(dá)式閉包是函數(shù)聲明表達(dá)式的一種省略形式。可以像下面這樣省略只有return的函數(shù)聲明表達(dá)式中的return與{}。
var sum = function (a,b) {return Number(a+b)}; // 可以省略為 var sum = function (a,b) Number(a+b);
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/87034.html
摘要:當(dāng)初看這個解釋有點懵逼,理解成閉包就是函數(shù)中的函數(shù)了。里的閉包最近不滿足于只干前端的活,開始用起了。里的閉包最近在學(xué)習(xí)語言,讓我們來看一下語言里的閉包。在中,閉包特指將函數(shù)作為值返回的情況,被返回的函數(shù)引用了生成它的母函數(shù)中的變量。 本人開始接觸編程是從js開始的,當(dāng)時網(wǎng)上很多人說閉包是難點,各種地方對閉包的解釋也是千奇百怪。如今開始接觸js以外的各種編程語言,發(fā)現(xiàn)不光是js,php、...
摘要:當(dāng)初看這個解釋有點懵逼,理解成閉包就是函數(shù)中的函數(shù)了。里的閉包最近不滿足于只干前端的活,開始用起了。里的閉包最近在學(xué)習(xí)語言,讓我們來看一下語言里的閉包。在中,閉包特指將函數(shù)作為值返回的情況,被返回的函數(shù)引用了生成它的母函數(shù)中的變量。 本人開始接觸編程是從js開始的,當(dāng)時網(wǎng)上很多人說閉包是難點,各種地方對閉包的解釋也是千奇百怪。如今開始接觸js以外的各種編程語言,發(fā)現(xiàn)不光是js,php、...
摘要:注此讀書筆記只記錄本人原先不太理解的內(nèi)容經(jīng)過閱讀你不知道的后的理解。作用域及閉包基礎(chǔ),代碼運行的幕后工作者引擎及編譯器。 注:此讀書筆記只記錄本人原先不太理解的內(nèi)容經(jīng)過閱讀《你不知道的JS》后的理解。 作用域及閉包基礎(chǔ),JS代碼運行的幕后工作者:引擎及編譯器。引擎負(fù)責(zé)JS程序的編譯及執(zhí)行,編譯器負(fù)責(zé)詞法分析和代碼生成。那么作用域就像一個容器,引擎及編譯器都從這里提取東西。 ...
摘要:但閉包的情況不同嵌套函數(shù)的閉包執(zhí)行后,,然后還在被回收閉包會使變量始終保存在內(nèi)存中,如果不當(dāng)使用會增大內(nèi)存消耗。每個函數(shù),不論多深,都可以認(rèn)為是全局的子作用域,可以理解為閉包。 閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應(yīng)用都要依靠閉包實現(xiàn)。 閉包的特性 閉包有三個特性: 1.函數(shù)嵌套函數(shù) 2.函數(shù)內(nèi)部可以引用外部的參數(shù)和變量 3.參數(shù)和變量不會...
摘要:閉包是什么這是一個在面試的過程中出現(xiàn)的概率為以上的問題,也是我們張口就來的問題。文章推薦我們面試中在被問到閉包這個問題是要注意的幾點閉包的延伸,讓面試變得 閉包是什么?這是一個在面試的過程中出現(xiàn)的概率為60%以上的問題,也是我們張口就來的問題。但是我們往往發(fā)現(xiàn),在面試的過程中我們的回答并不那么讓面試官滿意,我們雖然能張口說出一些但是卻不能系統(tǒng)的對這個問題進行回答。面試官希望加入自己團隊...
閱讀 2975·2021-11-24 10:22
閱讀 3045·2021-11-23 10:10
閱讀 1353·2021-09-28 09:35
閱讀 1752·2019-08-29 13:16
閱讀 1395·2019-08-26 13:29
閱讀 2782·2019-08-26 10:27
閱讀 678·2019-08-26 10:09
閱讀 1436·2019-08-23 18:05