【JS】類型檢測

前言

js 中的類型檢測也是很重要的一部分缤弦,所以說這篇文章我們就來講一下怎么對 JavaScript 中的基本數(shù)據(jù)類型進(jìn)行檢測领迈。其實(shí)這也是在讀 Zepto 源碼中學(xué)習(xí)到的,所以閱讀源碼對我們的提升還是很有幫助的碍沐。本文基于參考了前輩們的文章之后個人理解此文寫的有不當(dāng)?shù)牡胤嚼晖保埜魑淮罄兄刚?/p>

其實(shí)常規(guī)方法主要有四種

  1. typeof
  2. instanceof
  3. Object.prototype.toString
  4. construcor

其實(shí)這四種方式歸根結(jié)底就是兩種思路:

  1. 根據(jù)數(shù)據(jù)類型判斷(1,2)
  2. 根據(jù)構(gòu)造函數(shù)判斷(3累提,4)

前置基礎(chǔ)

再看 Zepto 之前看了 慕課網(wǎng)一個老師的視頻尘喝,一共一個小時左右,開了快進(jìn)估計(jì)也就 45 分鐘左右斋陪。只是講了 Zepto 的架構(gòu)和設(shè)計(jì)朽褪,沒有詳細(xì)的將每一個方法,初看之前可以看一下无虚,對 Zepto 有一個大概的印象缔赠。

原型與原型鏈

其實(shí)這部分真的是老生常談的問題,但是每一次聽其他人都有新的收獲友题。真的是不想寫這部分嗤堰,但是自我感覺整體思路比較清晰,所以推薦大家閱讀一下度宦。

Zepto 整個的設(shè)計(jì)思想其實(shí)是基于 js 的原型鏈踢匣。關(guān)于原型鏈,這個老師講的比較清晰斗埂,需要記住三句話:

  1. 每一個函數(shù)符糊,都有一個 prototype 屬性。
  2. 所有通過函數(shù) new 出來的對象呛凶,這個對象都有一個 __proto__ 指向這個函數(shù)的 prototype男娄。
  3. 當(dāng)你想要使用一個對象(或者一個數(shù)組)的某個功能時:如果該對象本身具有這個功能,則直接使用漾稀;如果該對象本身沒有這個功能模闲,則去 __proto__ 中找。

什么是 prototype(顯示原型)

每一個函數(shù)在創(chuàng)建之后都會擁有一個名為 prototype 的屬性崭捍,這個屬性指向函數(shù)的原型對象尸折。通過Function.prototype.bind方法構(gòu)造出來的函數(shù)是個例外,它沒有prototype屬性殷蛇。

var fn = function() {}
console.log( fn.prototype );

通過下面這幅圖我們可以看出創(chuàng)建一個函數(shù)時实夹,都會有一個 prototype 屬性指向它的原型橄浓。而 fn.prototype 中有一個 constructor 屬性指向 fn 函數(shù)。

原型圖

什么是 __proto__(隱式原型)

JavaScript 中任意對象都有一個內(nèi)置屬性 __proto__亮航,隱式原型指向創(chuàng)建這個對象的函數(shù)(constructor)的 prototype荸实。

Object.prototype 這個對象是個例外,它的 __proto__ 值為 null

console.log( typeof Array );   // 'function'
console.log( Array.prototype );

數(shù)組構(gòu)造函數(shù) Array 也是一個函數(shù)缴淋,并且在 Array 的原型中除了指向 Array 的 constructor 之外還有其他的內(nèi)置對象准给。

__proto__ 的指向

上面應(yīng)該都不難理解,主要是 __proto__ 的指向重抖,這個問題是比較難理解的露氮,我們來看剛剛的定義,__proto__ 指向創(chuàng)建這個對象的函數(shù)的顯式原型钟沛。創(chuàng)建函數(shù)一共有三種方式:

  1. 字面量方式
    var person1 = {
       name: 'cyl',
       sex: 'male'
    };

字面量的方式是一種為了開發(fā)人員更方便創(chuàng)建對象的一個語法糖畔规,本質(zhì)就是

   var o = new Object(); 
   o.xx = xx;
   o.yy=yy;

