摘要:通過(guò)查看的文檔可以發(fā)現(xiàn)整個(gè)分為個(gè)階段定時(shí)器相關(guān)任務(wù),中我們關(guān)注的是它會(huì)執(zhí)行和中到期的回調(diào)執(zhí)行某些系統(tǒng)操作的回調(diào)內(nèi)部使用執(zhí)行,一定條件下會(huì)在這個(gè)階段阻塞住執(zhí)行的回調(diào)如果或者關(guān)閉了,就會(huì)在這個(gè)階段觸發(fā)事件,執(zhí)行事件的回調(diào)的代碼在文件中。
這次我們就不要那么多前戲,直奔主題,我們的龍門陣正式開(kāi)始。
開(kāi)局一道題,內(nèi)容全靠吹。(此處應(yīng)有滑稽)
// 文件名: index.js // 我們盡量模擬所有的異步場(chǎng)景,包括 timers、Promise、nextTick等等 setTimeout(() => { console.log("timeout 1"); }, 1); process.nextTick(() => { console.log("nextTick 1"); }); fs.readFile("./index.js", (err, data) => { if(err) return; console.log("I/O callback"); process.nextTick(() => { console.log("nextTick 2"); }); }); setImmediate(() => { console.log("immediate 1"); process.nextTick(() => { console.log("nextTick 3"); }); }); setTimeout(() => { console.log("timeout 2"); process.nextTick(() => { console.log("nextTick 4"); }); }, 100); new Promise((resolve, reject) => { console.log("promise run"); process.nextTick(() => { console.log("nextTick 5"); }); resolve("promise then"); setImmediate(() => { console.log("immediate 2"); }); }).then(res => { console.log(res); });
note: 上面的代碼執(zhí)行環(huán)境是 node v10.7.0,瀏覽器的事件循環(huán)和 node 還是有一點(diǎn)區(qū)別的,有興趣的可以自己找資料看一看。
好了,上面的代碼涉及到定時(shí)器、nextTick、Promise、setImmediate 和 I/O 操作。頭皮有點(diǎn)小發(fā)麻哈,大家想好答案了么?檢查一下吧!
promise run nextTick 1 nextTick 5 promise then timeout 1 immediate 1 immediate 2 nextTick 3 I/O callback nextTick 2 timeout 2 nextTick 4
怎么樣?跟自己想的一樣么?不一樣的話,就聽(tīng)我慢慢道來(lái)。
event loop在 Node.js 中,event loop 是基于 libuv 的。通過(guò)查看 libuv 的文檔可以發(fā)現(xiàn)整個(gè) event loop 分為 6 個(gè)階段:
timers: 定時(shí)器相關(guān)任務(wù),node 中我們關(guān)注的是它會(huì)執(zhí)行 setTimeout() 和 setInterval() 中到期的回調(diào)
pending callbacks: 執(zhí)行某些系統(tǒng)操作的回調(diào)
idle, prepare: 內(nèi)部使用
poll: 執(zhí)行 I/O callback,一定條件下會(huì)在這個(gè)階段阻塞住
check: 執(zhí)行 setImmediate 的回調(diào)
close callbacks: 如果 socket 或者 handle 關(guān)閉了,就會(huì)在這個(gè)階段觸發(fā) close 事件,執(zhí)行 close 事件的回調(diào)
┌───────────────────────────┐ ┌─>│ timers │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ idle, prepare │ │ └─────────────┬─────────────┘ ┌───────────────┐ │ ┌─────────────┴─────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └─────────────┬─────────────┘ │ data, etc. │ │ ┌─────────────┴─────────────┐ └───────────────┘ │ │ check │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
event loop 的代碼在文件 deps/uv/src/unix/core.c 中。
int uv_run(uv_loop_t* loop, uv_run_mode mode) { int timeout; int r; int ran_pending; // 確定 event loop 是否繼續(xù) r = uv__loop_alive(loop); if (!r) uv__update_time(loop); while (r != 0 && loop->stop_flag == 0) { uv__update_time(loop); // 更新時(shí)間 uv__run_timers(loop); // timers 階段 ran_pending = uv__run_pending(loop); // pending callbacks 階段 uv__run_idle(loop); // idle 階段 uv__run_prepare(loop); // prepare 階段 timeout = 0; // 設(shè)置 poll 階段的超時(shí)時(shí)間,有以下情況超時(shí)時(shí)間設(shè)為 0,此時(shí) poll 不會(huì)阻塞 // 1. stop_flag 不為 0 // 2. 沒(méi)有活躍的 handles 和 request // 3. idle、pending callback、close 階段 handle 隊(duì)列不為空 // 否則的話會(huì)將超時(shí)時(shí)間設(shè)置成距離當(dāng)前時(shí)間最近的 timer 的時(shí)間 if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT) timeout = uv_backend_timeout(loop); // poll 階段 uv__io_poll(loop, timeout); // check 階段 uv__run_check(loop); // close 階段 uv__run_closing_handles(loop); if (mode == UV_RUN_ONCE) { uv__update_time(loop); uv__run_timers(loop); } r = uv__loop_alive(loop); if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT) break; } if (loop->stop_flag != 0) loop->stop_flag = 0; return r; }注冊(cè)加觸發(fā)
這一小節(jié)我們主要看看 Node 如何將我們寫(xiě)的定時(shí)器等等注冊(cè)到 event loop 中去并執(zhí)行的。
以 setTimeout 為例,首先我們進(jìn)到了 timers.js 這個(gè)文件中,找到了 setTimeout 函數(shù),我們主要關(guān)注這么兩句:
function setTimeout(callback, after, arg1, arg2, arg3) { // ... const timeout = new Timeout(callback, after, args, false); active(timeout); return timeout; }
我們看到它 new 了一個(gè) Timeout 類,我們順著這條線索找到了 Timeout 的構(gòu)造函數(shù):
function Timeout(callback, after, args, isRepeat) { // ... this._onTimeout = callback; // ... }
我們主要關(guān)注這一句,Node 將回調(diào)掛載到了 _onTimeout 這個(gè)屬性上。那么這個(gè)回調(diào)是在什么時(shí)候執(zhí)行的呢?我們?nèi)炙岩幌?_onTimeout(),我們可以發(fā)現(xiàn)是一個(gè)叫做 ontimeout 的方法執(zhí)行了回調(diào),好了,我們開(kāi)始順藤摸瓜,可以找到這么一條調(diào)用路徑 processTimers -> listOnTimeout -> tryOnTimeout -> ontimeout -> _onTimeout。
最后的最后,我們?cè)谖募念^部發(fā)現(xiàn)了這么幾行代碼:
const { getLibuvNow, setupTimers, scheduleTimer, toggleTimerRef, immediateInfo, toggleImmediateRef } = internalBinding("timers"); setupTimers(processImmediate, processTimers);
我們一看,setupTimers 是從 internalBinding("timers") 獲取的,我們?nèi)タ匆幌?internalBinding 就知道這就是 js 代碼和內(nèi)建模塊關(guān)聯(lián)的地方了。于是,我們順著這條線索往下找,我們?nèi)?src 目錄下去找叫 timers 的文件,果不其然,我們找到一個(gè)叫 timers.cc 的文件,同時(shí),找到了一個(gè)叫 SetupTimers 的函數(shù)。
void SetupTimers(const FunctionCallbackInfo& args) { CHECK(args[0]->IsFunction()); CHECK(args[1]->IsFunction()); auto env = Environment::GetCurrent(args); env->set_immediate_callback_function(args[0].As ()); env->set_timers_callback_function(args[1].As ()); }
上面的 args[1] 就是我們傳遞的 processTimers,在這個(gè)函數(shù)中我們其實(shí)就完成了 processTimers 的注冊(cè),它成功的注冊(cè)到了 node 中。
那是如何觸發(fā)的回調(diào)呢?這里我們首先先看到 event loop 代碼中的 timers 階段執(zhí)行的函數(shù),然后跟進(jìn)去:
void uv__run_timers(uv_loop_t* loop) { struct heap_node* heap_node; uv_timer_t* handle; for (;;) { heap_node = heap_min(timer_heap(loop)); if (heap_node == NULL) break; handle = container_of(heap_node, uv_timer_t, heap_node); if (handle->timeout > loop->time) break; uv_timer_stop(handle); uv_timer_again(handle); handle->timer_cb(handle); } }
這段代碼我們將我們的目光放在 handle->timer_cb(handle) 這一行,這個(gè)函數(shù)是在哪兒定義的呢?我們?nèi)炙岩幌?timer_cb 發(fā)現(xiàn) uv_timer_start 中有這么一行代碼:
handle->timer_cb = cb;
所以我們知道是調(diào)用了 uv_timer_start 將回調(diào)函數(shù)掛載到了 handle 上。那么 cb 又是什么呢?其實(shí)你沿著代碼上去找就能發(fā)現(xiàn)其實(shí) cb 就是 timers_callback_function,眼熟對(duì)么?這就是我們上面注冊(cè)進(jìn)來(lái)觸發(fā)回調(diào)的函數(shù) processTimers。
恍然大悟,原來(lái)是這么觸發(fā)的回調(diào),現(xiàn)在還有個(gè)問(wèn)題,誰(shuí)去調(diào)用的 uv_timer_start 呢?這個(gè)問(wèn)題就簡(jiǎn)單了,我們通過(guò)源碼可以知道是 ScheduleTimer 這個(gè)函數(shù)調(diào)用了,是不是感覺(jué)很熟悉,對(duì),這個(gè)函數(shù)就是我們通過(guò) internalBinding 引進(jìn)來(lái)的 scheduleTimer 函數(shù)。
在這個(gè)地方就有點(diǎn)不一樣了?,F(xiàn)在最新的 tag 版本和 github 上 node 最新的代碼是有區(qū)別的,在一次 pr 中,將 timer_wrap.cc 重構(gòu)成了 timers.cc,并且移除了 TimerWrap 類,再說(shuō)下面的區(qū)別之前,先補(bǔ)充一下 timer 對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu):
// 這是在有 TimeWrap 的版本 // 對(duì)應(yīng)的時(shí)間后面是一個(gè) timer 鏈表 refedLists = { 1000: TimeWrap._list(TimersList(item<->item<->item<->item)), 2000: TimeWrap._list(TimersList(item<->item<->item<->item)), }; // 這是 scheduleTimer 的版本 refedLists = { 1000: TimersList(item<->item<->item<->item), 2000: TimersList(item<->item<->item<->item), };
在 TimeWrap 的版本里,js 是通過(guò)調(diào)用實(shí)例化后的 start() 函數(shù)去調(diào)用了 uv_timer_start。
而 scheduleTimer 版本是注冊(cè)定時(shí)器的時(shí)候通過(guò)比較哪個(gè)定時(shí)器是最近要執(zhí)行的,從而將對(duì)應(yīng)時(shí)間的 timerList 注冊(cè)到 uv_timer 中去。
那么,為什么要這么改呢?是為了讓定時(shí)器和 Immediate 擁有更相似的行為,也就是將單個(gè) uv_timer_t handle 存在 Environment 上(Immediate 是有一個(gè) ImmediateQueue,這也是個(gè)鏈表)。
這里就只說(shuō)了一個(gè) timer,其他的大家就自己去看看吧,順著這個(gè)思路大家肯定會(huì)有所收獲的。
事件循環(huán)流程在加載 node 的時(shí)候,將 setTimeout、setInterval 的回調(diào)注冊(cè)到 timerList,將 Promise.resolve 等 microTask 的回調(diào)注冊(cè)到 microTasks,將 setImmediate 注冊(cè)到 immediateQueue 中,將 process.nextTick 注冊(cè)到 nextTickQueue 中。
當(dāng)我們開(kāi)始 event loop 的時(shí)候,首先進(jìn)入 timers 階段(我們只看跟我們上面說(shuō)的相關(guān)的階段),然后就判斷 timerList 的時(shí)間是否到期了,如果到期了就執(zhí)行,沒(méi)有就下一個(gè)階段(其實(shí)還有 nextTick,等下再說(shuō))。
接下來(lái)我們說(shuō) poll 階段,在這個(gè)階段,我們先計(jì)算需要在這個(gè)階段阻塞輪詢的時(shí)間(簡(jiǎn)單點(diǎn)就是下個(gè) timer 的時(shí)間),然后等待監(jiān)聽(tīng)的事件。
下個(gè)階段是 check 階段,對(duì)應(yīng)的是 immediate,當(dāng)有 immediateQueue 的時(shí)候就會(huì)跳過(guò) poll 直接到 check 階段執(zhí)行 setImmediate 的回調(diào)。
那有同學(xué)就要問(wèn)了,nextTick 和 microTasks 去哪兒了?。縿e慌,聽(tīng)我慢慢道來(lái)。
process.nextTick 和 microTasks現(xiàn)在我們有了剛剛找 timer 的經(jīng)驗(yàn),我們繼續(xù)去看看 nextTick 是怎么執(zhí)行的。
經(jīng)過(guò)排查我們能找到一個(gè)叫 _tickCallback 的函數(shù),它不斷的從 nextTickQueue 中獲取 nextTick 的回調(diào)執(zhí)行。
function _tickCallback() { let tock; do { while (tock = queue.shift()) { // ... const callback = tock.callback; if (tock.args === undefined) callback(); else Reflect.apply(callback, undefined, tock.args); emitAfter(asyncId); } tickInfo[kHasScheduled] = 0; runMicrotasks(); } while (!queue.isEmpty() || emitPromiseRejectionWarnings()); tickInfo[kHasPromiseRejections] = 0; }
我們看到了什么?在將 nextTick 的回調(diào)執(zhí)行完之后,它執(zhí)行了 runMicrotasks。一切都真相大白了,microTasks 的執(zhí)行時(shí)機(jī)是當(dāng)執(zhí)行完所有的 nextTick 的回調(diào)之后。那 nextTick 又是在什么時(shí)候執(zhí)行的呢?
這就需要我們?nèi)フ?C++ 的代碼了,在 bootstrapper.cc 里找到了 BOOTSTRAP_METHOD(_setupNextTick, SetupNextTick),所以我們就要去找 SetupNextTick 函數(shù)。
void SetupNextTick(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); // ... env->set_tick_callback_function(args[0].As ()); // ... }
我們關(guān)注這一句,是不是很熟啊,跟上面 timer 一樣是吧,我們將 __tickCallback 注冊(cè)到了 node,在 C++ 中通過(guò) tick_callback_function 來(lái)調(diào)用這個(gè)函數(shù)。
我們通過(guò)查看源碼可以發(fā)現(xiàn)是 InternalCallbackScope 這個(gè)類調(diào)用 Close 函數(shù)的時(shí)候就會(huì)觸發(fā) nextTixk 執(zhí)行。
void InternalCallbackScope::Close() { if (closed_) return; closed_ = true; HandleScope handle_scope(env_->isolate()); // ... if (!tick_info->has_scheduled()) { env_->isolate()->RunMicrotasks(); } // ... if (!tick_info->has_scheduled() && !tick_info->has_promise_rejections()) { return; } // ... if (env_->tick_callback_function()->Call(process, 0, nullptr).IsEmpty()) { failed_ = true; } }
可能有同學(xué)有疑問(wèn)了,為啥在執(zhí)行 nextTick 上面還有 RunMicrotasks 呢?其實(shí)這是對(duì) event loop 的優(yōu)化,假如沒(méi)有 process.nextTick 就直接從 node 里面調(diào)用 RunMicrotasks 加快速度。
現(xiàn)在在 node.cc 里我們找到了調(diào)用 Close 的地方:
MaybeLocalInternalMakeCallback(Environment* env, Local
而 InternalMakeCallback() 則是在 async_wrap.cc 的 AsyncWrap::MakeCallback() 中被調(diào)用。
找了半天,只找到了 setImmediate 注冊(cè)時(shí),注冊(cè)函數(shù)執(zhí)行回調(diào)運(yùn)行了這個(gè)函數(shù),沒(méi)有找到 timer 的。之前因?yàn)槭褂玫?TimeWrap,TimeWrap 繼承了 AsyncWrap,在執(zhí)行回調(diào)的時(shí)候調(diào)用了 MakeCallback(),問(wèn)題是現(xiàn)在移除了 TimeWrap,那是怎么調(diào)用的呢?我們會(huì)到 js 代碼,發(fā)現(xiàn)了這樣的代碼:
const { _tickCallback: runNextTicks } = process; function processTimers(now) { runNextTicks(); }
一切都明了了,在移除了 TimeWrap 之后,將 _tickCallback 放到了這里執(zhí)行,所以我們剛剛在 C++ 里找不到。
其實(shí),每一個(gè)階段執(zhí)行完之后,都會(huì)去執(zhí)行 _tickCallback ,只是方式可能有點(diǎn)不同。
答案解析好了,剛剛了解了關(guān)于 event loop 的一些情況,我們?cè)賮?lái)看看文章開(kāi)頭的那段代碼,我們一起來(lái)分析。
第一步首先運(yùn)行 Promise 里的代碼,輸出了 promise run,然后 promise.resolve 將 then 放入 microTasks。
第二步這里要提到的一點(diǎn)是 nextTick 在注冊(cè)之后,bootstrap 構(gòu)建結(jié)束后運(yùn)行SetupNextTick函數(shù),這時(shí)候就會(huì)清空 nextTickQueue 和 MicroTasks,所以輸出 nextTick 1、nextTick 5、promise then。
第三步在 bootstrap 之后便進(jìn)入了 event loop,第一個(gè)階段 timers,這時(shí) timeout 1 定時(shí)器時(shí)間到期,執(zhí)行回調(diào)輸出 timeout 1,timerList 沒(méi)有其他定時(shí)器了,去清空 nextTickQueue 和 MicroTasks,沒(méi)有任務(wù),這時(shí)繼續(xù)下階段,這時(shí)候有 immediate,所以跳過(guò) poll,進(jìn)入 check,執(zhí)行 immediate 回調(diào),輸出 immediate 1 和 immediate 2,并將 nextTick 3 推入 nextTickQueue,階段完成 immediateQueue 沒(méi)有需要處理的東西了,就去清空 nextTickQueue 和 MicroTasks 輸出 nextTick 3。
第四步在這一輪,文件讀取完成,并且 timers 沒(méi)到期,進(jìn)入 poll 階段,超時(shí)時(shí)間設(shè)置為 timeout 2 的時(shí)間,執(zhí)行回調(diào)輸出 I/O callback,并且向 nextTickQueue 推入 nextTick 2。阻塞過(guò)程中沒(méi)有其他的 I/O 事件,去清空 nextTickQueue 和 MicroTasks,輸出 nextTick 2。
第五步這時(shí)候又到了 timers 階段,執(zhí)行 timeout 2 的回調(diào),輸出 timeout 2,將 nextTick 4 推入 nextTickQueue,這時(shí) timeList 已經(jīng)沒(méi)有定時(shí)器了,清空 nextTickQueue 和 MicroTasks 輸出 nextTick 4。
總結(jié)不知道大家懂了沒(méi)有,整個(gè)過(guò)程其實(shí)還比較粗糙,在學(xué)習(xí)過(guò)程中也看了不少的源碼分析,但是 node 發(fā)展很快,很多分析已經(jīng)過(guò)時(shí)了,源碼改變了不少,但是對(duì)于理清思路還是很有作用的。
各位看官如果覺(jué)得還行、OK、有點(diǎn)用,歡迎來(lái)我 GitHub 給個(gè)小星星,我會(huì)很舒服的,哈哈。
文 / 小烜同學(xué)
天衣無(wú)縫的秘密是:做技術(shù),你快樂(lè)嗎?編 / 熒聲
本文已由作者授權(quán)發(fā)布,版權(quán)屬于創(chuàng)宇前端。歡迎注明出處轉(zhuǎn)載本文。本文鏈接:https://knownsec-fed.com/2018...
想要訂閱更多來(lái)自知道創(chuàng)宇開(kāi)發(fā)一線的分享,請(qǐng)搜索關(guān)注我們的微信公眾號(hào):創(chuàng)宇前端(KnownsecFED)。歡迎留言討論,我們會(huì)盡可能回復(fù)。
歡迎點(diǎn)贊、收藏、留言評(píng)論、轉(zhuǎn)發(fā)分享和打賞支持我們。打賞將被完全轉(zhuǎn)交給文章作者。
感謝您的閱讀。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/98271.html
摘要:項(xiàng)目組長(zhǎng)給我看了一道面試別人的面試題。打鐵趁熱,再來(lái)一道題來(lái)加深下理解。作者以樂(lè)之名本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。 showImg(https://segmentfault.com/img/bVbur0z?w=600&h=400); 剛?cè)肼毿鹿?,屬于公司萌新一枚,一天下午?duì)著屏幕看代碼架構(gòu)時(shí)。BI項(xiàng)目組長(zhǎng)給我看了一道面試別人的JS面試題。 雖然答對(duì)了,但把理由說(shuō)錯(cuò)了,照樣不及格。 ...
摘要:一看這二逼就是周杰倫的死忠粉看看控制臺(tái)輸出,確實(shí)沒(méi)錯(cuò)就是對(duì)象。從根本上來(lái)說(shuō),作用域是基于函數(shù)的,而執(zhí)行環(huán)境是基于對(duì)象的例如全局執(zhí)行環(huán)境即全局對(duì)象。全局對(duì)象全局屬性和函數(shù)可用于所有內(nèi)建的對(duì)象。全局對(duì)象只是一個(gè)對(duì)象,而不是類。 覺(jué)得本人寫(xiě)的不算很爛的話,可以登錄關(guān)注一下我的GitHub博客,博客會(huì)堅(jiān)持寫(xiě)下去。 今天同學(xué)去面試,做了兩道面試題,全部做錯(cuò)了,發(fā)過(guò)來(lái)給我看,我一眼就看出來(lái)了,因?yàn)?..
摘要:今天同學(xué)去面試,做了兩道面試題全部做錯(cuò)了,發(fā)過(guò)來(lái)給道典型的面試題前端掘金在界中,開(kāi)發(fā)人員的需求量一直居高不下。 排序算法 -- JavaScript 標(biāo)準(zhǔn)參考教程(alpha) - 前端 - 掘金來(lái)自《JavaScript 標(biāo)準(zhǔn)參考教程(alpha)》,by 阮一峰 目錄 冒泡排序 簡(jiǎn)介 算法實(shí)現(xiàn) 選擇排序 簡(jiǎn)介 算法實(shí)現(xiàn) ... 圖例詳解那道 setTimeout 與循環(huán)閉包的經(jīng)典面...
摘要:想必面試題刷的多的同學(xué)對(duì)下面這道題目不陌生,能夠立即回答出輸出個(gè),可是你真的懂為什么嗎為什么是輸出為什么是輸出個(gè)這兩個(gè)問(wèn)題在我腦邊縈繞。同步任務(wù)都好理解,一個(gè)執(zhí)行完執(zhí)行下一個(gè)。本文只是我對(duì)這道面試題的一點(diǎn)思考,有誤的地方望批評(píng)指正。 想必面試題刷的多的同學(xué)對(duì)下面這道題目不陌生,能夠立即回答出輸出10個(gè)10,可是你真的懂為什么嗎?為什么是輸出10?為什么是輸出10個(gè)10?這兩個(gè)問(wèn)題在我腦...
摘要:下面我們來(lái)使用面向?qū)ο箢悎D這里就不再畫(huà)了首先面試題中所提到的我們都可以看成類,比如停車場(chǎng)是一個(gè)類吧,它里面的車位是一個(gè)類吧,攝像頭,屏幕。。。 以下是某場(chǎng)的一道面試題(大概): 1、一個(gè)停車場(chǎng),車輛入場(chǎng)時(shí),攝像頭記錄下車輛信息2、屏幕上顯示所接收的車輛的信息情況(車牌號(hào))以及各層車位的車位余量3、停車場(chǎng)一共四層車位,其中的三層都為普通車位,還有一層為特殊車位(體現(xiàn)在停車計(jì)費(fèi)價(jià)格上面的不...
閱讀 2265·2021-09-27 13:35
閱讀 561·2019-08-30 15:55
閱讀 810·2019-08-30 15:53
閱讀 555·2019-08-30 15:52
閱讀 2146·2019-08-30 12:59
閱讀 2269·2019-08-29 16:42
閱讀 1385·2019-08-26 18:26
閱讀 2467·2019-08-26 13:48