欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript中到底存不存在“引用傳遞”詳解

 更新時間:2025年08月04日 09:35:30   作者:代碼里的小貓咪  
在Javascript中我們使用函數(shù)并且傳遞參數(shù)來調(diào)用函數(shù),但是Javascript到底是如果傳遞你所傳遞的參數(shù)呢?這篇文章主要介紹了JavaScript中到底存不存在引用傳遞的相關(guān)資料,需要的朋友可以參考下

一、概述

在編程語言中,“值傳遞”(pass by value)和“引用傳遞”(pass by reference)是兩種常見的參數(shù)傳遞方式:

  • 值傳遞:實參的值被復(fù)制一份,傳給形參。函數(shù)內(nèi)部修改形參,不會影響實參。
  • 引用傳遞:實參的地址或引用被傳遞給形參,函數(shù)內(nèi)部通過形參修改內(nèi)存中同一個對象,從而影響實參。

不同語言可能支持其中一種或兩種。比如 C 語義默認值傳遞,若要“引用傳遞”需要傳遞指針;C++ 支持兩者;Python 一切都是對象引用的“值傳遞”(亦即“傳遞引用的副本”);Java 中基本類型值傳遞,對象也是“傳遞引用的副本”;而 JavaScript 的傳參機制常被誤解為也有引用傳遞,嚴格意義上來說 JavaScript 只有“值傳遞”,但對于對象類型,所傳遞的“值”是一個“指向?qū)ο蟮囊?/strong>”,這導(dǎo)致看似像“引用傳遞”的效果。

二、JavaScript 的內(nèi)存模型

首先了解一下 JS 的內(nèi)存管理與內(nèi)存布局。

  1. 棧(Stack)
    存放局部變量、函數(shù)調(diào)用時的上下文(execution context)、基本類型的值等。棧區(qū)空間小且分配回收快。

  2. 堆(Heap)
    存放對象、數(shù)組、函數(shù)等“引用類型”數(shù)據(jù)。堆區(qū)空間大,但分配回收相對慢。

比如:

let a = 10;
let obj = { x: 1, y: 2 };
  • a 的值 10 直接存儲在棧上;
  • obj 存在堆上,棧上保存的其實是一個指向堆中對象的 引用(或稱指針)。

三、值傳遞與引用傳遞的定義

3.1 值傳遞(Pass by Value)

  • 實參的值 復(fù)制給 形參。
  • 形參與實參在內(nèi)存中各占獨立位置,互不干擾。
  • 修改形參不會影響實參。
function foo(x) {
  x = 100;
}
let a = 1;
foo(a);
console.log(a); // 1 —— a 未被修改

a 和 x 各自存儲在不同的位置,函數(shù)內(nèi)部對 x 的修改,不會反映到 a。

3.2 引用傳遞(Pass by Reference)

  • 實參的引用(地址) 直接傳遞給形參。
  • 形參和實參指向同一個內(nèi)存地址。
  • 通過形參修改內(nèi)存,會影響實參。

示例:支持引用傳遞的 C++

void foo(int &x) {
  x = 100;
}
int a = 1;
foo(a);
cout << a; // 100 —— a 被修改

C++ 中用 & 聲明引用參數(shù),就能直接修改實參。 

四、JavaScript 只有“值傳遞”

4.1 基本類型 —— 絕對的值傳遞

JS 的基本類型包括:undefined、null、boolean、number、string、symbol、bigint。這些類型的變量在賦值、傳參時,都是將值本身復(fù)制一份。

function changeNum(n) {
  n = 99;
}
let num = 10;
changeNum(num);
console.log(num); // 10 —— 仍然是 10

無論在函數(shù)內(nèi)如何修改 n,都無法影響外部的 num。

4.2 引用類型 —— 傳遞的是“引用的值”

