JavaScript中的參數傳遞
本篇文章主要講的是JavaScript中最正常不過的現象——函數參數傳遞,本篇文章篇幅不長,但一定能引發屬於你自己的思考!
大家可能會發現,系列的最近幾篇文章都圍繞着函數來講,畢竟作爲Js中的一等公民,它無處不在;
目錄
前言
在研究這個問題之前,大家可以回憶一下,Js基本的數據類型有哪些?如果分類?
- 原始數據類型值 primitive type,比如
Undefined
,Null
,Boolean
,Number
,String
。 - 引用類型值,也就是對象類型 Object type,
比如Object
,Array
,Function
,Date
等。
這是因爲聲明變量時不同的內存分配:
-
原始值:存儲在棧(stack)中的簡單數據段,也就是說,它們的值直接存儲在變量訪問的位置。它可以直接存儲,是因爲這些原始類型佔據的空間是固定的,所以可將他們存儲在較小的內存區域 –
棧
中。這樣存儲便於迅速查尋變量的值。 -
引用值:存儲在堆(heap)中的對象,也就是說,存儲在變量處的值是一個指針(point),指向存儲對象的內存地址。你可以想像成房間內放着你需要的物品,而你手裏拿着房間的鑰匙。這是因爲:引用值的大小會改變,所以不能把它放在棧中,否則會降低變量查尋的速度。相反,通過記錄
鑰匙
就可以找到對應存儲的數據。是的存儲鑰匙地址
的大小是固定的,所以把它存儲在棧中對變量性能無任何負面影響。
瞭解了值的類型,再來繼續瞭解函數中傳入參數都發生了什麼。
一、值傳遞
在紅寶書中,曾經提到:ECMAScript中所有函數的參數都是按值傳遞的
。也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變量複製到另一個變量一樣。
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20,沒有變化
alert(result); //30
二、引用傳遞
參數傳遞的另一種傳遞方式——引用傳遞
:函數接收的不是值的拷貝,而是對象的隱式引用。(因爲拷貝複雜的數據結構會在性能上產生問題),我們來看下面的代碼。
var group = {
num: 10
}
function func(obj) {
obj.num += 10;
console.log('obj:', obj);
}
func(group); // { num: 20 }
我是這樣理解的:
- 實際上是給了函數一把
鑰匙A
,鑰匙A
上記錄着哪個房間保存着函數需要的東西,函數就配了一把一摸一樣的鑰匙B
; - 之後函數用
鑰匙B
打開了對應的房間,對房間的物品進行了改變,離開房間; - 轉過身來你又用
鑰匙A
打開了這個房間,你意識到了函數其實改變了房間的佈局; - 而爲什麼是配了一把鑰匙呢?顯然配一把
鑰匙
比建造一間一模一樣的房間
,要簡單靠譜的多吧?
三、共享傳遞(call by sharing)
該策略是1974年由Barbara Liskov爲CLU編程語言提出的。
該策略的要點是: 函數接收的是對象對於的拷貝(副本),該引用拷貝和形參以及其值相關聯。
這裏出現的引用,我們不能稱之爲“按引用傳遞”,因爲函數接收的參數不是直接的對象別名,而是該引用地址的拷貝。
最重要的區別就是: 函數內部給參數重新賦新值不會影響到外部的對象(和上例按引用傳遞的case),但是因爲該參數是一個地址拷貝,所以在外面訪問和裏面訪問的都是同一個對象(例如外部的該對象不是想按值傳遞一樣完全的拷貝),改變該參數對象的屬性值將會影響到外部的對象。
我們來看下面的例子:
var obj = {
value: 1
};
function foo(o) {
o = 2;
console.log(o); //2
}
foo(obj);
console.log(obj.value) // 1
上面的例子就是傳入Object類型
的但結果卻和引用傳遞
不同。
有很多開發人員(包括我)錯誤地認爲:在局部作用域中修改的對象會在全局作用域中反映出來,就說明參數是按引用傳遞的。我們再看一看《你不知道的Js》中的例子:
function setName(obj) {
obj.name = "餘光";
obj = new Object();
obj.name = "未知";
}
var person = new Object();
setName(person);
alert(person.name); // 餘光
setName()函數中添加了兩行代碼:
- 爲
obj
重新定義了一個對象; - 爲該對象定義了一個帶有不同值的
name
屬性; - 將
obj.name
屬性設置爲’未知’(注意此時obj和外部傳入的obj有哪些聯繫?)
爲什麼會這樣?
- 雖然在函數內部修改了參數的值,但原始的引用仍然保持未變。
- 這是因爲,當在函數內部重寫
obj
時,這個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢後立即被銷燬。
四、總結
我們來總結一下前面幾節最核心的內容:
- 參數的值是調用者傳遞的對象值的拷貝(copy of value),函數內部改變參數的值不會影響到外面的對象(該參數在外面的值
- 按引用傳遞:函數內部對參數的任何改變都是影響該對象在函數外部的值,因爲兩者引用的是同一個對象,也就是說:這時候參數就相當於外部對象的一個別名。
- 共享傳遞不可能去解除引用和改變對象本身,但可以去修改該對象的屬性值。
參考
- 《你不知道的JavaScript》
寫在最後
目前,前端內功系列已經是第八篇了,也想聽聽大家的意見,無論是寫作風格和之後幾篇文章的側重點,大家都可以提哦~
JavaScript內功系列:
- this、call、apply詳解,系列(一)
- 從原型到原型鏈,系列(二)
- 從作用域到作用域鏈,系列(三)
- JavaScript中的執行上下文(四)
- JavaScript中的變量對象(五)
- JavaScript之自執行函數表達式(六)
- JavaScript中的閉包,給自己一場重生(七)
- 本文
- 下篇預告:Js中的數據類型都有哪些?
關於我
- 花名:餘光
- WX:j565017805
- 沉迷JS,水平有限,虛心學習中
其他沉澱
如果您看到了最後,不妨收藏、點贊、評論一下吧!!!
持續更新,您的三連就是我最大的動力,虛心接受大佬們的批評和指點,共勉!