JavaScript中如何實(shí)現(xiàn)深度克隆

js.png

一:為什么要實(shí)現(xiàn)深度克麓煞?

這是一個(gè)前端面試經(jīng)常問(wèn)到的問(wèn)題割坠,并且在知乎上我看到很多的前端大神也都探討過(guò)齐帚。這個(gè)問(wèn)題背后的考察點(diǎn)相當(dāng)豐富,涉及JS的數(shù)據(jù)類型彼哼、數(shù)據(jù)存儲(chǔ)对妄、內(nèi)存管理。還涉及很多邊界條件的考慮敢朱,很具有代表性剪菱。所以為了鞏固這個(gè)這些知識(shí)點(diǎn),查閱了很多資料拴签,整理一篇文章孝常,供學(xué)習(xí)交流使用,如有不足之處蚓哩,歡迎指正构灸。

二:JavaScript中的內(nèi)存管理

JS內(nèi)存管理,往深了挖很復(fù)雜岸梨,這里只做簡(jiǎn)單的介紹喜颁,幫助理解js的基本類型和引用類型稠氮,為了后面講解深度克隆做鋪墊,我們知道JS擁有自動(dòng)的垃圾回收機(jī)制半开,這樣就使得很多前端開發(fā)人員不是很重視內(nèi)存管理這一塊隔披。但是其實(shí)這一部分的內(nèi)容對(duì)于理解JS中原型與原型鏈,閉包寂拆,遞歸都是非常有幫助的奢米。

在JS中,每一個(gè)數(shù)據(jù)都需要一個(gè)內(nèi)存空間漓库。內(nèi)存空間又被分為兩種:

棧內(nèi)存(stock)
堆內(nèi)存(heap)
  • 基礎(chǔ)數(shù)據(jù)類型和棧內(nèi)存

    JS中的基礎(chǔ)數(shù)據(jù)類型恃慧,我們也稱之為原始數(shù)據(jù)類型,這些值都有固定的大小渺蒿,往往都保存在棧內(nèi)存中痢士,由系統(tǒng)自動(dòng)分配存儲(chǔ)空間。我們可以直接操作保存在棧內(nèi)存空間的值茂装,因此基礎(chǔ)數(shù)據(jù)類型都是按值訪問(wèn)怠蹂。也就是說(shuō),它們的值直接存儲(chǔ)在變量訪問(wèn)的位置少态。

    數(shù)據(jù)在棧內(nèi)存中的存儲(chǔ)與使用方式類似于數(shù)據(jù)結(jié)構(gòu)中的堆棧數(shù)據(jù)結(jié)構(gòu)城侧,遵循后進(jìn)先出的原則

    基礎(chǔ)數(shù)據(jù)類型: 
    Number String Null Undefined Boolean Symbol(ES6新增)
    

    要簡(jiǎn)單理解棧內(nèi)存空間的存儲(chǔ)方式,我們可以通過(guò)類比乒乓球盒子來(lái)分析彼妻。

乒乓球盒子.png

乒乓球的存放方式與棧內(nèi)存中存儲(chǔ)數(shù)據(jù)的方式如出一轍嫌佑。處于盒子中最頂層的乒乓球,它一定是最后被放進(jìn)去的侨歉,但可以最先被使用屋摇。而我們想要使用底層的乒乓球,就必須將上面的兩個(gè)乒乓球取出來(lái)幽邓,讓最底層的乒乓球處于盒子頂層炮温。這就是棧空間 “先進(jìn)后出牵舵,后進(jìn)先出” 的特點(diǎn)柒啤。

  • 引用數(shù)據(jù)類型與堆內(nèi)存

與java等其他語(yǔ)言不同,JS的引用數(shù)據(jù)類型畸颅,比如數(shù)組Array担巩,它們值的大小是不固定的,可以再不聲明長(zhǎng)度的情況下没炒,動(dòng)態(tài)填充兵睛。引用數(shù)據(jù)類型的值是保存在堆內(nèi)存中的對(duì)象。

JavaScript不允許直接訪問(wèn)堆內(nèi)存中的位置窥浪,因此我們不能直接操作對(duì)象的堆內(nèi)存空間祖很。