所以說使用字面量方式創(chuàng)建的函對象的 __proto__ 屬性是指向 Object.prototype 的。

  1. 構(gòu)造函數(shù)
    所謂的構(gòu)造函數(shù)讹剔,就是通過 new 關(guān)鍵字調(diào)用的函數(shù)油讯,只要是通過 new 關(guān)鍵字調(diào)用的函數(shù)都是構(gòu)造函數(shù)。由構(gòu)造函數(shù)構(gòu)造的對象延欠,其 __prototype__ 指向其構(gòu)造函數(shù)的 prototype 屬性指向的對象陌兑。
    var  arr = new Array()

比如 arr 是一個實(shí)例化的數(shù)組,那么 arr 的 __proto__ 屬性就指向 Array 的 prototype 屬性由捎。

原型圖
  1. 函數(shù)通過 Object.create 構(gòu)造的
var person1 = {
    name: 'cyl',
    sex: 'male'
};

var person2 = Object.create(person1);

Object.create 的內(nèi)部其實(shí)是這樣的:

function object(o){
    function F(){}
    F.prototype = o;
    return new F()
}

也可以看成是通過 new 創(chuàng)建的兔综。所以說我們就可以一目了然,person2 的 __proto__ 是指向 person1 的狞玛。(注意软驰,是直接指向 person1,而不是 person1.prototype)心肪。

prototype__proto__ 的作用

在了解了什么是顯示原型 prototype 和隱式原型 __proto__ 之后锭亏,我們也知道了怎么去找隱式原型,那么它們有什么作用呢硬鞍?

  • 顯式原型的作用:用來實(shí)現(xiàn)基于原型的繼承與屬性的共享慧瘤。
  • 隱式原型的作用:構(gòu)成原型鏈,同樣用于實(shí)現(xiàn)基于原型的繼承固该。舉個例子锅减,當(dāng)我們訪問 obj 這個對象中的 x 屬性時,如果在 obj 中找不到伐坏,那么就會沿著 __proto__ 依次查找怔匣。

這里我們要注意了,當(dāng)我們訪問 obj 這個對象中的 x 屬性時桦沉,如果在 obj 中找不到每瞒,那么就會沿著 __proto__ 依次查找金闽。

劃重點(diǎn),是在 __proto__ 中依次查找

重寫 __proto__

既然我們知道了繼承實(shí)際上是繼承對象 __proto__ 上的屬性剿骨,那我們就可以改寫我們的 __proto__ 屬性呐矾。

var arr = [1,2,3];
arr.__proto__.addClass = function () {
    console.log(123);
}
arr.push(4);
arr.addClass();   // 123

修改了之后,arr 不僅有內(nèi)置的 concat懦砂、push 等功能,還多了一個 addClass 功能组橄。

也可以完全改寫 __proto__ 屬性荞膘,那么其原先的所有的功能都沒有了,如下圖所示玉工。

原型圖

是時候祭上這張圖了:


深入理解JS原型鏈

typeof

typeof 是解釋器內(nèi)部實(shí)現(xiàn)羽资,根據(jù) ECMA-262 規(guī)定的幾種類型的值來返回類型名稱。

但是 typeof 的應(yīng)用場景非常有限遵班,基本上只能判斷出來使用字面量方式賦值的基本數(shù)據(jù)類型屠升,例如:

    var  a = 123;
    console.log(typeof(a));   // number

    var  b = "string";
    console.log(typeof(b));   // string

    var  c = true;
    console.log(typeof(c));   // boolean

    var  d;
    console.log(typeof(d));   // undefined

    var  e = null;
    console.log(typeof(e));   // object

    var  f = [1,2,3];
    console.log(typeof(f));   // object

    var  g = {};
    console.log(typeof(g));   // object

    var  fun = function () {};
    console.log(typeof(fun)); // function

    var  A = new Number(123);
    console.log(typeof(A));   // object
    console.log(A instanceof Number);  // true

    var  B = new String("123");
    console.log(typeof(B));    // object
    console.log(B instanceof String);  // true
    

由以上例子可以看出,typeof 測試的結(jié)果并不是特別的準(zhǔn)確狭郑,并且只能檢測使用字面量命名的基本數(shù)據(jù)類型(除了 null)腹暖。所以我們一般不使用 typeof 進(jìn)行數(shù)據(jù)檢測。

instanceof

在上面的例子中翰萨,我們已經(jīng)使用了 typeof 進(jìn)行數(shù)據(jù)檢測脏答。
instance 是“例子,實(shí)例”的意思亩鬼,所以 instanceof 意思是用于判斷變量是否是某一個對象的實(shí)例殖告。

