這兩天在學習原型鏈相關的知識,就來隨便談談簡單型別與複雜型別、原型鏈的一些體會
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`
如果有錯誤或者不嚴謹的地方,歡迎給予指正,十分感謝