js中的函數(shù)有兩種調(diào)用方式,一種是通過普通的聲明之后進(jìn)行的調(diào)用。一種是通過new關(guān)鍵字進(jìn)行構(gòu)造調(diào)用。普通的調(diào)用就是依次執(zhí)行函數(shù)內(nèi)部的函數(shù)語句,如果有返回值則返回返回值,如果沒有則函數(shù)內(nèi)部的聲明周期結(jié)束。但是,函數(shù)還有另一個調(diào)用方式,使用new關(guān)鍵字,將函數(shù)當(dāng)做構(gòu)造函數(shù)。js中沒有所謂獨(dú)立出來的構(gòu)造函數(shù)的概念,所有的函數(shù)都用同樣的方式聲明,所以有了new這個關(guān)鍵字,js(ES6之前)只能通過這種方式實(shí)現(xiàn)構(gòu)造器的構(gòu)造。那么使用new關(guān)鍵字跟普通的調(diào)用有什么區(qū)別呢?
使用new關(guān)鍵字,比普通的函數(shù)調(diào)用,主要分為以下四個步驟:
- 建立一個空對象 。
- 將函數(shù)內(nèi)部的this修正到上面新建的對象上來。
- 繼續(xù)執(zhí)行函數(shù)的其它語句,并且將上面新建的對象的[[prototype]]屬性(ES6中稱為"__proto__")指向該構(gòu)造函數(shù)。
- 如果函數(shù)沒有返回值或著函數(shù)有返回值,但是該返回值不是對象的話,則返回第一步建立的對象;如果函數(shù)有返回值,且返回值是對象的話,則忽略第一步建立的對象,返回默認(rèn)返回的對象。
稍后我們會對上面的四句話逐一解析,首先我們看一看函數(shù)調(diào)用的兩種方式:
var fun = function () {
var a = 2;
console.log(a * 2);
}
//新建函數(shù)fun
fun(); //4
new fun(); //4
這樣看來,兩者似乎沒有區(qū)別,但是這里要注意,使用new進(jìn)行構(gòu)造調(diào)用時,函數(shù)是有返回值的。
...
var return1 = fun(); //4
var return2 = new fun(); //4
console.log(return1); //undefined
console.log(return2); //fun {}
上面的return2并沒有返回一個函數(shù)。
...
typeof return2; //object
Object.prototype.toString.call(return2); //[object Object]
其實(shí)它會返回一個空的對象。這也就是上面使用new關(guān)鍵詞的第一步,內(nèi)部創(chuàng)建一個新的空對象。
那么當(dāng)函數(shù)內(nèi)部有this時,結(jié)果會是怎樣的呢?
var fun = function () {
this.a = 2;
console.log(this.a * 2);
}
fun(); //4
new fun(); //4
var return1 = fun(); //4
var return2 = new fun(); //4
console.log(return1); //undefined
console.log(return2); //{ a: 2 }
使用new關(guān)鍵字后,在函數(shù)內(nèi)部如果出現(xiàn)了this,則自動將this指向內(nèi)部新建的對象上。最后返回時,因?yàn)閠his的緣故,對象上新建了a屬性,并且賦值返回。
修正定義的對象Object的[[prototype]]
雖然實(shí)例上的[[prototype]]屬性__proto__是ES6才作為規(guī)范出版的。但是在這之前chrome已經(jīng)支持__proto__屬性,他指向?qū)ο蟮脑汀?/p>
原型的問題相當(dāng)復(fù)雜,單獨(dú)拿出來也可以當(dāng)好幾篇文章的量來講。但是這并不是本文的重點(diǎn)。但是每一個對象從根部來說,繼承自O(shè)bject。而Object.prototype上面定義了一些方法,有類似toString,valueOf等等等方法。對于對象來說,支持通過屬性鏈和方法鏈向上查詢。所以在一個對象實(shí)例中,如果沒有定義toString方法,但它還可以向上查詢,找到原型中的toString方法,進(jìn)行調(diào)用。
同時的,有很多元素通過Object實(shí)現(xiàn)繼承。比如Function, Array, RegExp等等對象,它們也是對象,但是卻是繼承來自O(shè)bject。
在這里,內(nèi)部定義的對象,讓他繼承來自構(gòu)造函數(shù)。
最后一步,也是最容易被忽略的一步,那就是當(dāng)構(gòu)造函數(shù)存在返回值時,并且返回值為對象時,返回對象而不返回之前定義的對象。
var fun = function () {
this.a = 1;
return {
a: 2
}
}
var obj = new fun(); //{ a:2 }
當(dāng)然,上面說的Function,Array,RegExp也算Object的一種,如果返回他們同樣也會阻止默認(rèn)的對象返回。
var fun = function () {
this.a = 1;
return function () {
this.a = 2;
}
}
var obj = new fun(); //function () { this.a = 2; }