instanceof 原理

以下部分是根據(jù) JavaScript instanceof 運(yùn)算符深入剖析 理解。

instanceof 的原理可以認(rèn)為是如下:

function instance_of(L, R) {    //L 表示左表達(dá)式雳锋,R 表示右表達(dá)式
 var O = R.prototype;           // 取 R 的顯示原型
 L = L.__proto__;               // 取 L 的隱式原型
 while (true) { 
   if (L === null) 
     return false; 
   if (O === L)                 // 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時黄绩,返回 true 
     return true; 
   L = L.__proto__; 
 } 
}

再結(jié)合我們在最開始介紹的前置知識的這張圖來看幾個例子幫助我們更好的理解 instanceof 的原理:

JS原型鏈

例1:

Object instanceof Object;
// 為了方便表述,首先區(qū)分左側(cè)表達(dá)式和右側(cè)表達(dá)式
ObjectL = Object, ObjectR = Object; 
// 下面根據(jù)規(guī)范逐步推演
O = ObjectR.prototype = Object.prototype 
L = ObjectL.__proto__ = Function.prototype 
// 第一次判斷
O != L 
// 循環(huán)查找 L 是否還有 __proto__ 
L = Function.prototype.__proto__ = Object.prototype 
// 第二次判斷
O == L 
// 返回 true

例2:

Function instanceof Function
// 為了方便表述玷过,首先區(qū)分左側(cè)表達(dá)式和右側(cè)表達(dá)式
FunctionL = Function, FunctionR = Function; 
// 下面根據(jù)規(guī)范逐步推演
O = FunctionR.prototype = Function.prototype 
L = FunctionL.__proto__ = Function.prototype 
// 第一次判斷
O == L 
// 返回 true

例3:

Foo instanceof Foo
// 為了方便表述爽丹,首先區(qū)分左側(cè)表達(dá)式和右側(cè)表達(dá)式
FooL = Foo, FooR = Foo; 
// 下面根據(jù)規(guī)范逐步推演
O = FooR.prototype = Foo.prototype 
L = FooL.__proto__ = Function.prototype 
// 第一次判斷
O != L 
// 循環(huán)再次查找 L 是否還有 __proto__ 
L = Function.prototype.__proto__ = Object.prototype 
// 第二次判斷
O != L 
// 再次循環(huán)查找 L 是否還有 __proto__ 
L = Object.prototype.__proto__ = null 
// 第三次判斷
L == null 
// 返回 false

其實(shí),instanceof 的重點(diǎn)也就是左邊對象的隱式原型等于右邊構(gòu)造函數(shù)的顯示原型冶匹,是不是聽著很熟悉呢习劫,這就是在 new 操作中的關(guān)鍵一步(new 操作是賦值),這樣就可以判斷指定的對象是否是某個構(gòu)造函數(shù)的實(shí)例嚼隘,使 L = L.proto(繼續(xù)沿原型鏈向上尋找)诽里,一直循環(huán)判斷左邊對象的隱式原型等于右邊構(gòu)造函數(shù)的顯示原型,直到L.__proto__ 為 null(L 已經(jīng)循環(huán)到 object.prototype) 或者 true

instanceof 局限性

instanceof 的局限性應(yīng)該也就是不能檢測基本數(shù)據(jù)類型了吧飞蛹,其他的貌似沒什么谤狡。通過對 instanceof 的原理進(jìn)行分析灸眼,我們可以得知,只要左邊的對象的對象能夠通過原型鏈 __proto__ 是指向右邊的構(gòu)造函數(shù)就可以~

instanceof 右邊必須是對象或構(gòu)造函數(shù)墓懂,否則會拋出 TypeError 的錯誤焰宣。

Object.prototype.toString

所有的數(shù)據(jù)類型都可以用 Object.prototype.toString 來檢測,而且非常的精準(zhǔn)。

以下內(nèi)容參考 談?wù)凮bject.prototype.toString

我們先來看一下 Object.prototype.toString 是怎么進(jìn)行類型檢測的

    var a = 123;
    console.log(Object.prototype.toString.call(a));    // [object Number]

    var b = "string";
    console.log(Object.prototype.toString.call(b));    // [object String]

    var c = [];
    console.log(Object.prototype.toString.call(c));    // [object Array]

    var d = {};
    console.log(Object.prototype.toString.call(d));    // [object Object]

    var e = true;
    console.log(Object.prototype.toString.call(e));    // [object Boolean]

    var f =  null;
    console.log(Object.prototype.toString.call(f));    // [object Null]

    var g;
    console.log(Object.prototype.toString.call(g));    // [object Undefined]

    var h = function () {};
    console.log(Object.prototype.toString.call(h));    // [object Function]

    var A = new Number();
    console.log(Object.prototype.toString.call(A));    // [object Number]

