JavaScript 基本資料型別和引用型別的區別詳解

Change發表於2017-02-24

前兩天看到kraaas大神的關於基本資料型別和引用型別的區別的文章覺得寫得非常不錯,就想著在其基礎上加上自己平時看到的一些知識點和理解,所以就有了以下的文章

js基本資料型別:

js基本資料型別包括:undefined,null,number,boolean,string.基本資料型別是按值訪問的,就是說我們可以操作儲存在變數中的實際的值

1. 基本資料型別的值是不可變的

任何方法都無法改變一個基本型別的值,比如一個字串:

var name = "change";
name.substr();//hang
console.log(name);//change

var s = "hello";
s.toUpperCase()//HELLO;
console.log(s)//hello

通過這兩個例子,我們會發現原先定義的變數name的值始終沒有發生改變,而呼叫substr()和toUpperCase()方法後返回的是一個新的字串,跟原先定義的變數name並沒有關係

或許有人會有以下的疑問,看程式碼:

var name = "change";
name = "change1";
console.log(name)//change1

這樣看起來name的值“改變了”其實,var name = “change”,這裡的基礎型別是string,也就是”change”,這裡的”change”是不可以改變的,name只是指向”change”的一個指標,指標的指向可以改變,所以你可以name = “change1″.此時name指向了”change1″,同理,這裡的”change1″同樣不可以改變

也就是說這裡你認為的改變只是“指標的指向改變”

這裡的基礎型別指的是”change”,而不是name,要區分清楚

2. 基本資料型別不可以新增屬性和方法

var p = "change";
p.age = 29;
p.method = function(){console.log(name)};
console.log(p.age)//undefined
console.log(p.method)//undefined

通過上面的程式碼,我們知道不能給基本型別新增屬性和方法,也再次說明基本型別是不可變的

3. 基本資料型別的賦值是簡單賦值

如果從一個變數向另一個變數賦值基本型別的值,會在變數物件上建立一個新值,然後把該值複製到為新變數分配的位置上

var a = 10;
var b = a;
a++;
console.log(a)//11
console.log(b)//10

上面的程式碼中,a中儲存的值是10.當使用a的值來初始化b時,b中也儲存了值10.但b中的10和a中的10是完全獨立的.b中的值知識a中值的一個副本.所以這兩個變數可以參與任何操作而不會相互影響.如下圖:

4. 基本資料型別的比較是值的比較

var person1 = '{}';
var person2 = '{}';
console.log(person1 == person2); // true

5. 基本資料型別是存放在棧區的

假如有以下幾個基本型別的變數:

var name = "jozo";
var city = "guangzhou";
var age = 22;

那麼它的儲存結構如下圖:

棧區包括了變數的識別符號和變數的值

js引用型別:

js中除了上面的基本型別之外就是引用型別了,也可以說就是物件了,比如:Object,Array,Function,Data等

1. 引用型別的值是可以改變的

var o = {x:1};
o.x = 2;//通過修改物件屬性值更改物件
o.y = 3;再次更改物件,給它增加一個屬性

var a = [1,2,3];
a[0] = 0;//更改陣列的一個元素
a[3] = 4;//給陣列增加一個元素

2. 引用型別可以新增屬性和方法

var person = {};
person.name = "change";
person.say = function(){alert("hello");}
console.log(person.name)//change
console.log(person.say)//function(){alert("hello");}

3. 引用型別的賦值是物件引用

先看以下程式碼:

var a = {};
var b= a;
a.name = "change";
console.log(a.name)//change;
console.log(b.name)//change
b.age = 29;
console.log(a.age)//29
console.log(b.age)//29

當從一個變數向另一個變數賦值引用型別的值時,同樣也會將儲存在變數中的物件的值複製一份放到為新變數分配的空間中.引用型別儲存在變數中的是物件在堆記憶體中的地址,所以,與基本資料型別的簡單賦值不同,這個值的副本實際上是一個指標,而這個指標指向儲存在堆記憶體的一個物件.那麼賦值操作後,兩個變數都儲存了同一個物件地址,而這兩個地址指向了同一個物件.因此,改變其中任何一個變數,都會互相影響

他們的關係如下圖:

因此,引用型別的賦值其實是物件儲存在棧區地址指標的賦值,所以兩個變數指向同一個物件,任何的操作都會互相影響。

4. 引用型別的比較是引用的比較

var person1 = {};
var person2 = {};
console.log(person1 == person2)//false

為什麼兩個物件看起來一摸一樣,但是卻不相等呢?

因為引用型別的比較是引用的比較,換句話說,就是比較兩個物件儲存在棧區的指向堆記憶體的地址是否相同,此時,雖然p1和p2看起來都是一個”{}”,但是他們儲存在棧區中的指向堆記憶體的地址卻是不同的,所以兩個物件不相等

5. 引用型別是同時儲存在棧區和堆區中的

引用型別的儲存需要在記憶體的棧區和堆區共同完成,棧區儲存變數識別符號和指向堆記憶體的地址

假如有以下幾個物件:

var person1 = {name:"change1"};
var person2 = {name:"change2"};
var person3 = {name:"change3"};

則這三個物件在記憶體中儲存的情況如下圖:

基本包裝型別(包裝物件):

先看下以下程式碼:

var s1 = "helloworld";
var s2 = s1.substr(4);

上面我們說到字串是基本資料型別,不應該有方法,那為什麼這裡s1可以呼叫substr()呢?

通過翻閱js權威指南第3.6章節和高階程式設計第5.6章節我們得知,ECMAScript還提供了三個特殊的引用型別Boolean,String,Number.我們稱這三個特殊的引用型別為基本包裝型別,也叫包裝物件.

也就是說當讀取string,boolean和number這三個基本資料型別的時候,後臺就會建立一個對應的基本包裝型別物件,從而讓我們能夠呼叫一些方法來操作這些資料.

所以當第二行程式碼訪問s1的時候,後臺會自動完成下列操作:

  1. 建立String型別的一個例項;// var s1 = new String(“helloworld”);
  2. 在例項上呼叫指定方法;// var s2 = s1.substr(4);
  3. 銷燬這個例項;// s1 = null;

正因為有第三步這個銷燬的動作,所以你應該能夠明白為什麼基本資料型別不可以新增屬性和方法,這也正是基本裝包型別和引用型別主要區別:物件的生存期.使用new操作符建立的引用型別的例項,在執行流離開當前作用域之前都是一直儲存在記憶體中.而自動建立的基本包裝型別的物件,則只存在於一行程式碼的執行瞬間,然後立即被銷燬。

相關文章