在操作對(duì)象時(shí),實(shí)際上是在操作對(duì)象的引用而不是實(shí)際的對(duì)象漾脂。因此假颇,引用類型的值都是按引用訪問(wèn)的。

這里的引用骨稿,我們可以粗淺地理解為保存在棧內(nèi)存中的一個(gè)地址笨鸡,該地址與堆內(nèi)存的實(shí)際值相關(guān)聯(lián)。

為了更好的搞懂棧內(nèi)存與堆內(nèi)存坦冠,我們可以結(jié)合以下例子與圖解進(jìn)行理解形耗。

```
var a1 = 0;   // 棧 
var a2 = 'this is string'; // 棧
var a3 = null; // 棧

var b = { m: 20 }; // 變量b存在于棧中,{m: 20} 作為對(duì)象存在于堆內(nèi)存中
var c = [1, 2, 3]; // 變量c存在于棧中辙浑,[1, 2, 3] 作為對(duì)象存在于堆內(nèi)存中
```
堆棧內(nèi)存圖解.png

上例變量的內(nèi)存分配情況圖解

因此當(dāng)我們要訪問(wèn)堆內(nèi)存中的引用數(shù)據(jù)類型時(shí)激涤,實(shí)際上我們首先是從棧中獲取了該對(duì)象的地址引用(或者地址指針),然后再?gòu)亩褍?nèi)存中取得我們需要的數(shù)據(jù)判呕。

三:JavaScript中基礎(chǔ)類型和引用類型的特點(diǎn)倦踢。

既然已經(jīng)明白了棧內(nèi)存和堆內(nèi)存的存儲(chǔ)數(shù)據(jù)的特點(diǎn),那么接下來(lái)就看一些小的例子侠草,這些小的例子專門用來(lái)考察基礎(chǔ)類型和引用類型的存儲(chǔ)特點(diǎn)

  • 例一

    let a = 20;
    let b = a;
    b = 30;
    console.log(a) // 這時(shí)a的值是多少辱挥?
    
原始類型的復(fù)制.png

在棧內(nèi)存中的數(shù)據(jù)發(fā)生復(fù)制行為時(shí),系統(tǒng)會(huì)自動(dòng)為新的變量分配一個(gè)新的內(nèi)存空間边涕。上例中 let b = a 執(zhí)行之后晤碘,a與b雖然值都等于20,但是他們其實(shí)已經(jīng)是相互獨(dú)立互不影響的值了功蜓。具體如圖园爷。所以我們修改了b的值以后,a的值并不會(huì)發(fā)生變化霞赫。因此輸出的 a 的值還是 20腮介。

  • 例二

    let m = { a: 10, b: 20 }
    let n = m;
    n.a = 15;
    console.log(m.a) // 這時(shí)m.a的值是多少
    
引用類型的復(fù)制.png

我們通過(guò)let n = m 執(zhí)行一次復(fù)制引用類型的操作。引用類型的復(fù)制同樣也會(huì)為新的變量自動(dòng)分配一個(gè)新的值保存在棧內(nèi)存中端衰,但不同的是叠洗,這個(gè)新的值,僅僅只是引用類型存在棧內(nèi)存中的一個(gè)地址指針旅东。當(dāng)?shù)刂分羔樝嗤瑫r(shí)灭抑,盡管他們相互獨(dú)立,但是在堆內(nèi)存中訪問(wèn)到的具體對(duì)象實(shí)際上是同一個(gè)抵代。如圖所示腾节。

因此當(dāng)我改變n時(shí),m也發(fā)生了變化。此時(shí)輸出的m.a的值也變成了15案腺,這就是引用類型的特性庆冕。

如果這樣還不好理解,就舉一個(gè)生活中的例子劈榨,假設(shè)甲乙兩個(gè)人一起租房子访递,那么他們都共同擁有同一個(gè)大門進(jìn)入房間,如果一個(gè)人將屋子里面的僅有的空調(diào)弄壞了同辣,那么兩個(gè)人就都沒(méi)有空調(diào)使用了拷姿。

四:JavaScript淺克隆和深度克隆

既然已經(jīng)理解了JS中基礎(chǔ)類型和引用類型的特點(diǎn),下面就開始真正探討關(guān)于深度克隆問(wèn)題了旱函。

  • 1响巢、淺克隆

