JavaScript中的參數傳遞(求值策略),ECMAScript中所有函數的參數都是按值傳遞嗎(系列八)

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 }

我是這樣理解的:

  1. 實際上是給了函數一把鑰匙A鑰匙A上記錄着哪個房間保存着函數需要的東西,函數就配了一把一摸一樣的鑰匙B
  2. 之後函數用鑰匙B打開了對應的房間,對房間的物品進行了改變,離開房間;
  3. 轉過身來你又用鑰匙A打開了這個房間,你意識到了函數其實改變了房間的佈局;
  4. 而爲什麼是配了一把鑰匙呢?顯然配一把鑰匙比建造一間一模一樣的房間,要簡單靠譜的多吧?

在這裏插入圖片描述

三、共享傳遞(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()函數中添加了兩行代碼:

  1. obj 重新定義了一個對象;
  2. 爲該對象定義了一個帶有不同值的 name 屬性;
  3. obj.name 屬性設置爲’未知’(注意此時obj和外部傳入的obj有哪些聯繫?)

爲什麼會這樣?

  1. 雖然在函數內部修改了參數的值,但原始的引用仍然保持未變。
  2. 這是因爲,當在函數內部重寫 obj 時,這個變量引用的就是一個局部對象了。而這個局部對象會在函數執行完畢後立即被銷燬。

在這裏插入圖片描述

四、總結

我們來總結一下前面幾節最核心的內容:

  • 參數的值是調用者傳遞的對象值的拷貝(copy of value),函數內部改變參數的值不會影響到外面的對象(該參數在外面的值
  • 按引用傳遞:函數內部對參數的任何改變都是影響該對象在函數外部的值,因爲兩者引用的是同一個對象,也就是說:這時候參數就相當於外部對象的一個別名。
  • 共享傳遞不可能去解除引用和改變對象本身,但可以去修改該對象的屬性值。

參考

  • 《你不知道的JavaScript》
    在這裏插入圖片描述

寫在最後

目前,前端內功系列已經是第八篇了,也想聽聽大家的意見,無論是寫作風格和之後幾篇文章的側重點,大家都可以提哦~

JavaScript內功系列:

  1. this、call、apply詳解,系列(一)
  2. 從原型到原型鏈,系列(二)
  3. 從作用域到作用域鏈,系列(三)
  4. JavaScript中的執行上下文(四)
  5. JavaScript中的變量對象(五)
  6. JavaScript之自執行函數表達式(六)
  7. JavaScript中的閉包,給自己一場重生(七)
  8. 本文
  9. 下篇預告:Js中的數據類型都有哪些?

關於我

  • 花名:餘光
  • WX:j565017805
  • 沉迷JS,水平有限,虛心學習中

其他沉澱

如果您看到了最後,不妨收藏、點贊、評論一下吧!!!
持續更新,您的三連就是我最大的動力,虛心接受大佬們的批評和指點,共勉!

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