天天看點

深入分析js中的this、constructor 和prototype

this表示目前對象,如果在全局作用範圍内使用this,則指代目前頁面對象window; 如果在函數中使用this,則this指代運作時此函數在什麼對象上被調用。 我們還可以使用apply和call兩個全局方法來改變函數中this的具體指向

先看一個在全局作用範圍内使用this的例子:

函數中的this是在運作時決定的,而不是函數定義時,如下:

全局函數apply和call可以用來改變函數中this的指向

注:apply和call兩個函數的作用相同,唯一的差別是兩個函數的參數定義不同

因為在javascript中函數也是對象,是以我們可以看到如下有趣的例子:

prototype本質上還是一個javascript對象。并且每個函數都有一個預設的prototype屬性。 如果這個函數被用在建立自定義對象的場景中,我們稱這個函數為構造函數

隻有構造函數才有prototype屬性,而構造函數的執行個體是沒有該屬性的。在javascript中,每個函數都自動有一個prototype屬性,而不是每一個對象擁有prototype屬性

原型屬性與執行個體對象的建立與否沒有關系,它在對象建立之前就已經存在

作為類比,我們考慮下javascript中的資料類型 - 字元串(string)、數字(number)、數組(array)、對象(object)、日期(date)等。我們有理由相信,在javascript内部這些類型都是作為構造函數來實作的

同時對數組操作的很多方法(比如concat、join、push)應該也是在prototype屬性中定義的。 實際上,javascript所有的固有資料類型都具有隻讀的prototype屬性 (這是可以了解的:因為如果修改了這些類型的prototype屬性,則那些預定義的方法就消失了), 但是我們可以向其中添加自己的擴充方法

function定義的對象有一個prototype屬性,使用new生成的對象就沒有這個prototype屬性

prototype屬性又指向了一個prototype對象,注意prototype屬性與prototype對象是兩個不同的東西,要注意差別。在prototype對象中又有一個constructor屬性,這個constructor屬性同樣指向一個constructor對象,而這個constructor對象恰恰就是這個function函數本身

上面的代碼證明了one這個對象沒有prototype屬性

要解釋這個結果就要仔細研究一下new這個操作符了.var one=new person('js');這個語句執行的過程可以分成下面的語句:

new形式建立對象的過程實際上可以分為三步:

第一步是建立一個新對象(叫a吧)

第二步将該對象(a)内置的原型對象設定為構造函數(就是person)prototype 屬性引用的那個原型對象

第三步就是将該對象(a)作為this 參數調用構造函數(就是person),完成成員設定等初始化工作

其中第二步中出現了一個新名詞就是内置的原型對象,注意這個新名詞跟prototype對象不是一回事,為了差別我叫它inobj,inobj就指向了函數person的prototype對象。在person的prototype對象中出現的任何屬性或者函數都可以在one對象中直接使用,這個就是javascript中的原型繼承了

這樣one對象通過内置的原型對象inobj就可以直接通路person的prototype對象中的任何屬性與方法了。這也就解釋了上面的代碼中 為什麼one可以通路form函數了。因為prototype對象中有一個constructor屬性,那麼one也可以直接通路constructor 屬性

constructor始終指向建立目前對象的構造函數,傳回對建立此對象的數組函數的引用

舉例:

但是當constructor遇到prototype時,有趣的事情就發生了。 我們知道每個函數都有一個預設的屬性prototype,而這個prototype的constructor預設指向這個函數

當時當我們重新定義函數的prototype時(注意:和上例的差別,這裡不是修改而是覆寫), constructor的行為就有點奇怪了

為什麼呢? 原來是因為覆寫person.prototype時,等價于進行如下代碼操作:

而constructor始終指向建立自身的構造函數,是以此時person.prototype.constructor === object,即是:

怎麼修正這種問題呢?方法也很簡單,重新覆寫person.prototype.constructor即可:

也可以這麼寫:

繼續閱讀