淺克隆之所以被稱為淺克隆,是因?yàn)閷?duì)象只會(huì)被克隆最外部的一層,至于更深層的對(duì)象,則依然是通過(guò)引用指向同一塊堆內(nèi)存.

// 淺克隆函數(shù)
function shallowClone(o) {
  const obj = {};
  for ( let i in o) {
    obj[i] = o[i];
  }
  return obj;
}
// 被克隆對(duì)象
const oldObj = {
  a: 1,
  b: [ 'e', 'f', 'g' ],
  c: { h: { i: 2 } }
};

const newObj = shallowClone(oldObj);
console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 }
console.log(oldObj.c.h === newObj.c.h); // true

我們可以很明顯地看到,雖然oldObj.c.h被克隆了,但是它還與oldObj.c.h相等,這表明他們依然指向同一段堆內(nèi)存,我們上面討論過(guò)了引用類型的特點(diǎn)棒妨,這就造成了如果對(duì)newObj.c.h進(jìn)行修改,也會(huì)影響oldObj.c.h踪古。這本身不是我們想要的,因此就不算是一版好的克隆靶衍。

newObj.c.h.i = '我們兩個(gè)都變了';
console.log(newObj.c.h, oldObj.c.h); // { i: '我們兩個(gè)都變了' } { i: '我們兩個(gè)都變了' }

