var currentTime = Date()
能生成一個(gè)當(dāng)前時(shí)間的日期對(duì)象半等,var currentTime = new Date()
也能生成一個(gè)同樣的對(duì)象。如果你看過一些框架洒试,那么你會(huì)發(fā)現(xiàn)有的框架生成對(duì)象寫法是 new ClassName()扣讼,有的框架是 className()。 那么兩種方式有什么區(qū)別呢铝阐?
普通函數(shù)/方法調(diào)用
假設(shè)我們定義了一個(gè)函數(shù):
function normalFunc() {
console.log( this );
}
// 第一種調(diào)法
normalFunc();
// 第二種調(diào)法
normalFunc.call( null );
// 第三種調(diào)法
var obj = {
method: normalFunc
}
obj.method();
我們把一個(gè)函數(shù)被當(dāng)作一個(gè)普通函數(shù)或者方法調(diào)用歸為一類拖刃,其被調(diào)用時(shí)發(fā)生的主要步驟:
- 生成一個(gè)新的執(zhí)行上下文和對(duì)應(yīng)的作用域删壮。(如果對(duì)執(zhí)行上下文是什么不了解的話,可以參考我上一篇《什么是作用域和執(zhí)行上下文》)
- 把當(dāng)前函數(shù)和這個(gè)新的執(zhí)行上下文和作用域關(guān)聯(lián)起來兑牡。
2.1. 如果當(dāng)前函數(shù)是箭頭函數(shù)央碟,那么把作用域中的 environment record 對(duì)象的內(nèi)部屬性[[thisBindingStatus]]
設(shè)置成 lexical。 - 把這個(gè)執(zhí)行上下文壓入調(diào)用棧的頂部均函,即設(shè)置成運(yùn)行執(zhí)行上下文(running execution context)亿虽。
- 接下來處理當(dāng)前函數(shù)的屬性 this 的取值:
4.1. 如果當(dāng)前函數(shù)是箭頭函數(shù),那么這步就不做任何處理(因?yàn)橐呀?jīng)在步驟2.1中做了標(biāo)志位)苞也。
4.2. 如果不是箭頭函數(shù)洛勉,那么先查看當(dāng)前函數(shù)是否處在嚴(yán)格模式下。
4.2.1. 嚴(yán)格模式:this 的取值取決于如何調(diào)用當(dāng)前函數(shù)如迟,譬如上例代碼中第一種調(diào)法收毫,取值為 undefined,第二種調(diào)法取值為normalFunc.call(
的第一個(gè)參數(shù),第三種調(diào)法取值為 obj牛哺。
4.2.2. 非嚴(yán)格模式:先按4.2.1的分類獲得 this 的取值,如果是 null 或者 undefined劳吠,用全局對(duì)象代替 null 或者 undefined引润。如果 this 的取值是非空值那么把 this 指向這個(gè)非空值(注1)。
4.3. 把 this 的取值保存在作用域中的 environment record 對(duì)象的內(nèi)部屬性[[thisValue]]
中(步驟4中并非把 this 直接指向這些取值痒玩,而是把值保存在作用域特定內(nèi)部屬性中淳附,this 的尋值過程還有額外一步,下面會(huì)說明)蠢古。 - 執(zhí)行函數(shù)體奴曙。
- 把當(dāng)前執(zhí)行上下文彈出調(diào)用棧。
- 如果步驟5有返回草讶,則返回這個(gè)結(jié)果洽糟。如果步驟5沒有返回,則返回 undefined堕战。
注1:這里非空值還要判斷是原始類型(primitive value)坤溃,還是對(duì)象類型。如果是原始類型嘱丢,取值還要再把原始類型包裝成對(duì)象才能作為 this 的取值薪介。步驟中避免太繁瑣,省略了細(xì)節(jié)顧特地加上注釋越驻。
函數(shù)中 this 的取值過程(ResolveThisBinding)
結(jié)合上面描述的步驟汁政,我們來看看當(dāng)你在函數(shù)中使用 this 時(shí)(上面的步驟5中 this 已經(jīng)可用),程序時(shí)如何尋找 this 的:
- 根據(jù)當(dāng)前執(zhí)行上下文查找到對(duì)應(yīng)的 enviroment record(execution context -> scope -> environment record)缀旁。
- 判斷當(dāng)前這個(gè) record 是否存儲(chǔ)過
[[thisValue]]
记劈,如果沒有的就沿著作用域鏈向上查找,以全局作用域?yàn)榻K點(diǎn)诵棵。 - 如果找到了抠蚣,則返回。
如上所述履澳,箭頭函數(shù)本身的作用域并沒有存儲(chǔ)[[thisValue]]
嘶窄,所以其內(nèi)部使用 this 會(huì)去定義箭頭函數(shù)的地方(函數(shù))去取 this,如果取不到繼續(xù)向上查找距贷。
函數(shù)作為構(gòu)造函數(shù)調(diào)用
// 沒有繼承關(guān)系
function normalFuncAsContructor() {
// return a new object?
// return {}
// or not
// [return]
}
var o = new normalFuncAsContructor();
// 有繼承關(guān)系
function Parent(){}
function Child(){}
Child.prototype = new Parent();
var c = new Child();
我們把一個(gè)函數(shù)被當(dāng)作構(gòu)造函數(shù)柄冲,使用 new 操作符調(diào)用時(shí)發(fā)生的主要步驟:
- 新建一個(gè)普通對(duì)象,把其原型
[[Prototype]]
指向構(gòu)造函數(shù)的 prototype 屬性的值忠蝗。 - 如普通函數(shù)調(diào)用的步驟1一樣现横,生成一個(gè)新的執(zhí)行上下文和對(duì)應(yīng)的作用域,并把當(dāng)前構(gòu)造函數(shù)和兩者關(guān)聯(lián)起來。
- 把這個(gè)執(zhí)行上下文壓入調(diào)用棧的頂部戒祠。
- 把第一步生成的對(duì)象當(dāng)作 this 的取值保存到作用域中的 environment record 對(duì)象的內(nèi)部屬性
[[thisValue]]
中骇两。 - 執(zhí)行函數(shù)體。
- 把當(dāng)前執(zhí)行上下文彈出調(diào)用棧姜盈。
- 處理函數(shù)執(zhí)行的結(jié)果低千,即 new 了之后返回啥:
7.1 如果步驟5返回一個(gè)對(duì)象,那么就把這個(gè)對(duì)象作為此次 new 操作的返回值馏颂。
7.2 如果返回的不是對(duì)象示血,而且這個(gè)函數(shù)不是 generator 函數(shù),那么返回第一步生成的對(duì)象(generator 就先不在這里討論了)救拉。
知道了兩者的區(qū)別难审,我們就能在函數(shù)體里面搞文章了,你可以通過如下代碼檢測用戶怎么調(diào)用你的函數(shù)亿絮。如果你知道了用戶怎么調(diào)用告喊,你自然可以根據(jù)你想要的結(jié)果限制用戶的使用方法。
function myFunc() {
if ( this && myFunc.prototype.isPrototypeOf( this ) ) {
console.log( 'called by new operator' );
} else {
console.log( 'commonly invoked' );
}
}
myFunc(); // commonly invoked
new myFunc(); // called by new operator
var obj = {
method: myFunc
}
obj.method(); // commonly invoked