所以說捕仔,Object.prototype.toString 還是能夠比較準(zhǔn)確的檢測出對應(yīng)的類型的匕积。

Object.prototype.toString 的實(shí)現(xiàn)過程

在 ECMAScript 5中,Object.prototype.toString() 被調(diào)用時榜跌,會進(jìn)行如下步驟:

  1. 如果 thisundefined 闪唆,返回 [object Undefined]
  2. 如果 thisnull钓葫, 返回 [object Null]悄蕾;
  3. Object 為以 this 作為參數(shù)調(diào)用 ToObject 的結(jié)果;
  4. classObject 的內(nèi)部屬性 [[Class]] 的值础浮;
  5. 返回三個字符串 [object", class, 以及"] 拼接而成的字符串帆调。

[[Class]]

本規(guī)范的每種內(nèi)置對象都定義了 [[Class]] 內(nèi)部屬性的值。宿主對象的 [[Class]] 內(nèi)部屬性的值可以是除了 "Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String" 的任何字符串豆同。[[Class]] 內(nèi)部屬性的值用于內(nèi)部區(qū)分對象的種類番刊。注,本規(guī)范中除了通過 Object.prototype.toString ( 見 15.2.4.2) 沒有提供任何手段使程序訪問此值影锈。

在 JavaScript 代碼里撵枢,唯一可以訪問該屬性的方法就是通過 Object.prototype.toString,通常方法如下:

Object.prototype.toString.call/apply(value)

在 ES6 請參見 談?wù)?Object.prototype.toString

construtor

construtor 其實(shí)也是用了原型鏈的知識精居。

constructor 屬性返回對創(chuàng)建此對象的數(shù)組函數(shù)的引用锄禽。

    var a = 123;
    console.log( a.constructor == Number);    // true

    var b = "string";
    console.log( b.constructor == String);    // true

    var c = [];
    console.log( c.constructor == Array);    // true

    var d = {};
    console.log( d.constructor == Object);    // true

    var e = true;
    console.log( e.constructor == Boolean);    // true

    var f =  null;
    console.log( f.constructor == Null);    //  TypeError: Cannot read property 'constructor' of null

    var g;
    console.log( g.constructor == Undefined);    // Uncaught TypeError: Cannot read property 'constructor' of
    undefined

    var h = function () {};
    console.log( h.constructor == Function);    // true

    var A = new Number();
    console.log( A.constructor == Number);    // true

    var A = new Number();
    console.log( A.constructor == Object);    // false

通過上述的實(shí)例,我們可以看到靴姿,無論是通過字面量或者構(gòu)造函數(shù)創(chuàng)建的基本類型沃但,都可以檢測出。并且也可以檢測出 Array佛吓、Object宵晚、Function引用類型,但是不能檢測出 NullUndefined

Zepto 中檢測數(shù)據(jù)類型

在 Zepto 中主要是用 Object.prototype.toString 來做數(shù)據(jù)類型的判斷的
現(xiàn)在讓我們來看一下 Zepto 中是怎么檢測這些數(shù)據(jù)類型的:

    var class2type = {},
        toString = class2type.toString

    // 在代碼中部维雇,執(zhí)行了
    // $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    //   class2type[ "[object " + name + "]" ] = name.toLowerCase()
    // })
    // 用來給 class2type 對象賦值
    //
    // a ? b : c || d
    //type 用來判斷類型
    function type(obj) {
        return obj == null ? String(obj) :
        class2type[toString.call(obj)] || "object"
    }

這里你可能會有疑問淤刃,為什么使用 toString 而不是 Object.prototype.toString
那是因?yàn)槿绻麑⒒緮?shù)據(jù)類型,比如 string吱型、number逸贾、boolean等類型的值使用 toString 的方法時,是直接將基本數(shù)據(jù)類型轉(zhuǎn)換為 string 類型,但是如果對 object 類型使用 toString 方法铝侵,則是會調(diào)用其原型上的 toString 方法灼伤,也就是 Object.prototype.toString。所以 Zepto 在開頭的地方就定義了 class2type 為一個 object 類型咪鲜。

