簡單型別與複雜型別及原型鏈

YyzclYang發表於2018-05-16

這兩天在學習原型鏈相關的知識,就來隨便談談簡單型別與複雜型別、原型鏈的一些體會

1 簡單型別與複雜型別

我們知道在JavaScript中宣告變數有幾種方式,拿數值來做例子

var n1 = 1;
var n2 = new Number(1);
複製程式碼

那麼這兩種宣告方式有什麼區別呢?首先來列印這兩個變數來看看

簡單型別與複雜型別及原型鏈

從圖中可以看到n1就只有一個數值,而n2除了有一個私有值1之外,還有一個__proto__,裡面放了很多東西。記憶體圖如下
簡單型別與複雜型別及原型鏈

從圖中我們可以看到n1是直接存放在棧記憶體中的一個值,而n2其實是存放在堆記憶體中,棧記憶體中只存放了一個地址,指向堆記憶體
那既然有這些差別,那我們為什麼在實際的使用過程中沒有感覺呢?n2所有的函式等等,用n1也全部都可以使用,看起來好像和記憶體圖表示的不一樣啊?

n1.toString(); // "1"
複製程式碼

n1看起來好像只有一個數值,並沒有那些函式,為什麼可以呼叫呢?其實這是由於JavaScript的獨特設計造成的
當執行上面那段程式碼的時候,其實JavaScript重新宣告一個臨時物件temp,利用new Number()的方式為temp賦值為1,那麼temp就和n2的儲存方式一樣的了,再將temp.toString()的值賦給n1.toString(),然後清除temp,好像temp從未存在過,而n1也擁有了看似只有n2才有的屬性

接著執行下面的語句會發生什麼?

n1.name = 'yyzcl';
n1.name // ?
複製程式碼

簡單型別與複雜型別及原型鏈

從圖中看明明n1.name賦值成功了,呼叫的時候怎麼就沒有呢?
其中的過程看可以看看記憶體圖
簡單型別與複雜型別及原型鏈

這是n1.name = 'yyzcl';執行時的場景,JavaScript宣告瞭一個臨時物件來存放n1.name的值,所以並未報錯。而當它執行n1.name時,由於臨時物件在賦值語句之後隨即被清除了,n1實際上還是隻存在於棧記憶體中。
當要搜尋n1.name的值時,再建立一個臨時物件,在臨時物件中去搜尋.name這個值,由於新建的臨時物件並沒有.name這個值,所以會返回undefined
簡單型別與複雜型別及原型鏈


2 原型與原型鏈

從上面我們可以知道,當利用

var n1 = new Number();
var n2 = new Number();
……
複製程式碼

這類語法宣告多個變數時,如果要在堆記憶體中為每個變數開闢一個空間,單獨存放每個變數的屬性,那麼就太浪費記憶體空間了。JavaScript就設計了一個特性,在記憶體中開闢一個空間存放變數共同的屬性,然後每個變數獨特的屬性分開開闢空間存放,並存放一個地址,指向相同存放變數共同屬性的地址。

簡單型別與複雜型別及原型鏈

這裡就涉及到原型鏈了,以數值字串物件為例來看,宣告一個變數,首先這個變數的私有屬性有一個空間,然後還指向變數所屬資料型別的一個共有屬性空間,這個共有屬性空間還指向Object的共有屬性空間,具體關係如圖所示
簡單型別與複雜型別及原型鏈

每個型別的共有屬性會存放到一起,並用一個地址指向它,這樣既可以節省空間,還不影響使用。

數值共有屬性就是數值的原型,物件共同屬性就是物件的原型


其實每個原型還有一個`constructor`屬性,它指向引用它的上一級原型,例如:數值原型的`constructor`就指向具體的數值變數

總結一下,由相互關聯的原型組成的鏈狀結構就是原型鏈

3 __proto__與prototype

首先扔一張圖

簡單型別與複雜型別及原型鏈

給一個公式

var 物件 = new 函式()
物件.__proto__ === 函式.prototype

函式.prototype也可以看做一個物件,於是就有了

物件.__proto__.__proto__ === 函式.prototype.__proto__ === 函式.prototype.prototype


一直可以追尋到`Object.prototype`就結束了,因為`Object.prototype.__proto__`的值為`null`

如果有錯誤或者不嚴謹的地方,歡迎給予指正,十分感謝

相關文章