JS 的引用類型包括:Object、Array、Function、Date、RegExp 等。它們存在堆上,棧上保存的是一個“引用”(內(nèi)存地址的值。

當將引用類型作為參數(shù)傳遞時,復(fù)制的是這個 引用本身的值,并非直接復(fù)制對象。因此:

  • 外部變量與形參都保存了同一個引用地址,指向同一塊堆內(nèi)存。
  • 通過形參修改對象屬性,外部對象也會感知到。
function changeObj(o) {
  o.x = 100;      // 修改同一對象的屬性
  o = { x: 200 }; // 重新給 o 賦了一個新對象
}
let obj = { x: 1 };
changeObj(obj);
console.log(obj.x); // 100 —— 改變屬性生效,但重新賦值不影響外部引用

簡單分析一下:

1、創(chuàng)建 obj

let obj = { x: 1 };

內(nèi)存結(jié)構(gòu)如下:

變量名   |  值
--------|-----------------
obj     |  指向 --> { x: 1 }(對象)

2、調(diào)用 changeObj(obj)

changeObj(obj);

現(xiàn)在 obj 的值(即引用地址)被復(fù)制一份傳給函數(shù)形參 o。可以理解為:

o = obj(引用地址被復(fù)制了)

此時內(nèi)存情況如下:

變量名   |  值
--------|-----------------
obj     |  --> { x: 1 }
o       |  --> { x: 1 }  (和 obj 指向同一個對象)

3、o.x = 100

修改了對象的屬性:{ x: 100 },形參 o 和外部 obj 引用同一個對象,故屬性修改影響可見。

現(xiàn)在內(nèi)存為:

變量名   |  值
--------|-----------------
obj     |  --> { x: 100 }
o       |  --> { x: 100 }  (仍然是同一個對象)

4、o = {x: 200}

這一行的關(guān)鍵點是:

只是給 o 這個局部變量重新賦值,指向了一個新對象,它和原來的 obj 再也沒有關(guān)系!此時外部 obj 仍指向舊對象,不受影響。

現(xiàn)在內(nèi)存狀態(tài)是:

變量名   |  值
--------|-----------------
obj     |  --> { x: 100 }
o       |  --> { x: 200 }(一個新的對象)

也就是說,只是“切斷”了 o 和 obj 的連接,但 obj 依舊指向舊的對象。 

想要影響外部變量 obj 本身的引用(讓它指向新對象),JS 是做不到的(因為無法通過函數(shù)改變調(diào)用者作用域中的變量綁定)。

延伸思考:那如果想改變外部引用呢?

那就需要 返回一個新對象

function changeObj(o) {
  return { x: 200 };
}

let obj = { x: 1 };
obj = changeObj(obj); // 手動接受返回值
console.log(obj.x);   // 200 

總結(jié):

JS 中沒有實參“地址傳遞給形參”,而是“地址的值”被復(fù)制給形參。這里的“引用傳遞”說法,只是因為復(fù)制的是對象的引用而已,函數(shù)內(nèi)部也只是在該引用上做操作。如果改寫引用本身,不會反向影響。

五、細分場景

5.1 基本類型

function foo(s) {
  s += ' world';
  console.log('內(nèi)部 s:', s);
}
let str = 'hello';
foo(str);               // 內(nèi)部 s: hello world
console.log(str);       // 外部 str: hello
  • 形參 s 拷貝了外部 str 的值 'hello'。
  • 函數(shù)內(nèi)部修改 s,不影響外部 str。

5.2 對象類型修改屬性

function mutate(o) {
  o.age = o.age + 1;
}
let person = { name: 'Alice', age: 20 };
mutate(person);
console.log(person.age); // 21 —— 屬性修改被保留
  • 形參 o 拷貝了 person 的引用(值)。
  • 兩者指向同一個對象,修改屬性生效。

5.3 對象整體重新賦值

function replace(o) {
  o = { name: 'Bob', age: 30 };
}
let person = { name: 'Alice', age: 20 };
replace(person);
console.log(person.name); // Alice —— 外部對象不變
  • o = {...} 只是改變了形參 o 的引用指向,原對象保持不動。

5.4 數(shù)組示例

function append(arr) {
  arr.push(4);
  arr = [1, 2];
  arr.push(3);
  console.log('內(nèi)部 arr:', arr);
}
let a = [1, 2, 3];
append(a);             // 內(nèi)部 arr: [1,2,3]
console.log(a);        // 外部 a: [1,2,3,4]
  •  arr.push(4):在原數(shù)組末尾添加 4;外部數(shù)組變化。
  •  arr = [1,2]:形參指向新數(shù)組,后續(xù) push(3) 只影響新數(shù)組,外部無感知。

六、值傳遞 vs 引用傳遞的對比

特征值傳遞“引用傳遞”(誤解)
傳遞內(nèi)容基本類型值,或引用類型的引用值直接傳遞對象內(nèi)存地址(C++ 引用語義)
修改效果不影響外部修改屬性會影響外部,但重賦值不影響
內(nèi)存操作復(fù)制獨立值復(fù)制指針/引用
是否支持JS 全部傳參模式JS 只支持前者,但復(fù)制的是引用的值,易混淆

關(guān)鍵:JS 中的每一次函數(shù)調(diào)用都只做“值拷貝”,不管是基本類型還是引用類型,形參拿到的都是一份拷貝。但如果拷貝的是“引用指針”,則通過該指針操作到同一塊堆內(nèi)存,就會有“修改可見”的效果。

6.1常見誤區(qū)

  1. 誤區(qū):JS 支持引用傳遞
    真相:JS 傳參始終是值傳遞,只是“值”可能是指向?qū)ο蟮囊谩?/p>

  2. 誤區(qū):對象傳參修改屬性等價于引用傳遞
    雖然看起來像引用傳遞,但形參本質(zhì)仍是指針的拷貝,若重寫指針則不會影響外部。

  3. 誤區(qū):函數(shù)內(nèi)部 arguments 改變會影響外部形參
    ES5 嚴格模式下已分離,非嚴格模式下 arguments[i] 與形參同名會關(guān)聯(lián),但這屬于語言特殊行為,與傳參語義不同。