如果 obj 的類型為 null 或者 undefined 直接返回狐赡,如果該對象的 Object.prototype.toString 將返回的結(jié)果作為 class2type 的 key 取值。Object.prototype.toString 對不同的數(shù)據(jù)類型會返回形如 [object Boolean] 的結(jié)果疟丙。

如果都不是以上情況颖侄,默認(rèn)返回 object 類型。

Zepto 中的其他檢測方法

isFunction

    // 判斷是否是函數(shù)
    function isFunction(value) { return type(value) == "function" }

isWindow

// 判斷是否是 window對象(注意享郊,w為小寫)指當(dāng)前的瀏覽器窗口发皿,window對象的window屬性指向自身。
    // 即 window.window === window
    function isWindow(obj)     { return obj != null && obj == obj.window }

isDocument

// 判斷是否是 document 對象
    // window.document.nodeType == 9 數(shù)字表示為9拂蝎,常量表示為 DOCUMENT_NODE
    function isDocument(obj)   { return obj != null && obj.nodeType == obj.DOCUMENT_NODE }

isObject

// 判斷是否是 object
    function isObject(obj)     { return type(obj) == "object" }

isPlainObject

function isPlainObject(obj) {
        return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
    }

isArray

    // 判斷是否是arr
    isArray = Array.isArray || function(object){ return object instanceof Array };

likeArray

    // 判斷是否是數(shù)組或者對象數(shù)組
    // !!的作用是把一個其他類型的變量轉(zhuǎn)成的bool類型。
    // !!obj 直接過濾掉了false惶室,null温自,undefined,''等值
    function likeArray(obj) {
        var length = !!obj && 'length' in obj && obj.length,

            // 獲取obj的數(shù)據(jù)類型
            type = $.type(obj);

        // 不能是function類型皇钞,不能是window
        // 如果是array則直接返回true
        // 或者當(dāng)length的數(shù)據(jù)類型是number悼泌,并且其取值范圍是0到(length - 1)這里是通過判斷l(xiāng)ength - 1 是否為obj的屬性

        return 'function' != type && !isWindow(obj) && (
                'array' == type || length === 0 ||
                (typeof length == 'number' && length > 0 && (length - 1) in obj)
            )
    }

isEmptyObject

    // 空對象
    $.isEmptyObject = function(obj) {
        var name
        for (name in obj) return false
        return true
    }

    

isNumeric

    //數(shù)字
    $.isNumeric = function(val) {
        var num = Number(val), type = typeof val;
        return val != null && type != 'boolean' &&
            (type != 'string' || val.length) &&
            !isNaN(num) && isFinite(num) || false
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市夹界,隨后出現(xiàn)的幾起案子馆里,更是在濱河造成了極大的恐慌,老刑警劉巖可柿,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸠踪,死亡現(xiàn)場離奇詭異,居然都是意外死亡复斥,警方通過查閱死者的電腦和手機(jī)营密,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來目锭,“玉大人评汰,你說我怎么就攤上這事×『纾” “怎么了被去?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奖唯。 經(jīng)常有香客問我惨缆,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任踪央,我火速辦了婚禮臀玄,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘畅蹂。我一直安慰自己健无,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布液斜。 她就那樣靜靜地躺著累贤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪少漆。 梳的紋絲不亂的頭發(fā)上臼膏,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音示损,去河邊找鬼渗磅。 笑死,一個胖子當(dāng)著我的面吹牛检访,可吹牛的內(nèi)容都是我干的始鱼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼脆贵,長吁一口氣:“原來是場噩夢啊……” “哼医清!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起卖氨,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤会烙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后筒捺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柏腻,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年系吭,在試婚紗的時候發(fā)現(xiàn)自己被綠了葫盼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡村斟,死狀恐怖贫导,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蟆盹,我是刑警寧澤孩灯,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站逾滥,受9級特大地震影響峰档,放射性物質(zhì)發(fā)生泄漏败匹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一讥巡、第九天 我趴在偏房一處隱蔽的房頂上張望掀亩。 院中可真熱鬧,春花似錦欢顷、人聲如沸槽棍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炼七。三九已至,卻和暖如春布持,著一層夾襖步出監(jiān)牢的瞬間豌拙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工题暖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留按傅,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓胧卤,卻偏偏與公主長得像唯绍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子灌侣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容