function作為構(gòu)造函數(shù)和非構(gòu)造函數(shù)調(diào)用的區(qū)別

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ā)生的主要步驟:

  1. 生成一個(gè)新的執(zhí)行上下文和對(duì)應(yīng)的作用域删壮。(如果對(duì)執(zhí)行上下文是什么不了解的話,可以參考我上一篇《什么是作用域和執(zhí)行上下文》
  2. 把當(dāng)前函數(shù)和這個(gè)新的執(zhí)行上下文和作用域關(guān)聯(lián)起來兑牡。
    2.1. 如果當(dāng)前函數(shù)是箭頭函數(shù)央碟,那么把作用域中的 environment record 對(duì)象的內(nèi)部屬性 [[thisBindingStatus]] 設(shè)置成 lexical。
  3. 把這個(gè)執(zhí)行上下文壓入調(diào)用棧的頂部均函,即設(shè)置成運(yùn)行執(zhí)行上下文(running execution context)亿虽。
  4. 接下來處理當(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ì)說明)蠢古。
  5. 執(zhí)行函數(shù)體奴曙。
  6. 把當(dāng)前執(zhí)行上下文彈出調(diào)用棧。
  7. 如果步驟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 的:

  1. 根據(jù)當(dāng)前執(zhí)行上下文查找到對(duì)應(yīng)的 enviroment record(execution context -> scope -> environment record)缀旁。
  2. 判斷當(dāng)前這個(gè) record 是否存儲(chǔ)過[[thisValue]]记劈,如果沒有的就沿著作用域鏈向上查找,以全局作用域?yàn)榻K點(diǎn)诵棵。
  3. 如果找到了抠蚣,則返回。

如上所述履澳,箭頭函數(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ā)生的主要步驟:

  1. 新建一個(gè)普通對(duì)象,把其原型 [[Prototype]] 指向構(gòu)造函數(shù)的 prototype 屬性的值忠蝗。
  2. 如普通函數(shù)調(diào)用的步驟1一樣现横,生成一個(gè)新的執(zhí)行上下文和對(duì)應(yīng)的作用域,并把當(dāng)前構(gòu)造函數(shù)和兩者關(guān)聯(lián)起來。
  3. 把這個(gè)執(zhí)行上下文壓入調(diào)用棧的頂部戒祠。
  4. 把第一步生成的對(duì)象當(dāng)作 this 的取值保存到作用域中的 environment record 對(duì)象的內(nèi)部屬性 [[thisValue]] 中骇两。
  5. 執(zhí)行函數(shù)體。
  6. 把當(dāng)前執(zhí)行上下文彈出調(diào)用棧姜盈。
  7. 處理函數(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壹无,一起剝皮案震驚了整個(gè)濱河市葱绒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌斗锭,老刑警劉巖地淀,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異岖是,居然都是意外死亡帮毁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門豺撑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烈疚,“玉大人,你說我怎么就攤上這事聪轿∫危” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵陆错,是天一觀的道長灯抛。 經(jīng)常有香客問我,道長音瓷,這世上最難降的妖魔是什么对嚼? 我笑而不...
    開封第一講書人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮绳慎,結(jié)果婚禮上纵竖,老公的妹妹穿的比我還像新娘漠烧。我一直安慰自己,他們只是感情好靡砌,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開白布已脓。 她就那樣靜靜地躺著,像睡著了一般通殃。 火紅的嫁衣襯著肌膚如雪摆舟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評(píng)論 1 308
  • 那天邓了,我揣著相機(jī)與錄音,去河邊找鬼媳瞪。 笑死骗炉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蛇受。 我是一名探鬼主播句葵,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼兢仰!你這毒婦竟也來了乍丈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤把将,失蹤者是張志新(化名)和其女友劉穎轻专,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體察蹲,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡请垛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洽议。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宗收。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亚兄,靈堂內(nèi)的尸體忽然破棺而出混稽,到底是詐尸還是另有隱情,我是刑警寧澤审胚,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布匈勋,位于F島的核電站,受9級(jí)特大地震影響菲盾,放射性物質(zhì)發(fā)生泄漏颓影。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一懒鉴、第九天 我趴在偏房一處隱蔽的房頂上張望诡挂。 院中可真熱鬧碎浇,春花似錦、人聲如沸璃俗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽城豁。三九已至苟穆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唱星,已是汗流浹背雳旅。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留间聊,地道東北人攒盈。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像哎榴,于是被迫代替她去往敵國和親型豁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容