一篇文中告訴你JS中的"值傳遞"和"引用傳遞"
前言
現(xiàn)代的前端開發(fā),不再是刀耕火種的 JQ 時(shí)代,而是 MVVM ,組件化,工程化,承載著日益復(fù)雜的業(yè)務(wù)邏輯。內(nèi)存消耗和性能問題,成為當(dāng)代開發(fā)者必須要考慮的問題。
本文從堆棧內(nèi)存講起,讓大家理解JS中變量的內(nèi)存使用以及變動(dòng)情況 。
初步了解堆棧
先初步了解JS中的堆和棧,內(nèi)存空間分為 堆和棧 兩個(gè)區(qū)域,代碼運(yùn)行時(shí),解析器會(huì)先判斷變量類型,根據(jù)變量類型,將變量放到不同的內(nèi)存空間中(堆和棧)。
如圖所示

堆棧和類型的關(guān)系
基本的數(shù)據(jù)類型(String,Number,Boolean,Null,Undefined, Symbol)都會(huì)分配棧區(qū)。它的值是存放在棧中的簡(jiǎn)單數(shù)據(jù)段,數(shù)據(jù)大小確定,內(nèi)存空間大小可以分配;按值存放,所以可以按值訪問。
引用數(shù)據(jù)類型 Object (對(duì)象)的變量都放到堆區(qū)。它在棧內(nèi)存中保存的實(shí)際上是對(duì)象在堆內(nèi)存中的引用地址, 通過這個(gè)引用地址可以快速查找到保存在堆內(nèi)存中的對(duì)象。存放在堆內(nèi)存中的對(duì)象,每個(gè)空間大小不一樣,要根據(jù)情況進(jìn)行特定的配置。
如下代碼示例:
var a = 12;
var b = false;
var c = 'string'
var obj = { name: 'sunshine' }
特點(diǎn)
棧區(qū)的特點(diǎn):空間小,數(shù)據(jù)類型簡(jiǎn)單,讀寫速度快,一般由JS引擎自動(dòng)釋放
堆區(qū)的特點(diǎn):空間大,數(shù)據(jù)類型復(fù)雜,讀寫速度稍遜,當(dāng)對(duì)象不在被引用時(shí),才會(huì)被周期性的回收。
了解了內(nèi)存的棧區(qū)和堆區(qū)后, 接下來,來看看變量如何在棧區(qū)和堆區(qū)“愉快的玩耍”。
變量賦值
下面來看一組基本類型的變量傳遞的例子:
let a = 100 let b = a a = 200 console.log(b) // 100
初始棧中 a 的值為100;其次棧區(qū)中添加 b,并且將a復(fù)制了一份給b;最后 a保存了另外一個(gè)值 200,而b的值不會(huì)改變。
再來看一組引用類型傳遞的例子:
let obj1 = { name: 'a' }
let obj2 = obj1
obj2.name = 'b'
console.log(obj1.name) // b以上代碼中,obj1 和 obj2 指向了同一個(gè)堆內(nèi)存,obj1 賦值給 obj2,實(shí)際上這個(gè)堆內(nèi)存對(duì)象在棧內(nèi)存的引用地址復(fù)制了一份給了 obj2,所以 obj1 和 obj2 指針都指向堆內(nèi)存中的同一個(gè)。
圖解如下:

綜合案例:
var a = [1, 2, 3, 4] var c = a[0] // 這時(shí)變量c是基本數(shù)據(jù)類型,存儲(chǔ)在棧內(nèi)存中;改變棧中的數(shù)據(jù)不會(huì)影響堆中的數(shù)據(jù) c = 5 console.log(c) // 5 console.log(a[0]) // 1 let b = a // b是引用數(shù)據(jù)類型,棧內(nèi)存指針和 a一樣都指向同一個(gè)堆內(nèi)存,改變數(shù)值后,會(huì)影響堆中的數(shù)據(jù) b[2] = 6 console.log(a[2]) // 6
劃重點(diǎn):在JS的變量傳遞中,本質(zhì)上都可以看成是值傳遞,只是這個(gè)值可能是基礎(chǔ)數(shù)據(jù)類型,也可能是一個(gè)引用地址,如果是引用地址,我們通常就說為引用傳遞。JS中比較特殊,不能直接操作對(duì)象的內(nèi)存空間,必須通過指針(所謂的引用)來訪問。
所以,即使是所有復(fù)雜數(shù)據(jù)類型(對(duì)象)的賦值操作,本質(zhì)上也是值傳遞。在往下看一下不同的值在參數(shù)中是如何傳遞的。
參數(shù)傳遞
由上可知,ECMAScript中所有函數(shù)的參數(shù)都是按值傳遞的。這意味著函數(shù)外的值會(huì)被復(fù)制到函數(shù)內(nèi)部的參數(shù)中,就像從一個(gè)變量賦值到另一個(gè)變量一樣。在按值傳遞參數(shù)時(shí),值會(huì)被復(fù)制到一個(gè)局部變量(arguments對(duì)象中的一個(gè)槽位)。在按引用傳遞參數(shù)時(shí),值在內(nèi)存中的位置會(huì)被保存在一個(gè)局部變量,這意味著對(duì)本地變量的修改會(huì)反映到函數(shù)外部。
下面看一個(gè)例子:在 bar 函數(shù)中,當(dāng)參數(shù)為基本數(shù)據(jù)類型時(shí),函數(shù)體內(nèi)會(huì)賦值一份參數(shù)值,而不會(huì)影響原參數(shù)的實(shí)際值。
let foo = 1
const bar = value => {
// var value = foo
value = 2
console.log(value)
}
bar(foo) // 2
console.log(foo) // 1如果將函數(shù)參改為引用類型,結(jié)果就不一樣了:
let foo = { bar: 1}
const func = obj => {
// var obj = foo
obj.bar = 2
console.log(obj.bar)
}
func(foo) // 2
console.log(foo.bar) // 2從以上代碼中可以看出,如果函數(shù)參數(shù)是一個(gè)引用類型的數(shù)據(jù),那么當(dāng)在函數(shù)體內(nèi)修改這個(gè)引用類型參數(shù)的某個(gè)屬性時(shí),也將對(duì)原來的參數(shù)進(jìn)行修改,因?yàn)榇藭r(shí)函數(shù)體內(nèi)的引用地址指向了原來的參數(shù)。
但是,如果在函數(shù)體內(nèi)直接修改對(duì)參數(shù)的引用,則情況又會(huì)不一樣:
let foo = { bar: 1}
const func = obj => {
// var obj = 2
obj = 2
console.log(obj)
}
func(foo) // 2
console.log(foo) // { bar: 1 }這是因?yàn)槿绻覀儗⒁粋€(gè)已經(jīng)賦值的變量重新賦值,那么它將包含新的數(shù)據(jù)或引用地址。這時(shí)函數(shù)體內(nèi)新創(chuàng)建了一個(gè)引用,任何操作都不會(huì)影響原參數(shù)的實(shí)際值。
如果一個(gè)對(duì)象沒有被任何變量指向,JavaScript引擎的垃圾回收機(jī)制會(huì)將該對(duì)象銷毀并釋放內(nèi)存。
小結(jié)
- 函數(shù)參數(shù)為基本數(shù)據(jù)類型時(shí),函數(shù)體內(nèi)賦值了一份參數(shù)值,任何操作都不會(huì)影響原參數(shù)的實(shí)際值
- 函數(shù)參數(shù)是引用類型時(shí),當(dāng)函數(shù)體內(nèi)修改這個(gè)值的某個(gè)屬性時(shí),將會(huì)對(duì)原來的參數(shù)進(jìn)行修改
- 函數(shù)參數(shù)是引用類型時(shí),如果直接修改這個(gè)值的引用地址,則相當(dāng)于在函數(shù)體內(nèi)新創(chuàng)建了一個(gè)新的引用,任何操作都不會(huì)影響原參數(shù)的實(shí)際值。
面試題
- 參數(shù)多次賦值問題
function func (person) {
person.age = 25
person = {
age: 50
}
return person
}
var person1 = {
age: 30
}
var person2 = func(person1);
console.log(person1)
console.log(person2)答案:{ age: 25 },{ age: 50 }。因?yàn)楹瘮?shù)內(nèi)部,person 第一次修改,相當(dāng)于 復(fù)制了 person1 的內(nèi)存地址給person,第二次修改是創(chuàng)建一個(gè)新的 person 變量。所以 person1 在堆內(nèi)存中的值會(huì)被修改,person 也是新的 person 變量返回的值
- 變量干擾問題
let obj1 = { x: 100, y: 200}
let obj2 = obj1
let x1 = obj1.x
obj2.x = 101
x1 = 102
console.log(obj1)答案:{ x: 101, y: 200 },x1是干擾項(xiàng),因?yàn)閛bj.x是原始類型值,所以修改后不會(huì)影響原數(shù)據(jù)的引用地址。
兩者的區(qū)別就是:
舉個(gè)例子:
值傳遞:A覺得B的房子裝修風(fēng)格很好,于是借用了B的裝修風(fēng)格。但是過了段時(shí)間A給房子里面又添加了點(diǎn)別的風(fēng)格,但是B的房子風(fēng)格還是原來的。
引用傳遞:A喜歡B的房子風(fēng)格,借用了人家的風(fēng)格,過了段時(shí)間A給家里添加了新的風(fēng)格,但是A覺得自己的風(fēng)格比B的好,于是通過B給A的地址,去B的家硬是把人家的風(fēng)格改成和自己一樣的了。
總結(jié)
到此這篇關(guān)于JS中"值傳遞"和"引用傳遞"的文章就介紹到這了,更多相關(guān)JS 值傳遞和引用傳遞內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)的3D旋轉(zhuǎn)魔方動(dòng)畫效果實(shí)例代碼
在本篇文章里小編給大家整理了關(guān)于JavaScript實(shí)現(xiàn)的3D旋轉(zhuǎn)魔方動(dòng)畫效果實(shí)例代碼,有興趣的朋友們測(cè)試下。2019-07-07
JavaScript實(shí)現(xiàn)可動(dòng)的canvas環(huán)形進(jìn)度條
這篇文章主要介紹了如何利用JavaScript canvas繪制一個(gè)可以動(dòng)的環(huán)形進(jìn)度條。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手試一試2022-02-02
如何根據(jù)url?批量下載二維碼實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了如何根據(jù)url批量下載二維碼實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
使用element-plus時(shí)重寫樣式不起作用的問題及解決方法
這篇文章給大家介紹使用element-plus時(shí)重寫樣式不起作用的問題及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-09-09
js取float型小數(shù)點(diǎn)后兩位數(shù)的方法
js中取小數(shù)點(diǎn)后兩位方法最常用的就是四舍五入函數(shù)了,前面我介紹過js中四舍五入一此常用函數(shù),這里正好用上,下面我們一起來看取float型小數(shù)點(diǎn)后兩位一些方法總結(jié)2014-01-01
javascript打造跨瀏覽器事件處理機(jī)制[Blue-Dream出品]
由于瀏覽器兼容的復(fù)雜性.打造一個(gè)較優(yōu)的跨瀏覽器事件處理函數(shù).不是件容易的事情.各大類庫也都通過了種種方案去抽象一個(gè)龐大的事件機(jī)制.2010-07-07
js 用CreateElement動(dòng)態(tài)創(chuàng)建標(biāo)簽示例
用CreateElement動(dòng)態(tài)創(chuàng)建標(biāo)簽,主要是html中常用的一些標(biāo)簽,在本文有詳細(xì)的示例,喜歡的朋友可以參考下2013-11-11
如何通過遞歸方法實(shí)現(xiàn)用json-diff渲染json字符串對(duì)比結(jié)果
JsonDiff是一個(gè)高性能json差異發(fā)現(xiàn)工具,它幾乎可以發(fā)現(xiàn)任何JSON結(jié)構(gòu)的差異,并且將錯(cuò)誤信息反饋給用戶,下面這篇文章主要給大家介紹了關(guān)于如何通過遞歸方法實(shí)現(xiàn)用json-diff渲染json字符串對(duì)比結(jié)果的相關(guān)資料,需要的朋友可以參考下2022-12-12

