理解Javascript_06_理解對象的創建過程

作者: 笨蛋的座右銘  來源: 博客園  發布時間: 2010-10-18 21:30  閱讀: 990 次  推薦: 0   原文鏈接   [收藏]  

  在《理解Javascript_05_原型繼承原理》一文中已經詳細的講解了原型鏈的實現原理,大家都知道原型鏈是基于對象創建的(沒有對象,哪來原型),那么今天就來解析一下對象的創建過程。

  我們先來看一段簡單的代碼:

function HumanCloning(){
}

HumanCloning.prototype ={
	name:'笨蛋的座右銘'
}

var clone01 = new HumanCloning();
alert(clone01.name);//'笨蛋的座右銘'
alert(clone01 instanceof HumanCloning);//true

HumanCloning.prototype = {};

alert(clone01.name);//'笨蛋的座右銘'
alert(clone01 instanceof HumanCloning);//false

var clone02 = new HumanCloning();
alert(clone02.name);//undefined
alert(clone02 instanceof HumanCloning);//true

  復雜的理論

  JS中只有函數對象(函數)具備類的概念,因此創建一個對象,必須使用函數對象。函數對象內部有[[Construct]]方法和[[Call]]方法,[[Construct]]用于構造對象,[[Call]]用于函數調用,只有使用new操作符時才觸發[[Construct]]邏輯。注:在本例中HumanCloning這個自定義函數是一個函數對象,那么請問Object,String,Number等本地對象是函數對象嗎?答案是肯定的,這是因為本地對象都可以看作是函數的派生類型,在這個意義上,可以將它們跟用戶定義的函數等同看待。(與《理解Javascript_04_數據模型》中"內置數據類型"一節相呼應)

var obj=new Object(); 是使用內置的Object這個函數對象創建實例化對象obj。var obj={};和var obj=[];這種代碼將由JS引擎觸發Object和Array的構造過程。function fn(){}; var myObj=new fn();是使用用戶定義的類型創建實例化對象。

  注:關于函數對象的具體概念會在后續的文章中講解,現在可以將"函數對象"簡單的理解為"函數"的概念及可.

  內部的實現

  結合本例,函數對象為HumanCloning,函數對象創建的對象實例為clone01和clone02. 現在我們來看一下var clone01 = new HumanCloning();的實現細節(注:函數對象的[[Construct]]方法處理邏輯來負責實現對象的創建):

  1. 創建一個build-in object對象obj并初始化
  2. 如果HumanCloning.prototype是Object類型,則將clone01的內部[[Prototype]]設置為HumanCloning.prototype,否則clone01的[[Prototype]]將為其初始化值(即Object.prototype)
  3. 將clone01作為this,使用args參數調用HumanCloning的內部[[Call]]方法
      3.1 內部[[Call]]方法創建當前執行上下文(注:關于執行上下文,將在后續博文中講解,在《Javascript提速_01_引用變量優化》一文中已有部分講解》)
      3.2 調用HumanCloning的函數體
      3.3 銷毀當前的執行上下文
      3.4 返回HumanCloning函數體的返回值,如果HumanCloning的函數體沒有返回值則返回undefined
  4. 如果[[Call]]的返回值是Object類型,則返回這個值,否則返回obj。

  注意,如下代碼為步驟1,步驟2和步驟3的代碼解釋:

var clone01 = {};
//程序外部是無法訪問[[prototype]]的,僅用于理解
//clone01.[[prototype]] = HumanCloning.prototype;
HumanCloning.call(clone01);

  注意步驟2中, prototype指對象顯示的prototype屬性,而[[Prototype]]則代表對象內部Prototype屬性(隱式的)。構成對象Prototype鏈的是內部隱式的[[Prototype]],而并非對象顯示的prototype屬性。顯示的prototype只有在函數對象上才有意義,從上面的創建過程可以看到,函數的prototype被賦給派生對象隱式[[Prototype]]屬性,這樣根據Prototype規則,派生對象和函數的prototype對象之間才存在屬性、方法的繼承/共享關系。(即原型繼承實現原理,正是《理解Javascript_05_原型繼承原理》的內容)

  注意步驟3.4中描述,讓我們來看一個來自于懌飛的問題:

  我想,現在回答這個問題,應該是易如反掌吧。

  內存分析

  一張簡易的內存圖,并引入的函數對象的概念,同樣也解釋了上面代碼(相對來說圖不是很嚴謹,但易于理解)。在此也引出了一個問題,instanceof的實現原理,想必大家也看出了一些苗頭,instanceof的判斷依賴于原型鏈,具體實現細節,請參見后續博文。

  本地屬性與繼承屬性

  對象通過隱式Prototype鏈能夠實現屬性和方法的繼承,但prototype也是一個普通對象,就是說它是一個普通的實例化的對象,而不是純粹抽象的數據結構描述。所以就有了這個本地屬性與繼承屬性的問題。
首先看一下設置對象屬性時的處理過程。JS定義了一組attribute,用來描述對象的屬性property,以表明屬性property是否可以在JavaScript代碼中設值、被for in枚舉等。
obj.propName=value的賦值語句處理步驟如下:

  1. 如果propName的attribute設置為不能設值,則返回
  2. 如果obj.propName不存在,則為obj創建一個屬性,名稱為propName
  3. 將obj.propName的值設為value
  可以看到,設值過程并不會考慮Prototype鏈,道理很明顯,obj的內部[[Prototype]]是一個實例化的對象,它不僅僅向obj共享屬性,還可能向其它對象共享屬性,修改它可能影響其它對象。

我們來看一個示例:

function HumanCloning(){
}

HumanCloning.prototype ={
	name:'笨蛋的座右銘'
}

var clone01 = new HumanCloning();
clone01.name = 'jxl';
alert(clone01.name);//jxl
var clone02 = new HumanCloning();
alert(clone02.name);//笨蛋的座右銘

  結果很明確,對象的屬性無法修改其原型中的同名屬性,而只會自身創建一個同名屬性并為其賦值。

0
0
 
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()