我們改變了newObj.c.h.i的值,oldObj.c.h.i也被改變了,這就是淺克隆的問(wèn)題所在.

  • 2灾炭、深克隆

    • 2.1 JSON.parse方法

      JSON對(duì)象parse方法可以將JSON字符串反序列化成JS對(duì)象,stringify方法可以將JS對(duì)象序列化成JSON字符串,這兩個(gè)方法結(jié)合起來(lái)就能產(chǎn)生一個(gè)便捷的深克隆.

      const newObj = JSON.parse(JSON.stringify(oldObj));
      

      我們依然使用上述中的那個(gè)例子做演示颅眶。

      const oldObj = {
        a: 1,
        b: [ 'e', 'f', 'g' ],
        c: { h: { i: 2 } }
      };
      
      const newObj = JSON.parse(JSON.stringify(oldObj)); // 將oldObj先序列化再反序列化蜈出。
      console.log(newObj.c.h, oldObj.c.h); // { i: 2 } { i: 2 }
      console.log(oldObj.c.h === newObj.c.h); // false 這時(shí)候就已經(jīng)不一樣了
      newObj.c.h.i = '我和oldObj相互獨(dú)立';
      console.log(newObj.c.h, oldObj.c.h); // { i: '我和oldObj相互獨(dú)立' } { i: 2 }
      

      果然,這是一個(gè)實(shí)現(xiàn)深克隆的好方法,但是這個(gè)解決辦法是不是太過(guò)簡(jiǎn)單了.

      確實(shí),這個(gè)方法雖然可以解決絕大部分是使用場(chǎng)景,但是卻有很多坑.

      • 1.他無(wú)法實(shí)現(xiàn)對(duì)函數(shù) 、RegExp等特殊對(duì)象的克隆;
      • 2.會(huì)拋棄對(duì)象的constructor,所有的構(gòu)造函數(shù)會(huì)指向Object;
      • 3.對(duì)象有循環(huán)引用,會(huì)報(bào)錯(cuò);

      針對(duì)以上的情況涛酗,我們可以測(cè)試一下:

      // 構(gòu)造函數(shù)
      function person(pname) {
        this.name = pname;
      }
      
      const Messi = new person('Messi');
      
      // 函數(shù)
      function say() {
        console.log('hi');
      };
      
      const oldObj = {
        a: say,
        b: new Array(1),
        c: new RegExp('ab+c', 'i'),
        d: Messi
      };
      
      const newObj = JSON.parse(JSON.stringify(oldObj));
      
      // 無(wú)法復(fù)制函數(shù)
      console.log(newObj.a, oldObj.a); // undefined [Function: say]
      // 稀疏數(shù)組 復(fù)制錯(cuò)誤
      console.log(newObj.b[0], oldObj.b[0]); // null undefined
      // 無(wú)法復(fù)制正則對(duì)象
      console.log(newObj.c, oldObj.c); // {} /ab+c/i
      // 構(gòu)造函數(shù)指向錯(cuò)誤
      console.log(newObj.d.constructor, oldObj.d.constructor); // [Function: Object] [Function: person]
      

      我們可以看到在對(duì)函數(shù)铡原、正則對(duì)象、稀疏數(shù)組等對(duì)象克隆時(shí)會(huì)發(fā)生意外商叹,構(gòu)造函數(shù)指向也會(huì)發(fā)生錯(cuò)誤燕刻。

      const oldObj = {};
      
      oldObj.a = oldObj;
      
      const newObj = JSON.parse(JSON.stringify(oldObj));
      console.log(newObj.a, oldObj.a); // TypeError: Converting circular structure to JSON
      

      對(duì)象的循環(huán)引用會(huì)拋出錯(cuò)誤。

  • 2.2 構(gòu)造一個(gè)深度克隆函數(shù)

    由于要面對(duì)不同的對(duì)象(正則剖笙、數(shù)組卵洗、Date等)要采用不同的處理方式,我們需要實(shí)現(xiàn)一個(gè)對(duì)象類型判斷函數(shù)

    const isType = (obj, type) => {
      if (typeof obj !== 'object') return false;
      // 判斷數(shù)據(jù)類型的經(jīng)典方法:
      const typeString = Object.prototype.toString.call(obj);
      let flag;
      switch (type) {
        case 'Array':
          flag = typeString === '[object Array]';
          break;
        case 'Date':
          flag = typeString === '[object Date]';
          break;
        case 'RegExp':
          flag = typeString === '[object RegExp]';
          break;
        default:
          flag = false;
      }
      return flag;
    };
    

    這樣我們就可以對(duì)特殊對(duì)象進(jìn)行類型判斷了,從而采用針對(duì)性的克隆策略.

    const arr = Array.of(3, 4, 5, 2);
    console.log(isType(arr, 'Array')); // true
    

    對(duì)于正則對(duì)象,我們?cè)谔幚碇耙妊a(bǔ)充一點(diǎn)新知識(shí).
    我們需要通過(guò)正則的擴(kuò)展了解到flags屬性等等,因此我們需要實(shí)現(xiàn)一個(gè)提取flags的函數(shù)

    const getRegExp = re => {
      var flags = '';
      if (re.global) flags += 'g';
      if (re.ignoreCase) flags += 'i';
      if (re.multiline) flags += 'm';
      return flags;
    };
    

    做好了這些準(zhǔn)備工作,我們就可以進(jìn)行深克隆的實(shí)現(xiàn)了.

    /**
    * deep clone
    * @param  {[type]} parent object 需要進(jìn)行克隆的對(duì)象
    * @return {[type]}        深克隆后的對(duì)象
    */
    const clone = parent => {
      // 維護(hù)兩個(gè)儲(chǔ)存循環(huán)引用的數(shù)組
      const parents = [];
      const children = [];
    
      const _clone = parent => {
        if (parent === null) return null;
        if (typeof parent !== 'object') return parent;
    
        let child, proto;
    
        if (isType(parent, 'Array')) {
          // 對(duì)數(shù)組做特殊處理
          child = [];
        } else if (isType(parent, 'RegExp')) {
          // 對(duì)正則對(duì)象做特殊處理
          child = new RegExp(parent.source, getRegExp(parent));
          if (parent.lastIndex) child.lastIndex = parent.lastIndex;
        } else if (isType(parent, 'Date')) {
          // 對(duì)Date對(duì)象做特殊處理
          child = new Date(parent.getTime());
        } else {
          // 處理對(duì)象原型
          proto = Object.getPrototypeOf(parent);
          // 利用Object.create切斷原型鏈
          child = Object.create(proto);
        }
    
        // 處理循環(huán)引用
        const index = parents.indexOf(parent);
    
        if (index != -1) {
          // 如果父數(shù)組存在本對(duì)象,說(shuō)明之前已經(jīng)被引用過(guò),直接返回此對(duì)象
          return children[index];
        }
        parents.push(parent);
        children.push(child);
    
        for (let i in parent) {
          // 遞歸
          child[i] = _clone(parent[i]);
        }
    
        return child;
      };
      return _clone(parent);
    };
    

    我們做一下測(cè)試

    function person(pname) {
      this.name = pname;
    }
    
    const Messi = new person('Messi');
    
    function say() {
      console.log('hi');
    }
    
    const oldObj = {
      a: say,
      c: new RegExp('ab+c', 'i'),
      d: Messi,
    };
    
    oldObj.b = oldObj;
    
    const newObj = clone(oldObj);
    console.log(newObj.a, oldObj.a); // [Function: say] [Function: say]
    console.log(newObj.b, oldObj.b); 
    // { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] } { a: [Function: say], c: /ab+c/i, d: person { name: 'Messi' }, b: [Circular] }
    console.log(newObj.c, oldObj.c); // /ab+c/i /ab+c/i
    console.log(newObj.d.constructor, oldObj.d.constructor); 
    // [Function: person] [Function: person]
    