6.2 深拷貝與淺拷貝

既然傳遞的是引用,那么如何避免“函數(shù)體內(nèi)無意修改對象屬性”帶來副作用?通常會先對對象或數(shù)組做淺拷貝或深拷貝,再傳入函數(shù)。

1、淺拷貝

對象淺拷貝:只拷貝一層屬性,若屬性值仍為引用類型,則拷貝的是內(nèi)部引用。

let obj = { a: 1, b: { c: 2 } };
let copy = { ...obj };   // 或 Object.assign({}, obj)
copy.a = 9;
copy.b.c = 99;
console.log(obj.b.c);    // 99 —— 內(nèi)部對象仍被修改

數(shù)組淺拷貝:arr.slice()、[...arr]。

2、深拷貝

  • 將所有層級都克隆一份,避免共享任何引用。
  • 方法:JSON.parse(JSON.stringify(obj))、手寫遞歸、使用 lodash.cloneDeep
let obj = { a: 1, b: { c: 2 } };
let deep = JSON.parse(JSON.stringify(obj));
deep.b.c = 100;
console.log(obj.b.c);    // 2 —— 原對象保持不變

6.3 函數(shù)式編程與不可變思想

在需要高可靠性的項目中,常常提倡“不可變數(shù)據(jù)”和“純函數(shù)”:

  • 純函數(shù):相同輸入,必定輸出相同;且無任何副作用(不修改外部狀態(tài))。
  • 對于對象或數(shù)組參數(shù),應(yīng)在函數(shù)內(nèi)部做拷貝后再處理,并返回新值。
function addItem(arr, item) {
  // 不修改傳入的 arr,而是返回新數(shù)組
  return [...arr, item];
}

如此,即可徹底避免因“引用”導(dǎo)致的意外修改。

七、“引用傳遞”的效果

在 ES 模塊(import/export)或 CommonJS (require/module.exports) 里,看似傳遞的是「值」,但對于引用類型,獲取到的是同一個對象的引用,并且模塊只會初始化一次、結(jié)果會被緩存并在多個模塊間共享。這就產(chǎn)生了“引用傳遞”的效果——在任意一個地方修改了這個對象,別的地方都能感知到。

7.1 為什么會這樣?

  1. 模塊只會執(zhí)行并初始化一次
    當?shù)谝淮?import 或 require 一個模塊時,模塊文件里的代碼會被執(zhí)行,導(dǎo)出的對象/變量就生成并存放在模塊緩存里。后續(xù)所有對這個模塊的導(dǎo)入,都是從緩存里拿到同一個“實例”。

  2. 導(dǎo)出的是對同一份對象的引用

    • 對象、數(shù)組、函數(shù) 等引用類型,導(dǎo)出時并不會把它“復(fù)制”一份給每個導(dǎo)入者,而是把同一個對象的引用掛到每個導(dǎo)入模塊里。

    • 即便 ES 模塊對 變量 本身采用“活綁定”(live binding),也不會把對象內(nèi)容克隆一次。

  3. 共享緩存帶來“引用”效果

    // a.js
    export const settings = { mode: 'light' };
    
    // b.js
    import { settings } from './a.js';
    settings.mode = 'dark';  // 改變了 a.js 中同一個對象
    
    // c.js
    import { settings } from './a.js';
    console.log(settings.mode);  // 'dark'
    

    無論在 b.js 還是 c.js 中修改 settings,因為它們都是指向同一個對象,所以彼此可見。

CommonJS(Node.js)的類似行為

對于 require,也是同樣道理:

// config.js
module.exports = { url: 'https://api.example.com' };

// service.js
const cfg = require('./config');
cfg.url = 'https://api.dev';  // 改變了緩存里的對象

// index.js
const cfg1 = require('./config');
console.log(cfg1.url);  // 'https://api.dev'

require 會緩存 module.exports,后續(xù) require 拿到的永遠是同一個對象。

