本文講述了在JavaScript中,基本型別與引用型別值的概念及區別
引入概念:基本型別和引用型別
- 可以感受到,JS的變數及其鬆散,那麼,正是 JS變數鬆散 的本質,決定了:
JS變數名只是 一個在特定的時間用於儲存特定值的一個名字 而已,也就是說,變數的值及其資料型別可以在指令碼的生命週期內改變 ,儘管這個功能看起來有趣、強大,但是JS變數實際上是比較複雜, - ECMAScirpt 變數有兩種不同的資料型別:
基本型別
和引用型別
,另外還有其他的叫法,比如:原始型別和物件型別、擁有方法的型別和不能擁有方法的型別 等等 - 將一個值賦值給變數時,解析器 必須確定這個值是基本型別值還是引用型別值
基本型別 指的是簡單的資料段,而 引用型別 指的是可能由多個值構成的物件
基本型別:undefined
、null
、string
、number
、boolean
、symbo(ES6)
引用型別:Object
、Array
、RegExp
、Date
、Function
兩種型別的區別
- 儲存:
- 基本型別的值是存放在 棧區 的,即記憶體中的棧記憶體
- 假如有以下變數:
var name = 'jozo'; var city = 'guangzhou'; var age = 22;
- 那麼他們的儲存結構如下:(棧區包括了變數的識別符號和值)
- 假如有以下變數:
- 引用型別的值是同時儲存在 棧記憶體和堆記憶體 的
- 假如有以下物件:
var person1 = {name:'jozo'}; var person2 = {name:'xiaom'}; var person3 = {name:'xiaoq'};
- 那麼他們的儲存結構如下:
- 假如有以下物件:
- 基本型別的值是存放在 棧區 的,即記憶體中的棧記憶體
- 訪問:
- 基本型別的值是 按值訪問 的,因為可以操作儲存在變數中的實際的值。
- 引用型別的值是 按引用訪問 的,因為引用型別的值是儲存在記憶體中的物件,而與其他語言不同的是,JavaScript 不允許直接訪問記憶體中的位置,即不可以直接操作物件的記憶體空間,那麼,在操作物件時,實際上是在操作物件的引用而不是實際的物件。
- 動態的屬性:
- 對於引用型別的值,很明顯,我們可以為其 新增、改變、刪除 屬性和方法:
var person = new Object(); person.name = "Ozzie"; console.log(person.name); //"Ozzie"
- 上述過程中,我們建立了一個物件併為其新增了一個屬性,如果物件不被銷燬或者這個屬性不被刪除,那麼這個屬性將一直存在
- 但是,我們不可以給基本型別的值新增方法和屬性
var name = "Ozzie"; name.age = 19; consoe.log(name.age); //undefined
- 儘管這樣操作不會報錯,但是仍然會被默默地掛掉
- 對於引用型別的值,很明顯,我們可以為其 新增、改變、刪除 屬性和方法:
- 比較:
- 基本型別的比較是 值的比較 :
- 例如:
var a = 1; var b = true; console.log(a == b); //true
- 例如:
- 引用型別的比較是 引用的比較 :
- 例如:
var person1 = {}; var person2 = {}; console.log(person1 == person2); //false
- 上文提到過,引用型別時按引用訪問的,換句話說就是比較兩個物件的堆記憶體中的地址是否相同,很明顯,並不是同一個記憶體位置:
- 例如:
- 基本型別的比較是 值的比較 :
- 複製變數值:
- 將一個基本型別的值複製給另一個變數,那麼,會在新變數上建立一個新值,然後再把該值複製到為新變數分配的位置上:
- 例如:
var a = 10; var b = a; a++; console.log(a); // 11 console.log(b); // 10
- a與b是完全獨立的,該值只是a中的值的一個副本。
- 基本型別在賦值操作後,兩個變數是相互不受影響的:
- 例如:
- 那麼,複製引用型別的值時,同樣也會將一份儲存在物件中的值複製到新變數的空間中,不同的是,這個值的副本實際上是一個 指標,指向的是儲存在堆中的物件。也就是說,複製結束後,這兩個變數將引用同一個物件。
- 例如:
var a = {}; // a儲存了一個空物件的例項 var b = a; // a和b都指向了這個空物件 a.name = 'jozo'; console.log(a.name); // 'jozo' console.log(b.name); // 'jozo'
- 改變其中一個變數就會影響到另一個變數
- 例如:
- 將一個基本型別的值複製給另一個變數,那麼,會在新變數上建立一個新值,然後再把該值複製到為新變數分配的位置上:
- 傳遞引數:
- 請記住,儘管在訪問變數時有著按值訪問和按引用訪問這兩種方式,但 ECMAScript 中所有的函式的引數都是按值傳遞的,即引數只能按值傳遞,也就是說,把函式外部的值複製給函式內部的引數,就類似於變數之間的值複製一樣
- 基本型別的值的傳遞如同基本型別的變數的複製,被傳遞的值會被賦值給一個區域性變數(即命名引數,用 ECMAScript 中的概念說,就是 arguments 物件中的一個元素),此處不再贅述…
- 但是向引數傳遞引用型別的值時,複製給區域性變數的是 記憶體中的地址,因此這個區域性變數的變化會被反映在函式的外部。
- 例如:
function setName(obj){ obj.name = "Ozzie"; } var person = new Object(); setName(person); console.log(person.name); //"Ozzie"
- 我們可以看到,在函式內部,
obj
和person
引用的是同一個物件,換句話說,即使這個變數是按值傳遞的,obj
也會按引用來訪問同一個物件,因為person
指向的物件在堆記憶體中只有一個,而且是全域性物件。 - 很多人會 錯誤地認為:引數是按引用傳遞的,因為在區域性作用域中修改的引數會在全域性作用域中反映出來,OK,那麼我們再看一個例子:
function setName(obj){ obj.name = "Ozzie"; obj = new Object(); obj.name = "Nicholas" } var person = new Object(); setName(person); console.log(person.name); //Ozzie
- 如果是按引用傳遞引數的,那麼顯然
person
物件就會在函式內部自動修改name
屬性為Nicholas
,但結果仍然是Ozzie
,這說明,即使在函式內部修改了引數的值,但原始的引用仍然保持不變,實際上,在函式內部重寫obj
時,這個變數的引用就是一個區域性物件了,而這個區域性物件在函式執行完畢後立即被銷燬。
- 例如:
本作品採用《CC 協議》,轉載必須註明作者和本文連結