JavaScript 原型和對象建立底層原理

1. prototype/__proto__/constructor

JS原型鏈和繼承網上已經爛大街了,5毛能夠買一堆,這裏只提一下:javascript

constructor:普通對象和函數對象都有,指向建立它的函數
prototype: 函數對象纔有,指向構造函數的原型對象(另外一個普通對象)
__proto__: 普通對象和函數對象都有,指向建立它的構造函數的原型對象java

function Fun1(){};
Fun1.prototype.constructor == Fun1 //true
var f2 = new Fun1();
f2.__proto__ == Fun1.prototype //true

2. 對象實例的建立過程

當實例化新對象時,JS將使用與來自相同構造函數的先前對象相同的初始隱藏類建立它們。
當添加屬性時,對象從一個隱藏類轉換爲另一個隱藏類,一般遵循所謂的「轉換樹」中的先前轉換。例如,若是咱們有如下構造函數:緩存

function fun1(){
    this.a = 1;
    this.b = 2;
}

建立過程大體以下(對於普通對象{a:1, b:2}也會有相似的過程):
fun1:  M0  {}
     |
this.a: M1  {a:?}
     |
this.b: M2  {a:?,b:?}函數

  • 初始化一個對象時(如: var o = new fun1()),會使用一個附加在fun1上的沒有任何屬性的初始隱藏類M0。
  • 當添加一個屬性a時,會從隱藏類M0轉變爲M1以描述新的屬性a。
  • 當再次添加屬性b時,會獲得M2來體現屬性a和b。

在第二次用一樣的構造函數建立一個新的對象實例時(如: var o2 = new fun1()),會複用M0,M1和M2。性能

這種機制有下面幾個優勢:優化

  1. 只有第一次建立對象實例時會有比較大的性能開銷,接下來若是再次建立實例時,將會複用以前的緩存,速度很快。
  2. 這種方法建立的對象實例一般比採用建立整個屬性字典的對象實例要小,僅須要存儲值在對象實例中而沒必要存儲屬性相關信息。
  3. 使用惟一的位置來存儲隱藏類,能夠很快的被再次使用。

能夠參考以下:
     M0
     {}
     |
     M1
    {a:?}
    /  \
   M2  M3
{a:?,b:?}  {a:?,c:?}this

3. prototype的建立過程

與實例的建立過程不一樣,原型的建立過程並不會使用隱藏類,由於原型prototypes一般狀況下是一個惟一的對象,而且不會被其餘的不一樣對象所共享結構。prototype

  • 不一樣對象將擁有不一樣的原型,共享原型並不會帶來性能上的收益,所以沒有必要。
  • 原型一般只會建立一次,爲原型建立隱藏類只會帶來不少無用的內存碎片。

Prototype建立有二個主要的階段:code

  1. 創建
    原型在創建階段使用dictionary鍵值對存儲,速度很是快。對比隱藏類的創建過程,使用鍵值對存儲不會切換到底層運行環境。
  2. 使用
    任何對原型的訪問,或者經過原型鏈的訪問,都會切換到使用階段。
function Foo() {}
// Prototype在'創建'模式
var proto = Foo.prototype;
proto.method1 = function() { ... }
proto.method2 = function() { ... }

var o = new Foo();
// 從'創建'模式切換到'使用'模式
o.method1();

// 一樣切換到'使用'模式
proto.method1.call(o);

4. prototype優化

那麼瞭解上面的原型建立過程有什麼用呢?
JS很難在編譯階段進行代碼分析,即便某個類被用做原型。當咱們發現一個類被當作原型,如:對象

var o = {x:1};
func.prototype = o;

由於原型是能夠改變的,所以以上代碼並不能肯定O被用做原型,除非直到全部步驟結束。
因此:JS不得不首先進行隱藏類的建立過程,並轉化爲原型創建過程,這很是消耗性能。

那麼應該怎麼作呢?以下:

var o = {};
func.prototype = o;
o.x = 1;

若是一個對象要做爲原型,那麼儘可能在給對象添加屬性以前就把該對象賦給原型屬性。

更好的方式是下面這種:

func.prototype = Object.create(…);
func.prototype.method1 = …
func.prototype.method2 = …

最後,優雅的寫法是:

var proto = func.prototype = Object.create(…);
proto.method1 = …
proto.method2 = …
相關文章
相關標籤/搜索