前端 淺拷貝和深拷貝

一燕少、什么是淺拷貝鞍陨、什么是深拷貝

我們都知道js的數(shù)據(jù)類型分為基本類型和引用類型,一般討論到淺拷貝和深拷貝的都是針對引用類型的阻塑,像Object和Array這樣的復雜類型废赞,

1、淺拷貝:以Object為例
var  a  =  {
    name:  'Wendy'
};

var  b  =  a;
b.name  =  'Lily';
console.log(b.name);    // Lily
console.log(a.name);    // Lily

可以看出叮姑,對于Object類型唉地,當我們將a賦值給b,然后更改b中的屬性传透,a也會隨著變化耘沼。
也就是說a和b指向了同一塊內存,所以修改其中任意的值朱盐,另一個值都會隨之變化群嗤,這就是淺拷貝。

2兵琳、深拷貝

如果給b放到新的內存中狂秘,將a的各個屬性都復制到新內存里骇径,就是深拷貝。
也就是說者春,當b中的屬性有變化的時候破衔,a內的屬性不會發(fā)生變化。

1.png

二钱烟、淺拷貝的實現(xiàn)

這里說兩個實現(xiàn)方式:

1晰筛、Object.assign()

用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象。它將返回目標對象拴袭。

var  target  =  {a:  1,  b:  1};
var  obj1  =  {a:  2,  b:  2,  c: {ca:1}};
var  obj2  =  {c:  {ca:  3,  cb:  2,  cd:  1}};
var  result  =  Object.assign(target,  obj1,  obj2);

console.log(target);    // {a: 2, b: 2, c: {ca: 31, cb: 32, cc: 33}}
console.log(target  ===  result);    // true

可以看到读第,Object.assign()拷貝的只是屬性值,假如源對象的屬性值是一個指向對象的引用拥刻,它也只拷貝那個引用值怜瞒。所以Object.assign()只能用于淺拷貝或是合并對象。這是Object.assign()值得注意的地方般哼。

2吴汪、函數(shù)實現(xiàn)

function shallowClone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }

    return target;
}
var obj = {a:1, b:2, c:[1,2,3], d:{da:1}}
var clone = shadowClone(obj) //{a:1, b:2, c:[1,2,3], d:{da:1}}

三、深拷貝的實現(xiàn)

1逝她、JSON.parse和JSON.stringify

對于 JSON 安全(也就是說可以被序列化為一個 JSON 字符串并且可以根據(jù)這個字符串解析出一個結構和值完全一樣的對象)的對象來說,有一種巧妙的復制方法:

var clone = JSON.parse(JSON.stringify(target))      

當然睬捶,這種方法需要保證對象是 JSON 安全的黔宛,所以只適用于部分情況。

2擒贸、淺拷貝+遞歸

function clone(source) {
    var target = {};
    for(var i in source) {
        if (source.hasOwnProperty(i)) {
            if (typeof source[i] === 'object') {
                target[i] = clone(source[i]); // 注意這里
            } else {
                target[i] = source[i];
            }
        }
    }

    return target;
}

但是這樣寫還不夠嚴謹臀晃,比如:

  • 沒有對參數(shù)做校驗
  • 判斷是否是對象的邏輯不夠嚴謹
  • 如果用了嚴謹?shù)膶ο笈袛啵敲淳蜎]有考慮到數(shù)組的情況

先看第一個介劫,函數(shù)需要校驗參數(shù)徽惋,如果不是對象直接返回

function clone(source) {    
  if (!isObject(source)) 
  return source;    // xxx
}

第二個typeof校驗實際上只能區(qū)分基本類型和引用類型,其對于Date座韵、RegExp险绘、Array類型返回的是"object"。

2.png

目前判斷一個對象類型的最好的辦法是Object.prototype.toString.call()

function isObject(x) {
  return Object.prototype.toString.call(x) === '[object Object]';
}

再抽象一些

