js-由深拷貝淺拷貝,傳值與傳址-引發的關於堆(“heap”)棧(“stack”)的思考!

前端面試一定會都遇到輸出值的問題,並且不止一道題而是幾乎兩頁紙。這類問題大多都是看一個前端開發者的js基礎,數據類型和變量作用域。今天我們就堆棧的概念來看看js中的變量是如何存儲的。

Js的數據類型可分爲 值類型(基本類型):字符串(String)、數字(Number)、布爾(Boolean)、對空(Null)、未定義(Undefined)。和 引用數據類型:對象(Object)、數組(Array)、函數(Function)。

由於棧和堆的緩存設計各異所以存儲的數據也有所不同。

使用的是一級緩存,內置在CPU內部並與CPU同速運行,效率高,但存儲空間小。適合存儲簡單的數據段,佔據固定空間大小的基本數據類型(String,Number,Boolean,Null,Undefined)。

存放在二級緩存中,是位於CPU與內存之間的臨時存儲器,比一級緩存容量更大,但處理速度更慢。適合存儲大小不定,構造複雜的引用類型值(Function,Array,Object)。

傳值&傳址

傳值就是變量之間值的傳遞與引用,傳址是相對於引用類型而言。如例:

會輸出什麼呢?

按照js自上而下的執行順序我們期望得到<5,1,[2],[0],false>而事實卻是這樣的<5,1,[2],[2],true>。

例子1:a爲Number基本類型,存在棧內存中。將a賦值給b也就是將a的值賦值給b,所以b=1;改變a的值對b不會有影響。

例子2:arr爲Array引用類型,存儲在堆內存中,棧內存儲的是arr(Array)在堆內存空間的引用指針。將arr賦值給arr1也就是將arr在堆內存空間的引用指針賦值給arr1,所以他們都指向了同一個存儲空間的相同數據,固 arr === arr1。

那麼同樣是存儲爲什麼會有存值和存址之分?這就要從棧和堆的存儲方式說起。

前面提到棧使用的是一級緩存,一級緩存被內置在CPU內部並與CPU同速運行,容量小,速度快,更適合佔據固定空間的簡單數據段,故而存值。而堆使用的是二級緩存,二級緩存比一級緩存速度慢,容量大,是一級緩存和內存之間臨時交換數據的地方。適合存儲大小不固定,構造複雜的引用類型值。所以當我們在程序中調用引用類型時,是通過棧內存中的引用指針在堆內存中查找到相應的存儲數據。

現在回看arr的例子,先var申明arr,再申明arr1並賦值爲arr,再改變arr的值,然後分別調用輸出了arr和arr1。從執行順序考慮只是改變了arr的值,arr1是不受影響的,但是在arr1 = arr的時候我們只是傳遞了引用類型再堆內存中的引用指針。所以都變了...

假如兩個例子同時出現在面試題中,是不是一點疑惑都沒有了呢。

深拷貝&淺拷貝

我們經常會遇到將對象賦值給另一對象的場景,有時候也會費力去寫一些clone的方法,爲什麼呢?

如上面例子2中我們看到,對象的傳遞是傳址是通過指針的引用,例子中的傳遞也就是表面上的傳址這是淺拷貝。而深拷貝就是申請新的存儲空間,將源對象的值循環遍歷賦值給目標對象,存入到新的存儲空間。

function clone(origin){
    var target = null;
    if(origin instanceof Array){
        target = origin.concat();
    }else{
        target = {};
        for(var item in origin){
            var val = origin[item];
            target[item] = typeof val == 'object'?clone(val):val;
        }
    }
    return target;
}

而ES6也給我們提供了更好用的方法 Object.assign(target, ...source)用於將所有可枚舉屬性的值從一個或多個源對象複製到目標對象。它返回目標對象。這裏需要注意假如源對象的屬性值是一個對象的引用,那麼它也只指向那個引用。所以要慎用。

當然還有一種方法也不得不提就是jQuery的$.extend()與Object.assign有着異曲同工之妙,但$.extend多了首個參數deep從而實現了深拷貝如列:

 第一次試着寫這種長篇大論,可能漏洞百出,還請多多指正。文章沒有深層意義,僅供參考,以免將您帶入歧途。一些堆棧,一級緩存,二級緩存的知識也都是參照百度百科,如果感興趣的可以自己去研究研究。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章