摘要:規(guī)范鏈接規(guī)范中文鏈接本篇文章將從的使用角度來剖析源碼具體實(shí)現(xiàn)。但是對于則不一樣,回調(diào)函數(shù)通過遞歸調(diào)用自己從而保證其值不為類型才結(jié)束,并將賦值到數(shù)組,最后直到所有的數(shù)組都處理完畢由統(tǒng)一的方法結(jié)束當(dāng)前的操作,進(jìn)入處理流程。
開篇
最近在 github 上看到了一個(gè) extremely lightweight Promise polyfill 實(shí)現(xiàn),打開源碼發(fā)現(xiàn)只有240行,果然極其輕量級,于是帶著驚嘆和好奇的心理去了解了下其具體實(shí)現(xiàn)。
源碼的 github 地址:promise-polyfill
Promise 對于前端來說,是個(gè)老生常談的話題,Promise 的出現(xiàn)解決了 js 回調(diào)地域的問題。目前市面上有很多 Promise 庫,但其最終實(shí)現(xiàn)都要遵從 Promise/A+ 規(guī)范,這里對規(guī)范不做解讀,有興趣的可以查看鏈接內(nèi)容。
Promise/A+規(guī)范鏈接
Promise/A+規(guī)范中文鏈接
本篇文章將從 Promise 的使用角度來剖析源碼具體實(shí)現(xiàn)。
API 列表Promise // 構(gòu)造函數(shù) Promise.prototype.then Promise.prototype.catch Promise.prototype.finally // 靜態(tài)方法 Promise.resolve Promise.reject Promise.race Promise.all源碼解析 構(gòu)造函數(shù)
使用
Promise 使用第一步,構(gòu)造實(shí)例,傳入 Function 形參,形參接收兩個(gè) Function 類型參數(shù)resolve, reject
const asyncTask = () => {}; const pro = new Promise((resolve, reject) => { asyncTask((err, data) => { if (err) { reject(err); } else { resolve(data); } }); });
源碼
function Promise(fn) { if (!(this instanceof Promise)) throw new TypeError("Promises must be constructed via new"); if (typeof fn !== "function") throw new TypeError("not a function"); this._state = 0; this._handled = false; this._value = undefined; this._deferreds = []; doResolve(fn, this); } function doResolve(fn, self) { // done變量保護(hù) resolve 和 reject 只執(zhí)行一次 // 這個(gè)done在 Promise.race()函數(shù)中有用 var done = false; try { // 立即執(zhí)行 Promise 傳入的 fn(resolve,reject) fn( function(value) { // resolve 回調(diào) if (done) return; done = true; resolve(self, value); }, function(reason) { // reject 回調(diào) if (done) return; done = true; reject(self, reason); } ); } catch (ex) { if (done) return; done = true; reject(self, ex); } }
Promise必須通過構(gòu)造函數(shù)實(shí)例化來使用,傳入 Promise 構(gòu)造函數(shù)的形參 fn 在doResolve方法內(nèi)是 立即調(diào)用執(zhí)行 的,并沒有異步(指放入事件循環(huán)隊(duì)列)處理。doResolve內(nèi)部針對 fn 函數(shù)的回調(diào)參數(shù)做了封裝處理,done變量保證了 resolve reject 方法只執(zhí)行一次,這在后面說到的Promise.race()函數(shù)實(shí)現(xiàn)有很大用處。
Promise 實(shí)例的內(nèi)部變量介紹名稱 | 類型 | 默認(rèn)值 | 描述 |
---|---|---|---|
_state | Number | 0 | Promise內(nèi)部狀態(tài)碼 |
_handled | Boolean | false | onFulfilled,onRejected是否被處理過 |
_value | Any | undefined | Promise 內(nèi)部值,resolve 或者 reject返回的值 |
_deferreds | Array | [] | 存放 Handle 實(shí)例對象的數(shù)組,緩存 then 方法傳入的回調(diào) |
_state枚舉值類型
_state === 0 // pending _state === 1 // fulfilled,執(zhí)行了resolve函數(shù),并且_value instanceof Promise === true _state === 2 // rejected,執(zhí)行了reject函數(shù) _state === 3 // fulfilled,執(zhí)行了resolve函數(shù),并且_value instanceof Promise === false
注意:這里_state區(qū)分了1 和 3 兩種狀態(tài),下面會解釋原因
/** * Handle 構(gòu)造函數(shù) * @param onFulfilled resolve 回調(diào)函數(shù) * @param onRejected reject 回調(diào)函數(shù) * @param promise 下一個(gè) promise 實(shí)例對象 * @constructor */ function Handler(onFulfilled, onRejected, promise) { this.onFulfilled = typeof onFulfilled === "function" ? onFulfilled : null; this.onRejected = typeof onRejected === "function" ? onRejected : null; this.promise = promise; }
_deferreds數(shù)組的意義:當(dāng)在 Promise 內(nèi)部調(diào)用了異步處理任務(wù)時(shí),pro.then(onFulfilled,onRejected)傳入的兩個(gè)函數(shù)不會立即執(zhí)行,所以此時(shí)會把當(dāng)前的回調(diào)和下一個(gè) pro 對象關(guān)聯(lián)緩存起來,待到 resolve 或者 reject觸發(fā)調(diào)用時(shí),會去 forEach 這個(gè)_deferreds數(shù)組中的每個(gè) Handle 實(shí)例去處理對應(yīng)的 onFulfilled,onRejected 方法。
Promise 內(nèi)部 resolve reject finale 方法上面說到,doResolve 內(nèi)部做了 fn 的立即執(zhí)行,并保證 resolve 和 reject 方法只執(zhí)行一次,接下來說說resolve 和 reject 內(nèi)部具體做了什么
function resolve(self, newValue) { try { // resolve 的值不能為本身 this 對象 // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === self) throw new TypeError("A promise cannot be resolved with itself."); // 針對 resolve 值為 Promise 對象的情況處理 if ( newValue && (typeof newValue === "object" || typeof newValue === "function") ) { var then = newValue.then; if (newValue instanceof Promise) { self._state = 3; self._value = newValue; finale(self); return; } else if (typeof then === "function") { // 兼容類 Promise 對象的處理方式,對其 then 方法繼續(xù)執(zhí)行 doResolve doResolve(bind(then, newValue), self); return; } } // resolve 正常值的流程,_state = 1 self._state = 1; self._value = newValue; finale(self); } catch (e) { reject(self, e); } } function reject(self, newValue) { self._state = 2; self._value = newValue; finale(self); } function finale(self) { // Promise reject 情況,但是 then 方法未提供 reject 回調(diào)函數(shù)參數(shù) 或者 未實(shí)現(xiàn) catch 函數(shù) if (self._state === 2 && self._deferreds.length === 0) { Promise._immediateFn(function() { if (!self._handled) { Promise._unhandledRejectionFn(self._value); } }); } for (var i = 0, len = self._deferreds.length; i < len; i++) { // 這里調(diào)用之前 then 方法傳入的onFulfilled, onRejected函數(shù) // self._deferreds[i] => Handler 實(shí)例對象 handle(self, self._deferreds[i]); } self._deferreds = null; }
resolve,reject 是由用戶在異步任務(wù)里面觸發(fā)的回調(diào)函數(shù)
調(diào)用 resolve reject 方法的注意點(diǎn)
1、newValue不能為當(dāng)前的 this 對象,即下面的這樣寫法是錯(cuò)誤的
const pro = new Promise((resolve)=>{setTimeout(function () { resolve(pro); },1000)}); pro.then(data => console.log(data)).catch(err => {console.log(err)});
因?yàn)閞esolve做了 try catch 的操作,直接會進(jìn)入 reject 流程。
2、newValue可以為另一個(gè)Promise 對象類型實(shí)例, resolve 的值返回的是另一個(gè) Promise 對象實(shí)例的內(nèi)部的_value,而不是其本身 Promise 對象。即可以這樣寫
const pro1 = new Promise((resolve)=>{setTimeout(function () { resolve(100); },2000)}); const pro = new Promise((resolve)=>{setTimeout(function () { resolve(pro1); },1000)}); pro.then(data => console.log("resolve" + data)).catch(err => {console.log("reject" + err)}); // 輸出結(jié)果:resolve 100 // data 并不是pro1對象
具體原因就在 resolve 方法體內(nèi)部做了newValue instanceof Promise的判斷,并將當(dāng)前的_state=3,self._value = newValue,然后進(jìn)入 finale 方法體,在 handle 方法做了核心處理,這個(gè)下面介紹 handle 方法會說到;
這里有一個(gè)注意點(diǎn),resolve 的 value 可能是其他框架的 Promise(比如:global.Promise,nodejs 內(nèi)部的 Promise 實(shí)現(xiàn)) 構(gòu)造實(shí)例,所以在typeof then === "function"條件下做了doResolve(bind(then, newValue), self);的重新調(diào)用,繼續(xù)執(zhí)行當(dāng)前類型的 Promise then 方法,即又重新回到了doResolve流程。
如果這里的實(shí)現(xiàn)方式稍微調(diào)整下,即不管newValue是自身的 Promise 實(shí)例還是其他框架實(shí)現(xiàn)的 Promise實(shí)例,都執(zhí)行doResolve(bind(then, newValue), self)也能行得通,只不過會多執(zhí)行 then 方式一次,從代碼性能上說,上面的實(shí)現(xiàn)方式會更好。參照代碼如下
function resolve(self, newValue) { try { // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure if (newValue === self) throw new TypeError("A promise cannot be resolved with itself."); if ( newValue && (typeof newValue === "object" || typeof newValue === "function") ) { // 這里簡單粗暴處理,無論是 Promise 還是 global.Promise // 都直接調(diào)用doResolve var then = newValue.then; if (typeof then === "function") { doResolve(bind(then, newValue), self); return; } } // resolve 正常值的流程,_state = 1 self._state = 1; self._value = newValue; finale(self); } catch (e) { reject(self, e); } }
所有 resolve 和 reject 的值最終都會去到finale函數(shù)中去處理,只不過在這里的_state狀態(tài)會有所不同;當(dāng)Promise 出現(xiàn)reject的情況時(shí),而沒有提供 onRejected 函數(shù)時(shí),內(nèi)部會打印一個(gè)錯(cuò)誤出來,提示要捕獲錯(cuò)誤。代碼實(shí)現(xiàn)即
const pro = new Promise((resolve,reject)=>{setTimeout(function () { reject(100); },1000)}); pro.then(data => console.log(data)); // 會報(bào)錯(cuò) pro.then(data => console.log(data)).catch(); // 會報(bào)錯(cuò) pro.then(data => console.log(data)).catch(()=>{}); // 不會報(bào)錯(cuò) pro.then(data => console.log(data),()=>{}) // 不會報(bào)錯(cuò)then、catch、finally 方法
第二步,調(diào)用 then 方法來處理回調(diào),支持無限鏈?zhǔn)秸{(diào)用,then 方法第一個(gè)參數(shù)成功回調(diào),第二個(gè)參數(shù)失敗或者異?;卣{(diào)
源碼
function noop() {} Promise.prototype.then = function(onFulfilled, onRejected) { var prom = new this.constructor(noop); handle(this, new Handler(onFulfilled, onRejected, prom)); return prom; }; Promise.prototype["catch"] = function(onRejected) { return this.then(null, onRejected); }; Promise.prototype["finally"] = function(callback) { var constructor = this.constructor; return this.then( function(value) { return constructor.resolve(callback()).then(function() { return value; }); }, function(reason) { return constructor.resolve(callback()).then(function() { return constructor.reject(reason); }); } ); };
Promise.prototype.then方法內(nèi)部構(gòu)造了一個(gè)新的Promsie 實(shí)例并返回,這樣從 api 角度解決了 Promise 鏈?zhǔn)秸{(diào)用的問題,而且值得注意的是,每個(gè) then 方法返回的都是一個(gè)新的 Promise 對象,并不是當(dāng)前的 this鏈接調(diào)用方式。最終的處理都會調(diào)用 handle 方法。
catch方法在 then 方法上做了一個(gè)簡單的封裝,所以從這里也可以看出,then 方法的形參并不是必傳的,catch 只接收onRejected。
finally方法不管是調(diào)用了 then 還是 catch,最終都會執(zhí)行到finally的 callback
核心邏輯:handle方法內(nèi)部實(shí)現(xiàn)上面說了這么多,最終的 resolve reject 回調(diào)處理都會進(jìn)入到 handle 方法中,來處理onFulfilled 和 onRejected,先看源碼
Promise._immediateFn = (typeof setImmediate === "function" && function(fn) { setImmediate(fn); }) || function(fn) { setTimeoutFunc(fn, 0); }; function handle(self, deferred) { // 如果當(dāng)前的self._value instanceof Promise // 將self._value => self,接下來處理新 Promise while (self._state === 3) { self = self._value; } // self._state=== 0 說明還沒有執(zhí)行 resolve || reject 方法 // 此處將 handle 掛起 if (self._state === 0) { self._deferreds.push(deferred); return; } self._handled = true; // 通過事件循環(huán)異步來做回調(diào)的處理 Promise._immediateFn(function() { // deferred.promise :第一個(gè) Promise then 方法 返回的新 Promise 對象 // 這里調(diào)用下一個(gè) Promise 對象的 then 方法的回調(diào)函數(shù) // 如果當(dāng)前 Promise resolve 了,則調(diào)用下一個(gè) Promise 的 resolve方法,反之,則調(diào)用下一個(gè) Promise 的 reject 回調(diào) // 如果當(dāng)前 Promise resolve 了,則調(diào)用下一個(gè) Promise 的 resolve方法 // cb回調(diào)方法:如果自己有onFulfilled||onRejected方法,則執(zhí)行自己的方法;如果沒有,則調(diào)用下一個(gè) Promise 對象的onFulfilled||onRejected var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; // 自己沒有回調(diào)函數(shù),進(jìn)入下一個(gè) Promise 對象的回調(diào) if (cb === null) { (self._state === 1 ? resolve : reject)(deferred.promise, self._value); return; } // 自己有回調(diào)函數(shù),進(jìn)入自己的回調(diào)函數(shù) var ret; try { ret = cb(self._value); } catch (e) { reject(deferred.promise, e); return; } // 處理下一個(gè) Promise 的 then 回調(diào)方法 // ret 作為上一個(gè)Promise then 回調(diào) return的值 => 返回給下一個(gè)Promise then 作為輸入值 resolve(deferred.promise, ret); }); }
self._state === 3,說明當(dāng)前 resolve(promise)方法回傳的值類型為 Promise 對象,
即 self._value instanceOf Promise === true, 將 self=self._value,即當(dāng)前處理變更到了新的 Promise 對象上 ,如果當(dāng)前 promise對象內(nèi)部狀態(tài)是fulfilled或者 rejected,則直接處理onFulfilled 或者 onRejected回調(diào);如果仍然是 padding 狀態(tài),則繼續(xù)等待。這就很好的解釋了為什么resolve(pro1),pro.then的回調(diào)取的值卻是 pro1._value.
從使用角度來看
const pro1 = new Promise(resolve=>{setTimeout(()=>{resolve(100)},1000)}) // 執(zhí)行耗時(shí)1s 的異步任務(wù) pro.then(()=>pro1).then(data => console.log(data)).catch(err => {}); // 輸出結(jié)果: 正常打印了100,data并不是當(dāng)前的pro1對象
pro1內(nèi)部是耗時(shí)1s 的異步任務(wù),此時(shí)self._state === 0,即內(nèi)部是 Padding 狀態(tài),則將deferred對象 push 到_deferreds數(shù)組里面,然后等待 pro1內(nèi)部調(diào)用resolve(100)時(shí),繼續(xù)上面resolve方法體執(zhí)行
const pro1 = new Promise(resolve=>resolve(100)}) // 執(zhí)行同步任務(wù) pro.then(()=>pro1).then(data => console.log(data)).catch(err => {}); // 輸出結(jié)果: 正常打印了100,data并不是當(dāng)前的pro1對象
但是如果pro1內(nèi)部是同步任務(wù),立即執(zhí)行的話,當(dāng)前的self._state === 1,即調(diào)過 push 到_deferreds數(shù)組的操作,執(zhí)行最后的onFulfilled, onRejected回調(diào),onFulfilled, onRejected會被放入到事件循環(huán)隊(duì)列里面執(zhí)行,即執(zhí)行到了Promise._immediateFn
Promise._immediateFn回調(diào)函數(shù)放到了事件循環(huán)隊(duì)列里面來執(zhí)行
這里的deferred對象存放了當(dāng)前的onFulfilled和onRejected回調(diào)函數(shù)和下一個(gè) promise 對象。
當(dāng)前對象的onFulfilled和onRejected如果存在時(shí),則執(zhí)行自己的回調(diào);
pro.then(data => data}).then(data => data).catch(err => {}); // 正確寫法: 輸出兩次 data
注意:then 方法一定要做 return 下一個(gè)值的操作,因?yàn)楫?dāng)前的 ret 值會被帶入到下一個(gè) Promise 對象,即 resolve(deferred.promise, ret)。如果不提供返回值,則第二個(gè) then 的 data 會變成 undefined,即這樣的錯(cuò)誤寫法
pro.then(data => {}}).then(data => data).catch(err => {}); // 錯(cuò)誤寫法: 第二個(gè) then 方法的 data 為 undefined
如果onFulfilled和onRejected回調(diào)不存在,則執(zhí)行下一個(gè) promise 的回調(diào)并攜帶當(dāng)前的_value 值。即可以這樣寫
pro.then().then().then().then(data => {}).catch(err => {}); // 正確寫法: 第四個(gè) then 方法仍然能取到第一個(gè)pro 的內(nèi)部_value 值 // 當(dāng)然前面的三個(gè) then 寫起來毫無用處
所以針對下面的情況:當(dāng)?shù)谝粋€(gè) then 提供了 reject 回調(diào),后面又跟了個(gè) catch 方法。
當(dāng) reject 時(shí),會優(yōu)先執(zhí)行第一個(gè) Promise 的onRejected回調(diào)函數(shù),catch 是在下一個(gè) Promise 對象上的捕獲錯(cuò)誤方法
pro.then(data => data,err => err).catch(err => err);
最終總結(jié):resolve 要么提供帶返回值的回調(diào),要么不提供回調(diào)函數(shù)
靜態(tài)方法:racePromise.race = function(values) { return new Promise(function(resolve, reject) { for (var i = 0, len = values.length; i < len; i++) { // 因?yàn)閐oResolve方法內(nèi)部 done 變量控制了對 resolve reject 方法只執(zhí)行一次的處理 // 所以這里實(shí)現(xiàn)很簡單,清晰明了,最快的 Promise 執(zhí)行了 resolve||reject,后面相對慢的 // Promise都不執(zhí)行 values[i].then(resolve, reject); } }); };
用法
Promise.race([pro1,pro2,pro3]).then()
race的實(shí)現(xiàn)非常巧妙,對當(dāng)前的 values(必須是 Promise 數(shù)組) for 循環(huán)執(zhí)行每個(gè) Promise 的 then 方法,resolve, reject方法對于所有race中 promise 對象都是公用的,從而利用doResolve內(nèi)部的 done變量,保證了最快執(zhí)行的 Promise 能做 resolve reject 的回調(diào),從而達(dá)到了多個(gè)Promise race 競賽的機(jī)制,誰跑的快執(zhí)行誰。
靜態(tài)方法:allPromise.all = function(arr) { return new Promise(function(resolve, reject) { if (!arr || typeof arr.length === "undefined") throw new TypeError("Promise.all accepts an array"); var args = Array.prototype.slice.call(arr); if (args.length === 0) return resolve([]); var remaining = args.length; function res(i, val) { try { // 如果 val 是 Promise 對象的話,則執(zhí)行 Promise,直到 resolve 了一個(gè)非 Promise 對象 if (val && (typeof val === "object" || typeof val === "function")) { var then = val.then; if (typeof then === "function") { then.call( val, function(val) { res(i, val); }, reject ); return; } } // 用當(dāng)前resolve||reject 的值重寫 args[i]{Promise} 對象 args[i] = val; // 直到所有的 Promise 都執(zhí)行完畢,則 resolve all 的 Promise 對象,返回args數(shù)組結(jié)果 if (--remaining === 0) { resolve(args); } } catch (ex) { // 只要其中一個(gè) Promise 出現(xiàn)異常,則全部的 Promise 執(zhí)行退出,進(jìn)入 catch異常處理 // 因?yàn)?resolve 和 reject 回調(diào)有 done 變量的保證只能執(zhí)行一次,所以其他的 Promise 都不執(zhí)行 reject(ex); } } for (var i = 0; i < args.length; i++) { res(i, args[i]); } }); };
用法
Promise.all([pro1,pro2,pro3]).then()
all 等待所有的 Promise 都執(zhí)行完畢,才會執(zhí)行 Promise.all().then()回調(diào),只要其中一個(gè)出錯(cuò),則直接進(jìn)入錯(cuò)誤回調(diào),因?yàn)閷τ谒?all 中 promise 對象 reject 回調(diào)是公用的,利用doResolve內(nèi)部的 done變量,保證一次錯(cuò)誤終止所有操作。
但是對于 resolve 則不一樣, resolve 回調(diào)函數(shù)通過 res 遞歸調(diào)用自己,從而保證其值_value不為 Promise 類型才結(jié)束,并將_value 賦值到 args 數(shù)組,最后直到所有的數(shù)組Promise都處理完畢由統(tǒng)一的 resolve 方法結(jié)束當(dāng)前的 all 操作,進(jìn)入 then 處理流程。
結(jié)束語本篇針對 Promise 的所有 api 做了詳細(xì)的代碼解釋和使用場景,篇幅可能過長,看起來比較費(fèi)力,如果有寫的不對的地方歡迎指正。
最后附上我的 github 源碼注釋版鏈接 promise源碼注釋版
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/94207.html
摘要:盤點(diǎn)一下,模式反應(yīng)了典型的控制權(quán)問題。異步狀態(tài)管理與控制權(quán)提到控制權(quán)話題,怎能少得了這樣的狀態(tài)管理工具。狀態(tài)管理中的控制主義和極簡主義了解了異步狀態(tài)中的控制權(quán)問題,我們再從全局角度進(jìn)行分析。 控制權(quán)——這個(gè)概念在編程中至關(guān)重要。比如,輪子封裝層與業(yè)務(wù)消費(fèi)層對于控制權(quán)的爭奪,就是一個(gè)很有意思的話題。這在 React 世界里也不例外。表面上看,我們當(dāng)然希望輪子掌控的事情越多越好:因?yàn)槌橄髮?..
摘要:盤點(diǎn)一下,模式反應(yīng)了典型的控制權(quán)問題。異步狀態(tài)管理與控制權(quán)提到控制權(quán)話題,怎能少得了這樣的狀態(tài)管理工具。狀態(tài)管理中的控制主義和極簡主義了解了異步狀態(tài)中的控制權(quán)問題,我們再從全局角度進(jìn)行分析。 控制權(quán)——這個(gè)概念在編程中至關(guān)重要。比如,輪子封裝層與業(yè)務(wù)消費(fèi)層對于控制權(quán)的爭奪,就是一個(gè)很有意思的話題。這在 React 世界里也不例外。表面上看,我們當(dāng)然希望輪子掌控的事情越多越好:因?yàn)槌橄髮?..
摘要:雖然有著各種各樣的不同,但是相同的是,他們前端優(yōu)化不完全指南前端掘金篇幅可能有點(diǎn)長,我想先聊一聊閱讀的方式,我希望你閱讀的時(shí)候,能夠把我當(dāng)作你的競爭對手,你的夢想是超越我。 如何提升頁面渲染效率 - 前端 - 掘金Web頁面的性能 我們每天都會瀏覽很多的Web頁面,使用很多基于Web的應(yīng)用。這些站點(diǎn)看起來既不一樣,用途也都各有不同,有在線視頻,Social Media,新聞,郵件客戶端...
摘要:原理剖析第篇之服務(wù)端啟動工作原理分析下一大致介紹由于篇幅過長難以發(fā)布,所以本章節(jié)接著上一節(jié)來的,上一章節(jié)為原理剖析第篇之服務(wù)端啟動工作原理分析上那么本章節(jié)就繼續(xù)分析的服務(wù)端啟動,分析的源碼版本為二三四章節(jié)請看上一章節(jié)詳見原理剖析第篇之 原理剖析(第 011 篇)Netty之服務(wù)端啟動工作原理分析(下) - 一、大致介紹 1、由于篇幅過長難以發(fā)布,所以本章節(jié)接著上一節(jié)來的,上一章節(jié)為【原...
閱讀 1652·2023-04-25 20:36
閱讀 2077·2021-09-02 15:11
閱讀 1212·2021-08-27 13:13
閱讀 2666·2019-08-30 15:52
閱讀 4777·2019-08-29 17:13
閱讀 1013·2019-08-29 11:09
閱讀 1503·2019-08-26 11:51
閱讀 849·2019-08-26 10:56