當(dāng)然,我們這個(gè)深克隆還不算完美,例如Buffer對(duì)象弥咪、Promise过蹂、Set、Map可能都需要我們做特殊處理聚至,另外對(duì)于確保沒(méi)有循環(huán)引用的對(duì)象酷勺,我們可以省去對(duì)循環(huán)引用的特殊處理,因?yàn)檫@很消耗時(shí)間扳躬,不過(guò)一個(gè)基本的深克隆函數(shù)我們已經(jīng)實(shí)現(xiàn)了脆诉。

實(shí)現(xiàn)一個(gè)完整的深克隆是由許多坑要踩的,npm上一些庫(kù)的實(shí)現(xiàn)也不夠完整,在生產(chǎn)環(huán)境中最好用lodash的深克隆實(shí)現(xiàn).

參考鏈接:
https://juejin.im/post/5abb55ee6fb9a028e33b7e0a
https://juejin.im/entry/589c29a9b123db16a3c18adf
https://www.zhihu.com/question/20289071
https://www.zhihu.com/question/47746441?from=profile_question_card
http://laichuanfeng.com/study/javascript-immutable-primitive-values-and-mutable-object-references/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末甚亭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子击胜,更是在濱河造成了極大的恐慌亏狰,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潜的,死亡現(xiàn)場(chǎng)離奇詭異骚揍,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)啰挪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嘲叔,“玉大人亡呵,你說(shuō)我怎么就攤上這事×蚋辏” “怎么了锰什?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)丁逝。 經(jīng)常有香客問(wèn)我汁胆,道長(zhǎng),這世上最難降的妖魔是什么霜幼? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任嫩码,我火速辦了婚禮,結(jié)果婚禮上罪既,老公的妹妹穿的比我還像新娘铸题。我一直安慰自己,他們只是感情好琢感,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布丢间。 她就那樣靜靜地躺著,像睡著了一般驹针。 火紅的嫁衣襯著肌膚如雪烘挫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天柬甥,我揣著相機(jī)與錄音饮六,去河邊找鬼。 笑死暗甥,一個(gè)胖子當(dāng)著我的面吹牛喜滨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播撤防,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼虽风,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起辜膝,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤无牵,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后厂抖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茎毁,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年忱辅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了七蜘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡墙懂,死狀恐怖橡卤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情损搬,我是刑警寧澤碧库,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站巧勤,受9級(jí)特大地震影響嵌灰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颅悉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一沽瞭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧签舞,春花似錦秕脓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至搂鲫,卻和暖如春傍药,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魂仍。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工拐辽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人擦酌。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓俱诸,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親赊舶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子睁搭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,334評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,090評(píng)論 1 32
  • 從三月份找實(shí)習(xí)到現(xiàn)在赶诊,面了一些公司,掛了不少园骆,但最終還是拿到小米舔痪、百度、阿里锌唾、京東锄码、新浪、CVTE晌涕、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,207評(píng)論 11 349
  • Reflect on your powers of concentration. Do you find it d...
    黃虎閱讀 447評(píng)論 0 2
  • 我愛(ài)籃球滋捶,我更喜歡籃球了,總之就是更喜歡了渐排! 我今天也更喜歡老女人了炬太!哈哈哈哈哈哈哈!我不知道我的喜歡可以持續(xù)多久...
    嘎旺閱讀 203評(píng)論 0 0