var types = ["Array", "Boolean", "Date", "Number", "Object", "RegExp", "String", "Window", "HTMLDocument"];
for(var i = 0, c = types[i ];i<types.length;I++ ){
    is[c] = (function(type){
        return function(obj){
          return Object.prototype.toString.call(obj) == "[object " + type + "]";
        }
    )(c);
}

完善下第三個問題就是

function isObject(x) {return Object.prototype.toString.call(x) === '[object Object]';}


function clone(source) {
    var target = {};
    if(!isObject(source)) target = source;
    else{
      for(var i in source) {
        if (source.hasOwnProperty(i)) {
            if (isObject(source[i]) || Array.isArray(source[i])) {
                target[i] = clone(source[i]); // 注意這里
            } else {
                target[i] = source[i];
            }
        }
      }
    }

    return target;
}

其實遞歸方法最大的問題在于爆棧誉碴,當數(shù)據(jù)的層次很深,需要同時保存成千上百個調用記錄,很容易發(fā)生"棧溢出"錯誤(stack overflow)

我們用斐波拉契數(shù)列為例响疚,普通遞歸寫法:

function f(n) {
  if (n === 0 || n === 1) return n 
  else return f(n - 1) + f(n - 2)
}

這種寫法国撵,簡單粗暴,但是有個很嚴重的問題成黄。調用棧隨著n的增加而線性增加呐芥,當n為一個大數(shù)時逻杖,就會爆棧了(棧溢出,stack overflow)思瘟。這是因為這種遞歸操作中荸百,同時保存了大量的棧幀,調用棧非常長潮太,消耗了巨大的內存管搪。

三、破解遞歸爆棧

其實破解遞歸爆棧的方法有兩條路铡买,第一種是消除尾遞歸更鲁,但在這個例子中貌似行不通,第二種方法就是干脆不用遞歸奇钞,改用循環(huán)澡为,

1、尾遞歸

要說尾遞歸景埃,就要先了解尾函數(shù)媒至,尾函數(shù)就是指函數(shù)調用最后一步是調用另一個函數(shù)

舉個??:

function f(x){  return g(x);}//屬于尾調用
function f(x){  let y = g(x);  return y;}// 不屬于谷徙,因為調用函數(shù)g之后有其它操作
function f(x){  return g(x) + 1;}//不屬于,因為調用后還要其它操作完慧,即使在同一行函數(shù)內

遞歸函數(shù)是調用自身的函數(shù),尾遞歸就是尾調用自身的函數(shù)屈尼,對尾遞歸來說,由于只存在一個調用記錄脾歧,所以永遠不會發(fā)生"棧溢出"錯誤。

簡單解釋下棧溢出問題鞭执,由于函數(shù)調用會在內存形成一個"調用記錄",又稱"調用幀"(call frame)兄纺,保存調用位置和內部變量等信息。如果在函數(shù)A的內部調用函數(shù)B囤热,那么在A的調用記錄上方,還會形成一個B的調用記錄。等到B運行結束锨苏,將結果返回到A,B的調用記錄才會消失伞租。如果函數(shù)B內部還調用函數(shù)C,那就還有一個C的調用記錄棧葵诈,以此類推。所有的調用記錄作喘,就形成一個"調用棧"(call stack)理疙。

3.png

尾調用由于是函數(shù)的最后一步操作,所以不需要保留外層函數(shù)的調用記錄泞坦,因為調用位置窖贤、內部變量等信息都不會再用到了,只要直接用內層函數(shù)的調用記錄贰锁,取代外層函數(shù)的調用記錄就可以了赃梧。

接下來,把斐波拉契數(shù)列升級為尾遞歸看看

function fTail(n, a=0, b=1){
  if(n===0) return a 
  else{
    return fTail(n-1, b, a+b)
  } 
}
fTail(5) => fTail(4, 1, 1) => fTail(3, 1, 2) => fTail(2, 2, 3) => fTail(1, 3, 5) => fTail(0, 5, 8) => return 5

被尾遞歸改寫之后的調用棧永遠都是更新當前的棧幀而已豌熄,這樣就完全避免了爆棧的危險

但是授嘀,想法是好的,從尾調用優(yōu)化到尾遞歸優(yōu)化的出發(fā)點也沒錯锣险,但是瀏覽器目前還沒有支持


4.png

那么我們可以手動優(yōu)化下:
直接改函數(shù)內部蹄皱,循環(huán)執(zhí)行

function fLoop(n, a = 0, b = 1) {
  while (n--) { 
    [a, b] = [b, a + b] 
  } 
  return a 
}

這個函數(shù)相對比較簡單,我們把深拷貝代碼用循環(huán)實現(xiàn)下:??

function cloneLoop(x) {
    const root = {};

    // 棧
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];

    while(loopList.length) {
        // 深度優(yōu)先
        const node = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化賦值目標囱持,key為undefined則拷貝到父元素夯接,否則拷貝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }

        for(let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循環(huán)
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }

    return root;
}

