摘要:調(diào)用棧意味著中的執(zhí)行環(huán)境棧,激活記錄意味著的激活對(duì)象。此外,所有的函數(shù)是一等公民。換句話說(shuō),自由變量并不存在自身的環(huán)境中,而是周?chē)沫h(huán)境中。值得注意的是,函數(shù)并沒(méi)有用到自由變量。在后面的情形中,我們將綁定對(duì)象稱(chēng)為環(huán)境幀。
原文
ECMA-262-5 in detail. Chapter 3.1. Lexical environments: Common Theory.
簡(jiǎn)介在這一章,我們將討論詞法環(huán)境的細(xì)節(jié)——一種被很多語(yǔ)言應(yīng)用管理靜態(tài)作用域的機(jī)制。為了能夠更好地理解這個(gè)概念,我們也會(huì)討論一些別的——?jiǎng)討B(tài)作用域(ECMAScript中并沒(méi)有直接使用)。我們將看到環(huán)境是如何管理嵌套的代碼結(jié)構(gòu)和閉包。ECMA-262-5規(guī)范介紹了詞法環(huán)境,盡管這是一個(gè)和ECMAScript相獨(dú)立的概念,被應(yīng)用于很多函數(shù)。實(shí)際上,部分和這個(gè)話題相關(guān)的技術(shù)部分,我們已經(jīng)在之前的ES3系列中討論過(guò)了,例如變量和激活對(duì)象,作用域鏈。嚴(yán)格地來(lái)說(shuō),詞法環(huán)境相比ES3中的概念只是更加理論化,更加抽象。但這是一個(gè)ES5的年代,我建議用這些新的定義來(lái)討論和解釋ECMAScript。盡管,更加普遍的概念,例如激活記錄(activation record)(ES3中的激活對(duì)象)的調(diào)用棧(call-stack)(ES中執(zhí)行環(huán)境棧),等等,已經(jīng)在低級(jí)抽象的層面上討論過(guò)了。這一章節(jié)致力于環(huán)境的一般理論,也會(huì)涉及程序語(yǔ)言理論(programming languages theory)的部分。我們將從不同的角度,用不同的語(yǔ)言實(shí)現(xiàn),來(lái)理解為什么詞法作用域是需要的,以及這些結(jié)構(gòu)是如何被創(chuàng)建的。事實(shí)上,如果我們完全理解來(lái)作用域的一般理論,那些ES中的作用域問(wèn)題也就消失了。
一般理論ES中的概念(激活對(duì)象,作用域鏈,詞法環(huán)境)都與作用域的概念相關(guān)。ES中提到的定義是作用域的一種本地實(shí)現(xiàn),及相關(guān)術(shù)語(yǔ)。
作用域作用域是用來(lái)在程序的不同部分中管理變量的可見(jiàn)行和可訪問(wèn)性。一些
封裝的抽象概念(例如命名空間,模塊)都和作用域相關(guān),作用域被用來(lái)使系統(tǒng)更加模塊化以及避免命名變量的沖突。函數(shù)有本地變量,代碼塊有本地變量,作用域封裝了內(nèi)在的數(shù)據(jù),提升了抽象程度。作用域使我們?cè)谝粋€(gè)程序中使用相同的變量,但是代表不同的含義,擁有不同的值。從這個(gè)角度來(lái)說(shuō),作用域是個(gè)閉合的上下文,里面的變量都與值相關(guān)聯(lián)。我們也可以說(shuō),作用域是某個(gè)變量它某個(gè)含義的邏輯邊界。例如,全局變量,局部變量等,都反映了這個(gè)變量的聲明周期。代碼塊和函數(shù)讓我們擁有了一個(gè)主要的作用域的屬性——嵌套其他作用域或者被嵌套。因此,我們可以看到并不是所有的實(shí)現(xiàn)都支持函數(shù)嵌套,同樣不是所有的實(shí)現(xiàn)都提供塊級(jí)作用域。讓我們來(lái)考慮以下的C代碼:
// global "x" int x = 10; void foo() { // local "x" of "foo" function int x = 20; if (true) { // local "x" of if-block int x = 30; printf("%d", x); // 30 } printf("%d", x); // 20 } foo(); printf("%d", x); // 10
它可以被下圖表示
ECMAScript在版本6之前并不支持塊級(jí)作用域
var x = 10; if (true) { var x = 20; console.log(x); // 20 } console.log(x); // 20
ES6標(biāo)準(zhǔn)中let關(guān)鍵字可以創(chuàng)建塊級(jí)變量
let x = 10; if (true) { let x = 20; console.log(x); // 20 } console.log(x); // 10
這個(gè)塊級(jí)作用域可以通過(guò)匿名自調(diào)用函數(shù)來(lái)實(shí)現(xiàn)
var x = 10; if (true) { (function (x) { console.log(x); // 20 })(20); } console.log(x); // 10靜態(tài)(詞法)作用域
在靜態(tài)作用域中,標(biāo)識(shí)符指向最近的詞法環(huán)境。單詞“l(fā)exical”在這個(gè)場(chǎng)合下指的是程序書(shū)寫(xiě)的屬性,詞法上變量出現(xiàn)的源文字,變量被聲明的地方。在那個(gè)作用域中,變量將會(huì)在運(yùn)行時(shí)被解析。單詞“static”意味著決定標(biāo)識(shí)符作用域是在程序的詞法分析(parsing)的過(guò)程中。這也就是說(shuō),在程序啟動(dòng)之前,我們通過(guò)閱讀代碼,就能判斷在哪個(gè)作用域下,變量將被解析。舉個(gè)例子
var x = 10; var y = 20; function foo() { console.log(x, y); } foo(); // 10, 20 function bar() { var y = 30; console.log(x, y); // 10, 30 foo(); // 10, 20 } bar();
在這個(gè)例子中,變量x在全局變量中被定義——意味著,運(yùn)行時(shí),它也將在全局對(duì)象中被解析。變量y有兩個(gè)定義,我們說(shuō)過(guò),考慮擁有變量最近的詞法作用域。變量自身所在的作用域擁有最高的優(yōu)先級(jí)。因此,在bar函數(shù)中,變量y被解析為30。bar函數(shù)中局部變量y覆蓋來(lái)同名的全局變量y。但是,同名變量y在foo函數(shù)中依然被解析為20,即使它在bar函數(shù)的內(nèi)部被調(diào)用,而且在bar函數(shù)內(nèi)部還有變量y。變量的解析和環(huán)境的調(diào)用是相互獨(dú)立的(in this case bar is a caller of foo, and foo is a callee)。因?yàn)閒oo函數(shù)被定義的位置,最近的含有變量y的詞法環(huán)境就是全局環(huán)境。如今,靜態(tài)作用域已經(jīng)被很多語(yǔ)言應(yīng)用:C, Java, ECMAScript, Python, Ruby, Lua等等。
動(dòng)態(tài)作用域動(dòng)態(tài)作用域并不在詞法環(huán)境中解析變量,而是動(dòng)態(tài)形成變量棧。每當(dāng)碰到變量聲明,就把變量放進(jìn)棧中。變量的聲明周期結(jié)束時(shí),將變量從棧中彈出。來(lái)看一段偽代碼。
// *pseudo* code - with dynamic scope y = 20; procedure foo() print(y) end // on the stack of the "y" name // currently only one value 20 // {y: [20]} foo() // 20, OK procedure bar() // and now on the stack there // are two "y" values: {y: [20, 30]}; // the first found (from the top) is taken y = 30 // therefore: foo() // 30!, not 20 end bar()
環(huán)境的調(diào)用影響了變量的解析。[譯者注:不是重點(diǎn)不譯了]
名稱(chēng)綁定在高級(jí)語(yǔ)言中,我們不在操作地址,這個(gè)地址指向內(nèi)存中的數(shù)據(jù),我們直接使用變量名來(lái)指代那些數(shù)據(jù)。名稱(chēng)綁定是標(biāo)識(shí)符和對(duì)象的關(guān)聯(lián)。一個(gè)標(biāo)識(shí)符可以綁定或解綁。如果標(biāo)識(shí)符被綁定了個(gè)對(duì)象,那么它就指向這個(gè)對(duì)象。
重新綁定// bind "foo" to {x: 10} object
var foo = {x: 10};
console.log(foo.x); // 10
// bind "bar" to the same object
// as "foo" identifier is bound
var bar = foo;
console.log(foo === bar); // true
console.log(bar.x); // OK, also 10
// and now rebind "foo"
// to the new object
foo = {x: 20};
console.log(foo.x); // 20
// and "bar" still points
// to the old object
console.log(bar.x); // 10
console.log(foo === bar); // false
// bind an array to the "foo" identifier var foo = [1, 2, 3]; // and here is a *mutation* of // the array object contents foo.push(4); console.log(foo); // 1,2,3,4 // also mutations foo[4] = 5; foo[0] = 0; console.log(foo); // 0,2,3,4,5環(huán)境
在這一部分,我們將提到詞法作用域?qū)崿F(xiàn)的技術(shù)。我們將操作更抽象的實(shí)體,討論詞法作用域,在以后的解釋中,我們將用環(huán)境而不是作用域,因?yàn)镋S5中也是這個(gè)術(shù)語(yǔ),全局環(huán)境,函數(shù)的本地環(huán)境等等。正如我們提到的,環(huán)境說(shuō)明了表達(dá)式中標(biāo)識(shí)符的含義。ECMAScript用調(diào)用棧(call-stack)來(lái)管理函數(shù)的執(zhí)行。來(lái)考慮一些通用的模型來(lái)保存變量。一些事情很有趣,有閉包的系統(tǒng)和沒(méi)有閉包的系統(tǒng)。
激活記錄模型如果沒(méi)有一等函數(shù),或者不允許內(nèi)部函數(shù),最簡(jiǎn)單存儲(chǔ)本地變量的方式就是調(diào)用棧本身。一個(gè)特殊的調(diào)用棧的數(shù)據(jù)結(jié)構(gòu)叫做激活記錄(activation record),被用來(lái)保存環(huán)境綁定。有時(shí)候也叫調(diào)用棧幀(call-stack frame)。每當(dāng)函數(shù)被調(diào)用,一個(gè)激活記錄(包含參數(shù)和本地變量)被壓入棧中。因此,當(dāng)函數(shù)調(diào)用其他函數(shù),另一個(gè)棧幀被壓入棧中。當(dāng)上下文結(jié)束來(lái),激活記錄從棧中彈出,意味這所有本地變量被銷(xiāo)毀。這個(gè)模型在c語(yǔ)言中被使用。
例如
void foo(int x) { int y = 20; bar(30); } void bar(x) { int z = 40; } foo(10);
調(diào)用棧會(huì)有如下變化
callStack = []; // "foo" function activation // record is pushed onto the stack callStack.push({ x: 10, y: 20 }); // "bar" function activation // record is pushed onto the stack callStack.push({ x: 30, z: 40 }); // callStack at the moment of // the "bar" activation console.log(callStack); // [{x: 10, y: 20}, {x: 30, z: 40}] // "bar" function ends callStack.pop(); // "foo" function ends callStack.pop();
當(dāng)bar函數(shù)被調(diào)用時(shí)
很多相似的函數(shù)執(zhí)行的邏輯方法在ECMAScript被使用。然后,有些很重要的不同。調(diào)用棧意味著ES中的執(zhí)行環(huán)境棧,激活記錄意味著ES3的激活對(duì)象。和C不同的是,ECMAScript不會(huì)從內(nèi)存中移除激活對(duì)象如果有個(gè)閉包。當(dāng)這個(gè)閉包是個(gè)內(nèi)部函數(shù),是用來(lái)外部函數(shù)中創(chuàng)建的變量,然后這個(gè)內(nèi)部函數(shù)被返回到了外面。這就意味著激活對(duì)象不應(yīng)該存在棧中,而是堆中(動(dòng)態(tài)分配內(nèi)存)。它會(huì)一直被保存,當(dāng)閉包的引用使用激活對(duì)象中的變量。更重要的是,不僅是一個(gè)激活對(duì)象被保存,如果需要,所有的父級(jí)的激活對(duì)象。
var bar = (function foo() { var x = 10; var y = 20; return function bar() { return x + y; }; })(); bar(); // 30
如果foo函數(shù)創(chuàng)建了一個(gè)閉包,即使foo執(zhí)行結(jié)束了,它的幀不會(huì)從內(nèi)存中移除,因?yàn)殚]包中有它的引用。
環(huán)境幀模型和c不同,ECMAScript含有內(nèi)部函數(shù)和閉包。此外,所有的函數(shù)是一等公民。
一等函數(shù)一等函數(shù)被當(dāng)作普通的對(duì)象,可以被作為參數(shù),可以作為返回值。一個(gè)簡(jiǎn)單的例子
// create a function expression // dynamically at runtime and // bind it to "foo" identifier var foo = function () { console.log("foo"); }; // pass it to another function, // which in turn is also created // at runtime and called immediately // right after the creation; result // of this function is again bound // to the "foo" identifier foo = (function (funArg) { // activate the "foo" function funArg(); // "foo" // and return it back as a value return funArg; })(foo);函數(shù)參數(shù)和高階函數(shù)
當(dāng)一個(gè)函數(shù)被作為參數(shù),稱(chēng)之為“funarg”-- functional argument的縮寫(xiě)。將函數(shù)作為參數(shù)的函數(shù)稱(chēng)為高階函數(shù)(higher-order function),和數(shù)學(xué)上的算子概念相似。
自由變量自由變量是函數(shù)中使用的變量,既不是函數(shù)的參數(shù),也不是函數(shù)的本地變量。換句話說(shuō),自由變量并不存在自身的環(huán)境中,而是周?chē)沫h(huán)境中。
// Global environment (GE) var x = 10; function foo(y) { // environment of "foo" function (E1) var z = 30; function bar(q) { // environment of "bar" function (E2) return x + y + z + q; } // return "bar" to the outside return bar; } var bar = foo(20); bar(40); // 100
在這個(gè)例子中,我們有三個(gè)環(huán)境:GE,E1和E2,分別對(duì)應(yīng)于全局對(duì)象,foo函數(shù)和bar函數(shù)。因此,對(duì)于bar函數(shù)來(lái)說(shuō),變量x,y,z是自由變量,它們既不是函數(shù)參數(shù),也不是bar的本地變量。值得注意的是,foo函數(shù)并沒(méi)有用到自由變量。但是變量x在內(nèi)部的bar函數(shù)中被使用,另外在foo函數(shù)運(yùn)行的過(guò)程中創(chuàng)建bar函數(shù),盡管如此,還是保存了父環(huán)境的綁定,為了能夠?qū)的綁定傳遞給內(nèi)部嵌套的函數(shù)。正確的并期望出現(xiàn)100,當(dāng)執(zhí)行bar函數(shù)后,這意味著bar函數(shù)記住了foo函數(shù)激活時(shí)的環(huán)境,即使foo函數(shù)已經(jīng)結(jié)束了。這就是與基于棧的激活記錄模型的不同。當(dāng)我們?cè)试S內(nèi)部函數(shù),同時(shí)希望靜態(tài)詞法作用域,同時(shí)將函數(shù)作為一等公民,我們應(yīng)該保存函數(shù)需要的所有自由變量,當(dāng)函數(shù)被創(chuàng)建的時(shí)候。
環(huán)境定義最直接最簡(jiǎn)單的方法去實(shí)現(xiàn)這樣的算法是保存完整的父環(huán)境,在這個(gè)父環(huán)境中,函數(shù)被創(chuàng)建。然后,在函數(shù)自己執(zhí)行時(shí),我們創(chuàng)建自己的環(huán)境,保存自己的本地變量和參數(shù),然后設(shè)置我們的外部環(huán)境為之前保存的那個(gè),為了能夠在那找到自由變量。我們用術(shù)語(yǔ)環(huán)境指代多帶帶綁定對(duì)象,或者所有的綁定對(duì)象依據(jù)嵌套的深度。在后面的情形中,我們將綁定對(duì)象稱(chēng)為環(huán)境幀。一個(gè)環(huán)境是一些列幀,每個(gè)幀是個(gè)記錄綁定,將變量名和值關(guān)聯(lián)起來(lái)。我們用抽象的概念記錄,而沒(méi)有具體說(shuō)明它的實(shí)現(xiàn)結(jié)構(gòu),它可能是堆中的哈希表,棧內(nèi)存,虛擬機(jī)注冊(cè)(registers of the virtual machine)等。例如,例子中,環(huán)境E2有三個(gè)幀:自己的bar,foo的和全局的。環(huán)境E1有兩個(gè)幀:foo自己的,和全局的。全局環(huán)境GE只有一個(gè)幀:全局。
一個(gè)幀中任何變量至多只有一個(gè)綁定。每個(gè)幀都有個(gè)指針,指向圍繞它的環(huán)境。全局幀的外部環(huán)境的鏈接是null。變量相對(duì)于環(huán)境的值是在包含該變量的綁定的環(huán)境中的第一幀中的變量的綁定給出的值(The value of a variable with respect to an environment is the value given by the binding of the variable in the first frame in the environment that contains a binding for that variable)(源自谷歌翻譯)。
var x = 10; (function foo(y) { // use of free-bound "x" variable console.log(x); // own-bound "y" variable console.log(y); // 20 // and free-unbound variable "z" console.log(z); // ReferenceError: "z" is not defined })(20);
一系列的環(huán)境幀形成了我們所稱(chēng)的作用域鏈。一個(gè)環(huán)境可能會(huì)包裹多個(gè)內(nèi)部環(huán)境。
// Global environment (GE) var x = 10; function foo() { // "foo" environment (E1) var x = 20; var y = 30; console.log(x + y); } function bar() { // "bar" environment (E2) var z = 40; console.log(x + z); }
偽代碼
// global GE = { x: 10, outer: null }; // foo E1 = { x: 20, y: 30, outer: GE }; // bar E2 = { z: 40, outer: GE };
變量x相對(duì)于環(huán)境E1的綁定遮蔽了同名變量在全局環(huán)境中的綁定。
函數(shù)創(chuàng)建和應(yīng)用法則一個(gè)函數(shù)是相對(duì)于給定的環(huán)境創(chuàng)建的。這導(dǎo)致函數(shù)對(duì)象是由函數(shù)本身的代碼(函數(shù)體)和指向創(chuàng)建函數(shù)本身的環(huán)境的指針構(gòu)成。
// global "x" var x = 10; // function "foo" is created relatively // to the global environment function foo(y) { var z = 30; console.log(x + y + z); }
相當(dāng)于偽代碼
// create "foo" function foo = functionObject { code: "console.log(x + y + z);" environment: {x: 10, outer: null} };
注意,函數(shù)指向它的環(huán)境,其中一個(gè)環(huán)境與函數(shù)自身相綁定。一個(gè)函數(shù)被調(diào)用,一系列的參數(shù)構(gòu)成了新的幀,在這個(gè)幀中綁定了本地變量,然后在創(chuàng)建的新的環(huán)境中執(zhí)行函數(shù)體。
// function "foo" is applied // to the argument 20 foo(20);
與之對(duì)應(yīng)的偽代碼
// create a new frame with formal // parameters and local variables fooFrame = { y: 20, z: 30, outer: foo.environment }; // and evaluate the code // of the "foo" function execute(foo.code, fooFrame); // 60閉包
閉包是由函數(shù)代碼和創(chuàng)建函數(shù)的環(huán)境構(gòu)成的。閉包被發(fā)明用來(lái)解決函數(shù)參數(shù)的問(wèn)題。
函數(shù)參數(shù)問(wèn)題當(dāng)返回一個(gè)函數(shù)到外面,這個(gè)函數(shù)使用了創(chuàng)建它的父環(huán)境中的自由變量怎么辦?
(function (x) { return function (y) { return x + y; }; })(10)(20); // 30
正如我們所知,詞法作用域在堆中保存封閉的幀。這是問(wèn)題的關(guān)鍵。像c一樣用棧來(lái)保存綁定是不可行的。被保存的代碼塊和環(huán)境就是個(gè)閉包。當(dāng)我們把函數(shù)作為參數(shù)傳遞到其他函數(shù)中,這個(gè)函數(shù)參數(shù)中的自由變量是如何被解析的,是在函數(shù)定義的作用域中,還是函數(shù)執(zhí)行的作用域中?
var x = 10; (function (funArg) { var x = 20; funArg(); // 10, not 20 })(function () { // create and pass a funarg console.log(x); });
回答這個(gè)問(wèn)題的關(guān)鍵就是詞法作用域。
組合環(huán)境幀模型很明顯,如果一些變量不被內(nèi)部函數(shù)需要,就沒(méi)有必要保存它們。
// global environment var x = 10; var y = 20; function foo(z) { // environment of "foo" function var q = 40; function bar() { // environment of "bar" function return x + z; } return bar; } // creation of "bar" var bar = foo(30); // applying of "bar" bar();
沒(méi)有函數(shù)使用變量y,因此,我們不需要在foo和bar的閉包中保存它。全局變量x沒(méi)有在函數(shù)foo中使用,但是我們?nèi)詰?yīng)該保存它,因?yàn)楦畹膬?nèi)部函數(shù)bar需要它。
bar = closure { code: <...>, environment: { x: 10, z: 30, } }
[譯者注:后面是python等其他語(yǔ)言閉包的例子,不譯了]
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/89602.html
摘要:嵌套函數(shù)在一個(gè)函數(shù)中創(chuàng)建另一個(gè)函數(shù),稱(chēng)為嵌套。這在很容易實(shí)現(xiàn)嵌套函數(shù)可以訪問(wèn)外部變量,幫助我們很方便地返回組合后的全名。更有趣的是,嵌套函數(shù)可以作為一個(gè)新對(duì)象的屬性或者自己本身被。 來(lái)源于 現(xiàn)代JavaScript教程閉包章節(jié)中文翻譯計(jì)劃本文很清晰地解釋了閉包是什么,以及閉包如何產(chǎn)生,相信你看完也會(huì)有所收獲 關(guān)鍵字Closure 閉包Lexical Environment 詞法環(huán)境En...
摘要:全局環(huán)境是作用域鏈的終點(diǎn)。環(huán)境記錄類(lèi)型定義了兩種環(huán)境記錄類(lèi)型聲明式環(huán)境記錄和對(duì)象環(huán)境記錄聲明式環(huán)境記錄聲明式環(huán)境記錄是用來(lái)處理函數(shù)作用域中出現(xiàn)的變量,函數(shù),形參等。變量環(huán)境變量環(huán)境就是存儲(chǔ)上下文中的變量和函數(shù)的。解析的過(guò)程如下 原文 ECMA-262-5 in detail. Chapter 3.2. Lexical environments: ECMAScript implement...
摘要:你覺(jué)得下列代碼中,哪些操作能成功人肉判斷一下,不要放進(jìn)瀏覽器里執(zhí)行。故對(duì)于解析而言,得到的為上述所有的屬性在下均為。那么又有什么玄機(jī)呢的操作可理解為對(duì)于,調(diào)用其內(nèi)部的方法。幾乎所有的都是不可刪除的。 你覺(jué)得下列代碼中,哪些delete操作能成功?人肉判斷一下,不要放進(jìn)瀏覽器里執(zhí)行。 // #1 a = hello world; delete a; // #2 var b = hel...
摘要:這意味著引擎在源代碼中聲明它的位置計(jì)算其值之前,你無(wú)法訪問(wèn)該變量。因此它們同樣會(huì)受到時(shí)間死區(qū)的影響。正確理解提升機(jī)制將有助于避免因變量提升而產(chǎn)生的任何未來(lái)錯(cuò)誤和混亂。 開(kāi)始執(zhí)行腳本時(shí),執(zhí)行腳本的第一步是編譯代碼,然后再開(kāi)始執(zhí)行代碼,如圖 showImg(https://segmentfault.com/img/bVbnPrh?w=1233&h=141); 另外,在編譯優(yōu)化方面來(lái)說(shuō),最開(kāi)...
摘要:不過(guò)到底是怎麼保留的另外為什麼一個(gè)閉包可以一直使用區(qū)域變數(shù),即便這些變數(shù)在該內(nèi)已經(jīng)不存在了為了解開(kāi)閉包的神秘面紗,我們將要假裝沒(méi)有閉包這東西而且也不能夠用嵌套來(lái)重新實(shí)作閉包。 原文出處: 連結(jié) 話說(shuō)網(wǎng)路上有很多文章在探討閉包(Closures)時(shí)大多都是簡(jiǎn)單的帶過(guò)。大多的都將閉包的定義濃縮成一句簡(jiǎn)單的解釋?zhuān)蔷褪且粋€(gè)閉包是一個(gè)函數(shù)能夠保留其建立時(shí)的執(zhí)行環(huán)境。不過(guò)到底是怎麼保留的? 另外...
閱讀 2328·2021-09-22 15:25
閱讀 3666·2019-08-30 12:48
閱讀 2289·2019-08-30 11:25
閱讀 2398·2019-08-30 11:05
閱讀 795·2019-08-29 17:28
閱讀 3335·2019-08-26 12:16
閱讀 2662·2019-08-26 11:31
閱讀 1851·2019-08-23 17:08