小結(jié):

  • 函數(shù)調(diào)用時,JS 始終是“按值傳參”,但當值本身是一個引用(對象/數(shù)組/函數(shù)),函數(shù)內(nèi)修改其屬性會反映到外部。

  • 模塊導(dǎo)入導(dǎo)出時,導(dǎo)出的引用類型在各個模塊間共享同一份實例,這就像把引用“傳”給每個模塊,修改同樣會被全局可見。

所以,我們所看到的“引用傳遞”,實際上是模塊系統(tǒng)的 “單例緩存 + 共享引用” 機制在起作用。

八. 總結(jié)

場景是否“引用傳遞”實際發(fā)生了什么
函數(shù)參數(shù)傳遞不是引用傳遞始終是值傳遞,但引用類型的值是指針副本,因此函數(shù)內(nèi)部能改對象內(nèi)部屬性,不能替換整個引用。
模塊 import/export表現(xiàn)像引用傳遞模塊導(dǎo)出的是同一個對象的引用,多處導(dǎo)入會共享這份引用,修改會同步反映。
對象賦值 let b = a表現(xiàn)像引用傳遞賦值的是引用的副本,修改對象內(nèi)部屬性會影響原對象。
原始類型(string, number, boolean)傳參或賦值值傳遞完全復(fù)制一份值,互不影響。

JS 永遠是值傳遞,但引用類型的“值”本質(zhì)是一個“指向?qū)ο蟮牡刂?rdquo;。

所以可以改對象內(nèi)容,卻無法改變原引用的綁定。

模塊導(dǎo)出時共享引用,看起來像引用傳遞,但那是模塊緩存機制的結(jié)果。 

到此這篇關(guān)于JavaScript中到底存不存在引用傳遞的文章就介紹到這了,更多相關(guān)JS存不存在引用傳遞內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • ToolTip 通過Js實現(xiàn)代替超鏈接中的title效果

    ToolTip 通過Js實現(xiàn)代替超鏈接中的title效果

    ToolTip 通過Js實現(xiàn)代替超鏈接中的title效果,需要的朋友可以參考下。
    2011-04-04
  • 詳解小程序如何動態(tài)綁定點擊的執(zhí)行方法

    詳解小程序如何動態(tài)綁定點擊的執(zhí)行方法

    這篇文章主要介紹了詳解小程序如何動態(tài)綁定點擊的執(zhí)行方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11
  • 輸入自動提示搜索提示功能的javascript:sugggestion.js

    輸入自動提示搜索提示功能的javascript:sugggestion.js

    該js文件中的代碼實現(xiàn)了[輸入自動搜索提示]功能,如百度、google搜索框中輸入一些字符會以下拉列表形式給出一些提示,提高了用戶體驗
    2013-09-09
  • javascript數(shù)組去掉重復(fù)

    javascript數(shù)組去掉重復(fù)

    去tx面試過幾次,基本都會考到數(shù)組去重。其實平時工作中幾乎不會用到,再者也沒認真去了解過,所以基本上每次面到這里都會露出很大的馬腳,面試自然也over了
    2011-05-05
  • 淺談webpack4.x 入門(一篇足矣)

    淺談webpack4.x 入門(一篇足矣)

    這篇文章主要介紹了淺談webpack4.x 入門(一篇足矣),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • 7道關(guān)于JS this的面試題,你能答對幾個

    7道關(guān)于JS this的面試題,你能答對幾個

    這篇文章主要給大家介紹了7道關(guān)于JS this的面試題,來看看你能答對幾個,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • JS控制鼠標拒絕點擊某一按鈕的實例

    JS控制鼠標拒絕點擊某一按鈕的實例

    下面小編就為大家分享一篇JS控制鼠標拒絕點擊某一按鈕的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • js中幾種循環(huán)的退出方式實例總結(jié)

    js中幾種循環(huán)的退出方式實例總結(jié)

    提到在一段程序中如果碰到需要終止,結(jié)束一個循環(huán),函數(shù)或者一段代碼,一般會想到以下這幾個關(guān)鍵字return、continue、break,這篇文章主要給大家介紹了關(guān)于js中幾種循環(huán)的退出方式,需要的朋友可以參考下
    2022-12-12
  • javascript中直接寫php代碼的方法

    javascript中直接寫php代碼的方法

    這篇文章介紹了javascript中直接寫php代碼的方法,有需要的朋友可以參考一下
    2013-07-07
  • 微信小程序自定義日期選擇器

    微信小程序自定義日期選擇器

    這篇文章主要為大家詳細介紹了微信小程序自定義日期選擇器,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下<BR>
    2022-01-01

最新評論