很多問題看似復雜姨俩,沒有章法柬祠,事實上卻有著千絲萬縷的聯(lián)系北戏,陳道長此次闡述因為數(shù)據(jù)類型不同而引發(fā)的問題,本文主要探討JS函數(shù)參數(shù)傳遞規(guī)則漫蛔、淺拷貝嗜愈、深拷貝的原理。
變量類型和存儲
首先要明確js中變量的特點莽龟,JS變量本身沒有類型蠕嫁,只有值有類型。這句話怎么理解呢毯盈,先看下面這段代碼剃毒。
let a = 42
typeof a //'number'
注意返回的是'number',不是number搂赋,typeof檢測的不是a的類型赘阀,而是42的類型,也就是a是沒有類型的脑奠,只有a的值有類型基公。
JS總共有7種數(shù)據(jù)類型:Number、String宋欺、Boolean轰豆、Null、Symbol齿诞、Undefined酸休、Object。Object是引用類型掌挚,其他的是基本類型雨席。至于數(shù)組和函數(shù)屬于Object的子類型。不過typeof 一個函數(shù)的時候 會返回'function'吠式,這是為了彰顯函數(shù)是一等公民的地位。它要特殊一點抽米。
我們在聲明一個變量時特占,會給變量進行賦值,變量在存儲的時候也有區(qū)別云茸。基本類型值存放在棧中是目,可以直接訪問。引用類型值存放在堆內(nèi)存中标捺。很關(guān)鍵的一點:JS是不允許直接訪問內(nèi)存的懊纳,所以當一個變量的值是Object時揉抵,它保存的只是一個指針,指向的是Object存放的內(nèi)存地址嗤疯。
定義一個var a = {{類型值}};b = a
冤今,會出現(xiàn)下面兩種情況:1、當類型值是基本類型時茂缚,a和b值雖然相同戏罢,但確是兩個獨立的變量。2脚囊、當類型值是Object時龟糕,a和b存儲的是一樣的指針,指向了共同的地址悔耘。所以再修改a或者b時讲岁,1會有各自的變化,2會兩個變化完還是一樣衬以。
JS函數(shù)參數(shù)
事實上缓艳,參數(shù)傳遞就是受到數(shù)據(jù)類型的影響。JS的函數(shù)有幾個特性:1泄鹏、參數(shù)沒有個數(shù)限制郎任,不管你函數(shù)里要用幾個,它卻可以接收任意多個备籽,因為不用它操心舶治,來的這么些參數(shù)都放到了一個參數(shù)數(shù)組里。另外參數(shù)值也沒有類型限制车猬,非常的開放霉猛,然后函數(shù)內(nèi)再通過arguments對象去訪問這個數(shù)組,拿到參數(shù)珠闰,所以參數(shù)不跟函數(shù)直接打交道惜浅,就好比中間有個傳話的。所以這也是為什么es6之前中不能直接給函數(shù)參數(shù)指定默認值伏嗜。
es6新搞了個rest參數(shù)坛悉,它搭配了一個數(shù)組,變量多余的參數(shù)會存放到這個數(shù)組里承绸,就不用arguments對象來獲取了裸影。
// arguments變量的寫法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest參數(shù)的寫法
const sortNumbers = (...numbers) => numbers.sort();
參數(shù)傳遞問題
重點來了!函數(shù)參數(shù)傳遞其實就是把函數(shù)外的值復制給函數(shù)內(nèi)部的值军熏,也就是按照上面的規(guī)則來
參數(shù)來自外部轩猩,要傳遞到函數(shù)里,找個中間變量,JS是參數(shù)是按“值”傳遞的均践,這個“值”就是變量的值晤锹。當變量的值屬于基本類型,這個“值”就是普通值彤委,當變量的值是引用類型時鞭铆,這個“值”時引用類型的地址。
傳遞的參數(shù)值是基本類型值會復制給一個局部變量葫慎。傳遞的參數(shù)是引用類型的值衔彻,復制的是地址。
深淺拷貝
為啥存在淺拷貝和深拷貝偷办,原因和上面一樣艰额。當我們想把對象a賦值給變量b,你會發(fā)現(xiàn)b和a指向的是一個地方椒涯。b改變的時候a也會變柄沮。這就是因為變量存儲的只是個指針,引用類型的值是放在內(nèi)存中废岂,沒法直接訪問祖搓。
普通復制就是淺拷貝,新對象修改時湖苞,老對象也會發(fā)生變化拯欧。
但是我們想把b復制給a后,然后兩個對象互不干擾财骨,完成這樣的復制镐作,這就是深拷貝。
深拷貝的原理就是把a對象的每個屬性的值遍歷一遍隆箩,然后把它復制給一個中間值该贾,它作為普通的值進行復制到b對象的屬性。這就回到了基本變量的復制問題捌臊。
當然一個對象的屬性也可能是對象杨蛋,例如:obj = [{a:1},{b:2}]
,我們想把obj賦值給新對象理澎,這個時候就得遞歸遍歷了逞力,把對象中a屬性的值1和b屬性的值2都給取到,然后把1和2再復制到新的對象里去糠爬,這個時候新對象就不會影響到之前的對象了掏击。
說的有點繞,就是要切斷新對象和老對象的關(guān)系秩铆,但是咱們又沒辦法直接操作內(nèi)存,就只能通過引用取到老對象中存的數(shù)據(jù)值,拿出來之后放到新對象里殴玛。這就是深拷貝的思想捅膘。
總結(jié)
由于js中數(shù)據(jù)類型不同,變量保存的值也有兩種存儲方式滚粟,在堆里面和在棧面寻仗,存儲方式不同,也就導致了讀取方式不同凡壤,也就導致了復制操作的結(jié)果不同署尤,而參數(shù)傳遞和深淺拷貝都是數(shù)據(jù)復制的操作。所以兩者受到的約束也是相同的亚侠,也就要遵循變量讀取的規(guī)則曹体。
本文是陳少棠原創(chuàng),收錄在《齊云札記》硝烂,轉(zhuǎn)載請標明原作箕别。