以上
參考文章:
You don't know JS(上)第109頁
深拷貝的終極探索
JavaScript 調用棧焕济、尾遞歸和手動優(yōu)化
尾調用優(yōu)化

在簡書上發(fā)布相關文章是對自己不斷學習的激勵纷妆;如有什么寫得不對的地方,歡迎批評指正晴弃;給我點贊的都是小可愛 ~_~

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末掩幢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子上鞠,更是在濱河造成了極大的恐慌际邻,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芍阎,死亡現(xiàn)場離奇詭異世曾,居然都是意外死亡,警方通過查閱死者的電腦和手機谴咸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門轮听,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骗露,“玉大人,你說我怎么就攤上這事血巍∠麸保” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵述寡,是天一觀的道長柿隙。 經常有香客問我,道長鲫凶,這世上最難降的妖魔是什么禀崖? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮帆焕,結果婚禮上不恭,老公的妹妹穿的比我還像新娘。我一直安慰自己折晦,他們只是感情好沾瓦,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布贯莺。 她就那樣靜靜地躺著缕探,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耙考。 梳的紋絲不亂的頭發(fā)上潭兽,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天山卦,我揣著相機與錄音,去河邊找鬼枚碗。 笑死,一個胖子當著我的面吹牛嬉荆,可吹牛的內容都是我干的酷含。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼限番,長吁一口氣:“原來是場噩夢啊……” “哼弥虐!你這毒婦竟也來了媚赖?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤颖对,失蹤者是張志新(化名)和其女友劉穎缤底,沒想到半個月后番捂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡徙歼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年鲁沥,在試婚紗的時候發(fā)現(xiàn)自己被綠了耕魄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彭谁。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖考润,靈堂內的尸體忽然破棺而出糊治,到底是詐尸還是另有隱情罚舱,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布粥脚,位于F島的核電站包个,受9級特大地震影響碧囊,放射性物質發(fā)生泄漏。R本人自食惡果不足惜糯而,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一歧蒋、第九天 我趴在偏房一處隱蔽的房頂上張望谜洽。 院中可真熱鬧,春花似錦序臂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽避矢。三九已至囊榜,卻和暖如春卸勺,著一層夾襖步出監(jiān)牢的瞬間烫扼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工映企, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卑吭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓挣菲,卻偏偏與公主長得像掷邦,于是被迫代替她去往敵國和親抚岗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,103評論 1 32
  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,803評論 0 38
  • 我是從下生那天就背負著罰款五千元的小男孩覆享,直到長大了聽母親說當時家境貧寒為了罰款這事是東躲西藏撒顿,月子都沒坐完身...
    xiangpeng閱讀 248評論 0 0
  • 今天早上晨讀的時候核蘸,感覺自己的狀態(tài)還是一般啸驯。對文字的感受也不是很深,不是非常的投入徙鱼。過后我聽到錄音的回放時候针姿。我感...
    喜悅之蘭閱讀 204評論 0 1
  • 第十一點绞绒,不專業(yè) 很多人都知道要讓自己變得專業(yè)一點,只是很多人不知道蓬衡,有時候彤枢,我們專業(yè),不是為了去說服客戶缴啡,而是避...
    賣葡萄酒的奶爸閱讀 262評論 0 0