摘要:用來構成和兩個函數(shù),主要針對的是為了將函數(shù)調(diào)用模式更改為構造器調(diào)用和方法調(diào)用。通過函數(shù)設定時間為毫秒后執(zhí)行函數(shù)的回調(diào)函數(shù),用以達到在規(guī)定時間毫秒時執(zhí)行函數(shù)的目的,并且規(guī)定時間內(nèi)只執(zhí)行一次函數(shù)。
北京的雨已經(jīng)斷斷續(xù)續(xù)下了好久,昏昏欲睡的躲在家里不愿意出門,火影忍者快要結束了,一拳超人第二季據(jù)說還要等好多年,勇者大冒險貌似斷更了,我又是在不喜歡海賊王的畫風,所以,我該看什么好呢。
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; };
executeBound 用來構成 _.bind 和 _.partial 兩個函數(shù),主要針對的是為了將函數(shù)調(diào)用模式更改為構造器調(diào)用和方法調(diào)用。
_.bind = restArgs(function(func, context, args) { if (!_.isFunction(func)) throw new TypeError("Bind must be called on a function"); var bound = restArgs(function(callArgs) { return executeBound(func, bound, context, this, args.concat(callArgs)); }); return bound; });
也許我們可以參考下 Function.prototype.bind(),_.bind 函數(shù)這個需要仔細講一下了,先化簡:
_.bind = function(func, context, args) { var length = arguments.length - 2; args = Array(length); for (var index = 0; index < length; index++) { args[index] = arguments[index + startIndex]; } if (!_.isFunction(func)) throw new TypeError("Bind must be called on a function"); var bound = function(args_2){ args_2 = Array(arguments.length); for (var index = 0; index < arguments.length; index++) { args_2[index] = arguments[index]; } (function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; })(func, bound, context, this, args.concat(args_2)); }; return bound; };
這樣看上去是不是直白很多,官網(wǎng)給它的定義是:綁定函數(shù) function 到對象 object 上, 也就是無論何時調(diào)用函數(shù), 函數(shù)里的 this 都指向這個 object.任意可選參數(shù) arguments 可以傳遞給函數(shù) function , 可以填充函數(shù)所需要的參數(shù),這也被稱為 partial application。對于沒有結合上下文的partial application綁定,請使用partial。,怎么聽怎么別扭,我們可以這樣理解:_.bind 函數(shù)是為其傳參中的 function 的 this 上綁定相應對象屬性,并且同時進行 function 的參數(shù)傳入,而其中最關鍵的就是在執(zhí)行這一系列動作的同時將傳入?yún)?shù) context 綁定到了指向它的 Function 對象本身的 this 身上(可參考函數(shù)調(diào)用模式與方法調(diào)用模式的區(qū)別)。官網(wǎng)有個栗子:
var func = function(greeting){ return greeting + ": " + this.name }; func = _.bind(func, {name: "moe"}, "hi"); func(); {"hi: moe"}
實際上呢它等同于:
var func = _.bind(function(greeting){ return greeting + ": " + this.name; }, {name: "moe"}, "hi" ); func(); {"hi: moe"}
結合前面簡化的 _.bind 代碼示例可知這個函數(shù)的核心思想就是先通過 _.bind 初始化的時候優(yōu)化第3+個參數(shù) args,為什么叫 3+ 呢,因為從第三個參數(shù)開始,可能是不限定的參數(shù)數(shù)量,所以從第三個開始到最后一個參數(shù)同一處理為一個數(shù)組 args。
緊接著就是執(zhí)行剛才初始化過后的函數(shù)了,當 func(); 的時候也就是開始執(zhí)行 _.bind 中的 bound 函數(shù)。bound 允許傳遞參數(shù)并且其參數(shù)會被 push 到 args 中,具體實現(xiàn)參看上面的簡化代碼 args.concat(args_2)。這里我們有幾個需要注意的點,其一是 callingContext instanceof boundFunc,之前我們講過 instanceof 的神奇用法,在這里它用與判斷 bound 中的 this 的指向是否繼承于 bound。我們一定知道 this 指向的四個情況,如下:
var obj = {}; var func = function (){console.log(this);}; func(); new func(); obj.func = func; obj.func(); func.apply(["this is parameter"]); func.call(["this is parameter"]);
輸出結果為:
Window {external: Object, chrome: Object, document: document, alogObjectConfig: Object, alogObjectName: "alog"…} func {} Object {} ["this is parameter"] ["this is parameter"]
分別代表四種情況:
函數(shù)調(diào)用模式:指向 Global,瀏覽器客戶端即 window;
方法調(diào)用模式:指向?qū)ο蟊旧恚?/p>
構造器調(diào)用模式:指向為新構造的對象,繼承自原 Function 對象;
apply 或 call 調(diào)用模式:指向傳入的參數(shù)。
這里還有一些非常好的資料:this、Understanding JavaScript Function Invocation and "this",在這里我要說一下我在推庫上看到一篇關于 this 的介紹文章說:“比較系統(tǒng)的分類是《JavaScript語言精粹》中的,分為函數(shù)調(diào)用模式(this綁定全局對象window)和方法調(diào)用模式(this綁定調(diào)用方法的主體)”,我把《JavaScript語言精粹》這本書從頭到尾翻看了好幾遍,實際上它原文是這樣說的:“在 JAVASCRIPT 中一共有4種調(diào)用模式:方法調(diào)用模式、函數(shù)調(diào)用模式、構造器調(diào)用模式和 apply 調(diào)用模式?!?/b>,具體敘述在原書的P27~P30頁,感興趣的朋友可以看下,在給大家看一個彩蛋,嚴格模式下的 this。緊接上文,當 bound 中的 this 的指向是否繼承于 bound 函數(shù)的時候說明是使用了 new 關鍵字的構造器調(diào)用模式調(diào)用了 _.bind 函數(shù),則繼續(xù)執(zhí)行 executeBound 函數(shù)中的 baseCreate 創(chuàng)建基本函數(shù)然后進行一系列的操作,其實說到底 baseCreate 的目的就是為了保證傳入?yún)?shù) Function 的 this 的干凈。
另外一個需要注意的地方是官網(wǎng)示例的暗示(特蛋疼的暗示),我擴展了一下:
var func = function(){ return JSON.stringify(arguments) + ": " + this.name }; func = _.bind(func, {name: "moe"}, "hi"); func(); func = _.bind(func, {name: "moe2"}, "hi2"); func();
輸出結果:
"{"0":"hi"}: moe" "{"0":"hi","1":"hi2"}: moe"
可能有些不明就里的同學會問這是為什么啊,怎么 this.name 的值沒有變化呢。實際上我們第一個 _.bind 是正常的函數(shù)綁定,而第二個 func = _.bind(func, {name: "moe2"}, "hi2"); 是將上一個 _.bind 作為了 Function 參數(shù)傳入到了新的 _.bind 中,而本來的函數(shù) func 作為第一個 _.bind 的 func 參數(shù)一直傳遞到第二個 _.bind 中,但是中間的 this.name 卻被綁定到了第一個 _.bind 上面而不是第一個 _.bind 中的 func 上。有一點繞口。用個代碼介紹下,第二個 _.bind 的情況是這樣子的:
func = _.bind(function( function(greeting){ return greeting + ": " + this.name; }, context, args ) { var length = arguments.length - 2; args = Array(length); for (var index = 0; index < length; index++) { args[index] = arguments[index + startIndex]; } if (!_.isFunction(func)) throw new TypeError("Bind must be called on a function"); var bound = function(args_2){ args_2 = Array(arguments.length); for (var index = 0; index < arguments.length; index++) { args_2[index] = arguments[index]; } (function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; })(func, bound, context, this, args.concat(args_2)); }; return bound; }, {name: "moe2"}, "hi2" );
所以 _.bind 一定要遵循正確的用法,不然真的出錯了可能調(diào)試都不好發(fā)現(xiàn)問題,多層回調(diào)嵌套的時候一層套一層,很麻煩。
_.partial = restArgs(function(func, boundArgs) { var placeholder = _.partial.placeholder; var bound = function() { var position = 0, length = boundArgs.length; var args = Array(length); for (var i = 0; i < length; i++) { args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); return executeBound(func, bound, this, this, args); }; return bound; });
_.partial 函數(shù)的核心思想與 _.bind 相同,都是為了解決 this 指向的問題,區(qū)別在于 _.partial 不需要對 this 上的值做什么處理。用法上我覺得 _.partial 看上去更怪異一些,也許用來做一些特定的計算可能更合適些。
_.partial.placeholder = _;
設置 _.partial.placeholder 為 _。
_.bindAll = restArgs(function(obj, keys) { keys = flatten(keys, false, false); var index = keys.length; if (index < 1) throw new Error("bindAll must be passed function names"); while (index--) { var key = keys[index]; obj[key] = _.bind(obj[key], obj); } });
這里我們看到 _.bindAll 函數(shù)官網(wǎng)的示例就有點糊涂了:
var buttonView = { label : "underscore", onClick: function(){ console.log("clicked: " + this.label); }, onHover: function(){ console.log("hovering: " + this.label); } }; _.bindAll(buttonView, "onClick", "onHover"); buttonView.onClick(); clicked: underscore
我們當然知道結果是 clicked: underscore,那么執(zhí)行 _.bindAll(buttonView, "onClick", "onHover"); 的意義在哪呢,所以說這又是官網(wǎng)坑人的地方了,_.bindAll 的本意是將其傳入的第二個及以后的參數(shù)放到一個共同的上下文環(huán)境里面執(zhí)行,從而達到 this 指向其第一個參數(shù)的本身的目的,而官網(wǎng)的示例為方法調(diào)用模式,this 指向已經(jīng)是 Object 本身了所以看不到變化,但是我們在瀏覽器控制臺查看的話應該能知道 this 上多了 [[TargetFunction]]: function ()、[[BoundThis]]: Object、[[BoundArgs]]: Array[0] 三個參數(shù)并且 [[BoundThis]] 恰好是 Object。閑來無事這好看到有人也寫了這個問題并舉證了一個示例,詳見 Understanding bind and bindAll in Backbone.js。我 cope 一下:
function Developer(skill) { this.skill = skill; this.says = function(){ console.log(this.skill + " rocks!"); } } var john = new Developer("Ruby"); _.bindAll(john, "says"); var func = john.says; func(); //Ruby rocks!
這個函數(shù)調(diào)用模式的示例正好答疑了 this 指向已經(jīng)被改變的這個問題。
_.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; var address = "" + (hasher ? hasher.apply(this, arguments) : key); if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; return memoize; };
_.memoize 函數(shù)更像是一個可以緩存第一次執(zhí)行結果的遞歸函數(shù),我們從源碼中可以看到 memoize.cache = {}; 就是用來存儲計算結果的容器,這里面比較有意思的是 hasher 這個參數(shù),官網(wǎng)釋義: hashFunction,實際上就是通過 hashFunction 對傳入的 key 值進行處理然后放到 memoize.cache = {}; 中,至于怎么處理 hash 也好、md5 也好、或者什么其他的計算加密真值判斷增加對象等等都可以通過 hasher 這個傳入的回調(diào)進行擴展。
————————— 疲憊的分割線 ———————————
這幾天北京總在下雨,身體特別的疲憊,狀態(tài)也不怎么好,所以今天才開始繼續(xù)更新。
————————— END ———————————
_.delay = restArgs(function(func, wait, args) { return setTimeout(function() { return func.apply(null, args); }, wait); });
_.delay 函數(shù)用于處理定時器相關函數(shù),原理是通過 setTimeout 進行二次封裝,比較關鍵的就是 args 參數(shù)通過 restArgs 函數(shù)處理為一個數(shù)組,方便了下一步的 func.apply(null, args); 傳值。
_.defer = _.partial(_.delay, _, 1);
_.defer 這個函數(shù)我們首先可以看到內(nèi)部應用了 _.partial 并且中間傳入?yún)?shù) _,這意味著當 _.defer 執(zhí)行的時候傳入的參數(shù)會被補全到 _.partial 內(nèi)部 bound 中的 args[0] 位置,而此時 args 的值為 [func, 1]并將它傳給 _.delay 函數(shù),即 _.delay.apply(null, args);,用著這種方式曲線的設置 setTimeout 函數(shù)的 wait = 1,目的就是處理代碼復用問題,不然的話完全可以改裝一下 _.delay 函數(shù)可以更簡單的實現(xiàn)這一功能。
_.throttle = function(func, wait, options) { var timeout, context, args, result; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; var throttled = function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; throttled.cancel = function() { clearTimeout(timeout); previous = 0; timeout = context = args = null; }; return throttled; };
_.throttle 函數(shù)可以限制和控制其參數(shù) func 的執(zhí)行次數(shù)和執(zhí)行時間,思想就是通過 wait、now、previous 和 remaining 進行判斷然后分別執(zhí)行相應的策略。
wait:使用 _.throttle 函數(shù)時傳入的時間標識,在每個 wait 毫秒時間段內(nèi)最多且一定調(diào)用一次該函數(shù)。
now:使用 _.now() 函數(shù)獲取當前時間戳。
previous:用來緩存函數(shù)執(zhí)行時的時間戳,用于后面與下一次執(zhí)行時的時間戳進行相關判斷。
remaining:緩存 wait - (now - previous) 的差值。
我們在看官網(wǎng)介紹可以知道 _.throttle 傳遞的 options 分四種情況(默認是 {leading:false,trailing:false}):
{leading:true,trailing:true}:從實例化 _.throttle 的時間開始到執(zhí)行實例化的函數(shù)的時間為止,中間的差值定義為 now - previous,進而得出設定的時間 wait 與 now - previous 的差值 remaining,從而決定怎么執(zhí)行函數(shù)。參考 世紀之光 的很有趣的說法,就是第一次可以立即執(zhí)行,第二次開始將在每 wait 時間內(nèi)只允許執(zhí)行一次,為什么會第一次立即執(zhí)行呢,因為大家設置的 wait 一般都不會太大,所以頁面加載過程中一般已經(jīng)執(zhí)行了 _.throttle 的實例化,也就是說其 remaining <= 0,而后面如果一直執(zhí)行函數(shù),那么就開始 0 < remaining <= wait 模式了,
{leading:false,trailing:false}:這種情況下比較有意思的是 previous 這個參數(shù),在實例化 _.throttle 的時候,previous = 0,利用了 !0 === true 的特性使 _.throttle 內(nèi)部并沒有執(zhí)行回調(diào)函數(shù) func,所以第一次函數(shù)調(diào)用失敗,在第二次開始 previous = now (now 為第一次調(diào)用的時間戳),所以它也分為兩種情況:
{leading:true,trailing:false}:這種情況下是沒有 setTimeout 函數(shù)的,因為 leading:true,所以 previous 初始化為 0,意味著第一次執(zhí)行函數(shù)會立即執(zhí)行,兒后面就要遵循 remaining <= 0 || remaining > wait 才能執(zhí)行,也就是說只有第一執(zhí)行完畢后的時間超過了 wait 才能繼續(xù)調(diào)用函數(shù)才能執(zhí)行(調(diào)用是重點),以此類推。
{leading:false,trailing:true}:這種情況由于 leading:false,所以每次 previous 都等于當前調(diào)用函數(shù)時的時間戳,所以完美的不存在 remaining <= 0 || remaining > wait 的情況,由此只能通過 setTimeout 執(zhí)行回調(diào),所以遵循通過 setTimeout 函數(shù)設定時間為 remaining 毫秒后執(zhí)行 _.throttle 函數(shù)的回調(diào)函數(shù) func,用以達到在規(guī)定時間 wait 毫秒時執(zhí)行函數(shù)的目的,并且規(guī)定 wait 時間內(nèi)只執(zhí)行一次函數(shù)。
其實總結一下就是大概一下兩種都存在或者只存在其一的情況:
remaining <= 0:立即執(zhí)行 _.throttle 函數(shù)的回調(diào)函數(shù) func。
0 < remaining <= wait:通過 setTimeout 函數(shù)設定時間為 remaining 毫秒后執(zhí)行 _.throttle 函數(shù)的回調(diào)函數(shù) func,用以達到在規(guī)定時間 wait 毫秒時執(zhí)行函數(shù)的目的,并且規(guī)定 wait 時間內(nèi)只執(zhí)行一次函數(shù)。
_.debounce = function(func, wait, immediate) { var timeout, result; var later = function(context, args) { timeout = null; if (args) result = func.apply(context, args); }; var debounced = restArgs(function(args) { if (timeout) clearTimeout(timeout); if (immediate) { var callNow = !timeout; timeout = setTimeout(later, wait); if (callNow) result = func.apply(this, args); } else { timeout = _.delay(later, wait, this, args); } return result; }); debounced.cancel = function() { clearTimeout(timeout); timeout = null; }; return debounced; };
_.debounce 更像是 _.delay 的方言版,當 immediate = true 的時候通過 var callNow = !timeout = false 達到立即執(zhí)行回調(diào)函數(shù) func 的目的,并用 later 函數(shù)限制 規(guī)定 wait 時間內(nèi)不允許在調(diào)用函數(shù)(later 函數(shù)內(nèi)部 context = args = underfind,其實我們知道 var later = function(context, args) 這個條件是為 _.delay(later, wait, this, args) 準備的)。
_.wrap = function(func, wrapper) { return _.partial(wrapper, func); };
_.wrap 的兩個參數(shù)理論上都要求是 Function,我們已經(jīng)知道 _.partial 是用來在 this 上下功夫的,雖然這里和 this 也沒什么太大關系,之所以這里應用了 _.partial 是為了讓 func 作為 wrapper 的第一個參數(shù)執(zhí)行,并且通過 executeBound 函數(shù)對函數(shù)調(diào)用模式和方法調(diào)用模式做處理。
_.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; };
_.negate 用來做真值判斷。
_.compose = function() { var args = arguments; var start = args.length - 1; return function() { var i = start; var result = args[start].apply(this, arguments); while (i--) result = args[i].call(this, result); return result; }; };
_.compose 用于將函數(shù)執(zhí)行結果進行傳遞,需要注意的是 var args = arguments; 中的 arguments 和 args[start].apply(this, arguments); 中的 arguments 并不相同就可以了。這個涉及到函數(shù)的執(zhí)行,當每一個函數(shù)執(zhí)行的時候都會形成一個內(nèi)部的上下文執(zhí)行環(huán)境(傳說叫 ExecutionContext,這個我還沒有考證過),在構建環(huán)境的同時生成 arguments 變量和作用域鏈表等等,這里不像敘述了。
_.after = function(times, func) { return function() { if (--times < 1) { return func.apply(this, arguments); } }; };
_.after 接受兩個參數(shù),Number 參數(shù)用來限定 _.after 實例化函數(shù)的執(zhí)行次數(shù),說白了就是只有當?shù)?Number 次執(zhí)行實例化函數(shù)的時候才會繼續(xù)執(zhí)行 func 回調(diào),這個用來處理遍歷 _.each 時某些情況很有用。
_.before = function(times, func) { var memo; return function() { if (--times > 0) { memo = func.apply(this, arguments); } if (times <= 1) func = null; return memo; }; };
_.before,與 _.after 相反,只在規(guī)定 Number 參數(shù)的次數(shù)內(nèi)以此執(zhí)行 _.before,超過之后結束。
_.once = _.partial(_.before, 2);
_.once 創(chuàng)建一個只能調(diào)用一次的函數(shù)。到這里關于函數(shù)相關的源碼就結束了,說心里話很多地方看得懂不一定說的懂,說的懂也不一定用的懂,就拿這個 _.once 來講,它只用了 _.partial 和 _.before 來做文章,用 _.before 限定只能執(zhí)行一次還好理解,那么為什么一定要用 _.partial 坐下處理呢,其目的真的只是為了讓 2 作為 _.before 的第一個參數(shù)進行傳遞過去并將 _.once 的傳參作為 arguments[1+] 傳入么,更深一層考慮,_.partial 函數(shù)是不是有處理過 _.once 傳遞過來的函數(shù)的作用域鏈和 this 相關的情況呢。
_.restArgs = restArgs;
_.restArgs 將 restArgs 函數(shù)綁定到 _ 對象上。
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/79653.html
摘要:組件的選擇命令行工具首先我們需要一個命令行工具來方便的執(zhí)行命令,這里我們選擇組件,如果不喜歡使用且有能力的人完全可以通過組件自己封裝執(zhí)行命令函數(shù)。 對于一個成熟的項目而言,一定需要一個注釋文檔生成工具,我們有很多可選的開源項目,如jsdoc、yuidocjs 等等,擁有這些強大的工具我們完全可以勝任任何注釋方面的管理了么? 一個成熟的開發(fā)者都會知道不管怎么樣的項目都會在不同的開發(fā)條件下...
摘要:新出臺的則規(guī)定,包括六種原始類型和,還有一種,詳見數(shù)據(jù)類型和數(shù)據(jù)結構。用于返回一個由給定對象的所有可枚舉自身屬性的屬性名組成的數(shù)組,。接下來判斷數(shù)字進行相應的操作,其中有和兩個方法,詳見和。 一直想寫一篇這樣的文章,于是心動不如行動,這里選擇的是 Underscore.js 1.8.3 版本,源碼注釋加在一起1625行。 Underscore.js 1.8.3 http://unde...
摘要:第四個判斷如果是對象執(zhí)行返回一個斷言函數(shù),用來判定傳入對象是否匹配指定鍵值屬性。都不匹配最后執(zhí)行,返回傳入的對象的屬性。設置的值并生成函數(shù),等同于,使具有屬性且有值則返回,否則返回,這是一個判斷函數(shù)。 在第二小章節(jié)里面我按照源碼順序介紹幾個方法,源碼緊接著第一章繼續(xù): var builtinIteratee; builtinIteratee,內(nèi)置的 Iteratee (迭代器)。...
摘要:接收三個參數(shù)分別為回調(diào)和,其中與是可選參數(shù)。官網(wǎng)釋義排序一個列表組成一個組,并且返回各組中的對象的數(shù)量的計數(shù)。類似,但是不是返回列表的值,而是返回在該組中值的數(shù)目。 繼續(xù)前面的內(nèi)容,前文我們提到了很多方法的講解,其實到這里就已經(jīng)差不多了,因為大部分代碼其實都是套路,一些基礎函數(shù)再靈活變化就可以組成很多實用的功能。 _.sortBy = function(obj, iteratee,...
摘要:傳入值進行判斷以此決定函數(shù),將三個參數(shù)包括回調(diào)傳入中其中回調(diào)函數(shù)充當?shù)鬟M行真值檢測,最后。是從一個中隨機返回值,并且返回值受限于這個參數(shù),如果沒有傳入或者傳入了則執(zhí)行語句,目的是將判斷處理之后返回單一值。 今天繼續(xù)上次的內(nèi)容,之前我們講到了 reduce 的用法,其實我覺得用法倒是其次的關鍵是作者實現(xiàn) reduce 過程中所靈活用到的函數(shù)處理方法,我們只要有心稍加總覺完全可以拿來主...
閱讀 1066·2021-11-22 15:33
閱讀 3375·2021-11-08 13:20
閱讀 1391·2021-09-22 10:55
閱讀 2060·2019-08-29 11:08
閱讀 783·2019-08-26 12:24
閱讀 3079·2019-08-23 17:15
閱讀 2242·2019-08-23 16:12
閱讀 1946·2019-08-23 16:09