一道面試題引發的js資料型別傳參思考

南風一濺發表於2018-12-27

1、丟擲問題

題目如下:

// 例子1,引用型別
var arr = [1,2,3]
function fn(a){
  var a = [4,5,6]
}
fn(arr)
arr; // [1, 2, 3]
複製程式碼

為什麼arr傳進去沒有任何變化,難道作為引用型別的arr不應該變成[4, 5, 6]?
既然引用型別沒變化,試一波基本資料型別:

// 例子2,基本資料型別
var x = 1;
function fx(ax){
  var ax = 2;
}
fx(x);
x; // 1
複製程式碼

很神奇,基本資料型別x也沒有任何變化。why?
實際上,原理很簡單:

1、函式呼叫中發生的資料傳送是單向的。 即只能把實參的值傳送給形參,而不能把形參的值反向地傳送給實參。 因此在函式呼叫過程中,形參的值發生改變,而實參中的值不會變化。
2、當形參和實參不是指標型別變數時,在該函式執行時,形參和實參是不同的變數,他們在記憶體中位於不同的位置,形參將實參的內容複製一份,在該函式執行結束的時候形參被釋放,而實參內容不會改變。
3、當函式的引數是指標型別變數,在呼叫該函式的過程中,傳給函式的是實參的地址,在函式體內部使用的也是實參的地址,即使用的就是實參本身。所以在函式體內部可以改變實參的值。

2、js資料型別在記憶體中的存放位置

原理有點繞,舉個例子前先講下js中基本資料型別、引用資料型別在記憶體中的存放位置

2.1、基本資料型別存放在棧區

基本的資料型別有:undefined,boolean,number,string,null、symbol
舉個簡單的例子:

var x = 1;
var y = 2;
複製程式碼

x、y在記憶體中的存放位置如下圖:

棧區
例子2的程式碼沒啥好分析的,就是函式內的形參不影響實參,所以外部的x不會有變化

2.2、引用型別的值同時儲存在棧記憶體和堆記憶體中

例子1的arr在記憶體中的存放位置:

一道面試題引發的js資料型別傳參思考

由記憶體位置來分析例子1

// 例子1,引用型別
var arr = [1,2,3]
function fn(a){
  var a = [4,5,6]
}
fn(arr);
arr; // [1, 2, 3]
複製程式碼

分析:

1、呼叫fn(arr),這裡傳入的是arr變數名,即arr的堆記憶體地址
2、在函式fn內部接收到變數是一串地址,形參和實參都不是指標型別變數
3、通過原理2可知,所以不會對外部的arr資料產生影響

相當於1=2,並不會影響棧區的內容

再看一個例子

// 例子3,改變引用型別
var arr = [1,2,3]
function fn(a) {
  a[0] = 4
}
fn(arr);
arr; // [4, 2, 3]
複製程式碼

分析:

1、呼叫fn(arr),這裡傳入的是arr變數名,即arr的堆記憶體地址
2、函式fn內呼叫a[0],即訪問arr的堆記憶體地址指向的其在堆區的資料arr[0],即改變了arr[0]在堆區存放的內容1→4
3、所以再次呼叫arr,其堆記憶體地址指向的堆區資料已經改變為[4, 2, 3]

同為引用型別的Object也是一樣。

3、賦值

3.1、基本型別賦值

var x = 1;
var y = x;
y = 2;
x; // 1
複製程式碼

同樣以記憶體分析上面的程式碼:

一道面試題引發的js資料型別傳參思考
基本型別的賦值是在棧記憶體中開闢一塊新的儲存區域存放變數,彼此間相互獨立,所以改變其中的一個值,不會影響到另一個。

var y = x; 先在棧區開闢一塊新記憶體儲存變數y,再複製一份x的值存到y
y=2; 改變的是棧區y地址的資料1→2,所以x的值保持不變

3.2、引用型別賦值

var obj1 = {x: 1, y: 2};
var obj2 = obj1;
obj2.x = '你猜';
obj1; // {x: '你猜', y: 2}
複製程式碼

同樣以記憶體分析上面的程式碼:

一道面試題引發的js資料型別傳參思考
引用型別變數在棧區開闢記憶體存放變數名以及指向堆區資料的地址,在堆區開闢記憶體存放資料:

var obj2 = obj1; 在棧區開闢一塊新地址存放變數obj2,obj2的堆記憶體地址指向堆區的物件{x:1, y:2}
obj2.x = '你猜'; 改變了堆區的資料1→"你猜"
obj1和obj2指向堆區的同一個記憶體地址的資料,所以obj1.x的結果也改變

下面這個不會有影響:

var obj1 = {x: 1, y: 2};
var obj2 = obj1;
obj2 = {x: 1, y: 2, z: 3};
obj2.x = 4;
obj1; // {x: 1, y: 2}
複製程式碼

一圖勝千言:

一道面試題引發的js資料型別傳參思考
obj2開始指向與obj1堆區地址一樣的資料,在obj2 = {x: 1, y: 2, z: 3};執行後就轉而指向了資料{x: 1, y: 2, z: 3}在堆區存放的地址,所以後面對obj2做任何操作都不會影響obj1

擴充套件

由上自然而然想到深淺拷貝,轉下篇js的深拷貝和淺拷貝

相關文章