前端進(jìn)擊的巨人(六):知否知否栋操,須知this

前端進(jìn)擊的巨人(六):知否知否僵腺,須知this

常見(jiàn)this的誤解

  1. 指向函數(shù)自身(源于this英文意思的誤解)
  2. 指向函數(shù)的詞法作用域(部分情況)

this的應(yīng)用環(huán)境

1. 全局環(huán)境

無(wú)論是否在嚴(yán)格模式下衡查,全局執(zhí)行環(huán)境中(任何函數(shù)體外部)this都指向全局對(duì)象

var name = '以樂(lè)之名';
this.name;  // 以樂(lè)之名
2. 函數(shù)(運(yùn)行內(nèi))環(huán)境

函數(shù)內(nèi)部,this的值取決于函數(shù)被調(diào)用的方式(被誰(shuí)調(diào)用)

var name = '無(wú)名氏';
function getName() {
 console.log(this.name);
}
getName();         // 無(wú)名氏 調(diào)用者是全局對(duì)象

var myInfo = {
  name: '以樂(lè)之名',
  getName: getName
};
myInfo.getName();  // 以樂(lè)之名 調(diào)用者是myInfo對(duì)象

this的正解

"this的指向是在運(yùn)行時(shí)進(jìn)行綁定的蜘腌,而不是代碼書(shū)寫(xiě)(函數(shù)聲明)時(shí)確定D拧!撮珠!"

"看誰(shuí)用"沮脖,this的指向取決于調(diào)用者,這也是很多文章提到過(guò)的觀點(diǎn)。"誰(shuí)調(diào)用勺届,this指向誰(shuí)"驶俊,只是這句話(huà)稍有偏頗,某些情況不見(jiàn)得都適用免姿。

生活栗子:你的錢(qián)并不一定是你的錢(qián)饼酿,只有當(dāng)你使用消費(fèi)了才是你的錢(qián) 。
"看誰(shuí)用")胚膊,借出去的錢(qián)就不是你的了故俐。。紊婉。

回到正文药版,我們先通過(guò)棧,來(lái)理解什么是調(diào)用位置肩榕?

JavaScript中函數(shù)的調(diào)用是以棧的方式來(lái)存儲(chǔ)刚陡,棧頂是正在運(yùn)行的函數(shù)惩妇,函數(shù)調(diào)用時(shí)入棧株汉,執(zhí)行完成后出棧。

function foo() {
  // 此時(shí)的棧:全局 -> foo歌殃,調(diào)用位置在foo
  bar();
}

function bar() {
  // 此時(shí)的棧:全局 -> foo -> bar乔妈,調(diào)用位置在bar
  baz();
}

function baz() {
  // 此時(shí)的棧:全局 -> foo -> bar -> baz,調(diào)用位置在baz
  // ...
}

foo();

代碼中雖然函數(shù)存在多層嵌套使用氓皱,但處于棧頂?shù)闹挥姓趫?zhí)行的函數(shù)路召,也即調(diào)用者只有頂層的那一個(gè)(或最后一個(gè)),理清調(diào)用位置(調(diào)用者)有助于我們理解this波材。

this的綁定規(guī)則

  1. 默認(rèn)綁定(函數(shù)單獨(dú)調(diào)用)
  2. 隱式綁定(作為對(duì)象的屬性方法調(diào)用股淡,帶有執(zhí)行上下文)
  3. 顯示綁定(call/apply/bind
  4. new綁定(new創(chuàng)建實(shí)例)
  5. 箭頭函數(shù)綁定(ES6新增,基于詞法作用域)

默認(rèn)綁定下(函數(shù)單獨(dú)調(diào)用)區(qū)分嚴(yán)格模式

  • 非嚴(yán)格模式廷区,this會(huì)指向全局對(duì)象(瀏覽器全局對(duì)象是window唯灵,NodeJS全局對(duì)象是global);
  • 嚴(yán)格模式隙轻,this指向undefined
// 非嚴(yán)格模式
function getName() {
  console.log(this.name);  // this指向全局對(duì)象
}
getName();  // ""埠帕,并不會(huì)報(bào)錯(cuò),如果外部有全局變量name玖绿,則會(huì)輸出對(duì)應(yīng)值

// 嚴(yán)格模式
function getName() {
  "use strict"
 console.log(this.name);   // this指向undefined
}
getName();  // TypeError: Cannot read property 'name' of undefined

TIPS: 嚴(yán)格模式中敛瓷,對(duì)函數(shù)中this的影響,只在函數(shù)內(nèi)聲明了嚴(yán)格模式才會(huì)存在斑匪,如果是調(diào)用時(shí)聲明嚴(yán)格模式則不會(huì)影響呐籽。

function getName() {
  console.log(this.name);
}

// 調(diào)用時(shí)聲明嚴(yán)格模式
"use strict";
getName();  // ""

隱式綁定

隱式綁定中,函數(shù)一般作為對(duì)象的屬性調(diào)用,帶有調(diào)用者的執(zhí)行上下文狡蝶。因此this值取決于調(diào)用者的上下文環(huán)境宙刘。如果存在多層級(jí)屬性引用,只有對(duì)象屬性引用鏈中最頂層(最后一層)會(huì)影響調(diào)用位置牢酵,而this的值取決于調(diào)用位置悬包。文章開(kāi)頭以棧來(lái)理解調(diào)用者的例子。

function getName() {
  return this.name;
}

var myInfo = {
  name: '以樂(lè)之名',
  getName: getName
};

var leader = {
  name: '大神組長(zhǎng)'
  man: myInfo
};
leader.man.getName();  // '以樂(lè)之名'
// man 指向 myInfo馍乙,最頂層(最后一層)對(duì)象為 myInfo

apply/call的區(qū)別

apply/call方法兩者類(lèi)似布近,都可以顯示綁定this,兩者的區(qū)別是參數(shù)傳遞的方式不同丝格。apply/call第一個(gè)參數(shù)都為要指定this的對(duì)象撑瞧,不同的是apply第二個(gè)參數(shù)接受的是一個(gè)參數(shù)數(shù)組,而call從第二個(gè)參數(shù)開(kāi)始接受的是參數(shù)列表显蝌。

apply語(yǔ)法:func.apply(thisArg, [argsArray])

call語(yǔ)法:func.call(thisArg, arg1, arg2, ...)

var numbers = [5, 6, 2, 3, 7];

// 求numbers的最大值

// apply
var max = Math.max.apply(null, numbers);

// call
var max = Math.max.call(null, ...numbers); // ...展開(kāi)運(yùn)算符

TIPS: 如果thisArg為原始值(數(shù)字预伺,字符串,布爾值)曼尊,this會(huì)指向該原始值的自動(dòng)包裝對(duì)象酬诀,如Number, String, Boolean

func.apply(1);
// func中的this -> Number對(duì)象;

bind的特別(柯里化的應(yīng)用)

bind是ES5新增的方法,跟apply/call功能一樣骆撇,可以顯示綁定this瞒御。

bind語(yǔ)法:function.bind(thisArg[, arg1[, arg2[, ...]]])

bind()方法創(chuàng)建一個(gè)新的函數(shù),在調(diào)用時(shí)設(shè)置this關(guān)鍵字為提供的值神郊,并在調(diào)用新函數(shù)時(shí)肴裙,將給定參數(shù)列表作為原函數(shù)的參數(shù)序列的前若干項(xiàng)。

-- 《Function.prototype.bind() | MDN》

"bind與apply/call的區(qū)別:apply/call傳入this并立即執(zhí)行函數(shù)涌乳,而bind傳入this則返回一個(gè)函數(shù)蜻懦,并不會(huì)立即執(zhí)行,只有調(diào)用返回的函數(shù)才會(huì)執(zhí)行原始函數(shù)"夕晓。

bind方法是函數(shù)柯里化的一種應(yīng)用宛乃,看過(guò)上篇《前端進(jìn)擊的巨人(五):學(xué)會(huì)函數(shù)柯里化(curry) 》的小伙伴,應(yīng)該還記得"函數(shù)柯里化的特點(diǎn):延遲執(zhí)行运授,部分傳參烤惊,返回一個(gè)可處理剩余參數(shù)的函數(shù)"

bind相較apply/call的優(yōu)點(diǎn)吁朦,可以通過(guò)部分傳參提前對(duì)this進(jìn)行一次"永久綁定"柒室,也就是說(shuō)this只需綁定一次,省卻每次執(zhí)行都要進(jìn)行this綁定的操作逗宜。

function getName() {
  return this.name;
}

var myInfo = {
  name: '以樂(lè)之名',
  job: '前端工程師'
};

var getName = getName.bind(myInfo);
getName();  // '以樂(lè)之名';
getName(); //  '以樂(lè)之名';

// 一次性綁定雄右,之后調(diào)用無(wú)需再修改this

TIPS: 函數(shù)柯里化可以用于參數(shù)預(yù)設(shè)空骚,像一次性操作(判斷/綁定)等。

有關(guān)函數(shù)柯里化的詳解擂仍,請(qǐng)回閱:《前端進(jìn)擊的巨人(五):學(xué)會(huì)函數(shù)柯里化(curry) 》囤屹。

構(gòu)造函數(shù)中的this

通過(guò)new操作符可以實(shí)現(xiàn)對(duì)函數(shù)的構(gòu)造調(diào)用。JavaScript中本身并沒(méi)有"構(gòu)造函數(shù)"逢渔,一個(gè)函數(shù)如果沒(méi)有使用new操作符調(diào)用肋坚,那么它就是個(gè)普通函數(shù),new Func()實(shí)際上是對(duì)函數(shù)Func的"構(gòu)造調(diào)用"肃廓。

在了解構(gòu)造函數(shù)中的this前智厌,有必要先了解下new實(shí)例化對(duì)象的過(guò)程。

new實(shí)例過(guò)程

  1. 創(chuàng)建(構(gòu)造)一個(gè)全新的空對(duì)象
  2. 這個(gè)新對(duì)象會(huì)被執(zhí)行"原型"鏈接(新對(duì)象的__proto__會(huì)指向函數(shù)的prototype)
  3. 構(gòu)造函數(shù)的this會(huì)指向這個(gè)新對(duì)象盲赊,并對(duì)this屬性進(jìn)行賦值
  4. 如果函數(shù)沒(méi)有返回其他對(duì)象铣鹏,則返回這個(gè)新對(duì)象(注意構(gòu)造函數(shù)的return,一般不會(huì)有return)
// 正常不帶return的構(gòu)造函數(shù)
function People(name, sex) {
  this.name = name;
  this.sex = sex;
}

var man = new People('亞當(dāng)', '男');
var woman = new People('夏娃', '女');
// 實(shí)例化對(duì)象成功
// 構(gòu)造函數(shù)帶了return
function People(name, sex) {
  return 1;  // 返回的是Number對(duì)象
}
function People(name, sex) {
  return 'hello world';  // 返回的是String對(duì)象
}
function People(name, sex) {
  return function() {}
}
function People(name, sex) {
  return {};
}
// 以上并未正確實(shí)例化對(duì)象

構(gòu)造函數(shù)自定義return哀蘑,會(huì)造成new無(wú)法完成正確的實(shí)例化操作诚卸。如果返回值為基本類(lèi)型,則返回其包裝對(duì)象Number/String/Bollean绘迁。

TIPS: 原型鏈中的this指向其實(shí)例化的對(duì)象

People.prototype.say = function() {
  console.log(`我的名字:${this.name}`);
};

var man = new People('亞當(dāng)', '男');
man.say();  // 我的名字:亞當(dāng)

this綁定規(guī)則的優(yōu)先級(jí)

顯示綁定 / new綁定 > 隱式綁定 > 默認(rèn)綁定

TIPS: new無(wú)法跟apply/call同時(shí)使用

this判定步驟

  1. 函數(shù)被new操作符使用(new綁定)合溺? YES --> this綁定的是new創(chuàng)建的新對(duì)象
  2. 函數(shù)通過(guò)call/apply/bind(顯示綁定)? YES --> this綁定的是指定的對(duì)象
  3. 函數(shù)在某個(gè)上下文對(duì)象中調(diào)用(隱式綁定)脊髓? YES --> this綁定的是那個(gè)上下文對(duì)象
  4. 默認(rèn)綁定辫愉,嚴(yán)格模式指向undefined栅受,否則指向全局對(duì)象

ES6的箭頭函數(shù)(詞法作用域的this機(jī)制将硝,規(guī)則之外)

箭頭函數(shù)的this機(jī)制不同于傳統(tǒng)的this機(jī)制,它采取的是另外一種機(jī)制屏镊,詞法作用域的this判定規(guī)則依疼。

// 例子一
var name = '無(wú)名氏';
var myInfo = {
  name: '以樂(lè)之名',
  getName: () => {
    console.log(this.name);
  }
};
var getName = myInfo.getName;
window.getName();     // 無(wú)名氏
myInfo.getName();     // 無(wú)名氏
// myInfo是在全局環(huán)境定義的,因此根據(jù)詞法作用域而芥,this指向全局對(duì)象

// 例子二
var name = '無(wú)名氏';
var myInfo = {
  name: '以樂(lè)之名',
  say: () => {
    setTimeout(() => {
      console.log(this.name);
    })
  }
};
myInfo.say();  // 無(wú)名氏
// 箭頭函數(shù)通過(guò)作用域鏈來(lái)逐層查找this律罢,最終找到全局變量myInfo,this指向全局對(duì)象

// 例子三
var name = '無(wú)名氏';
var myInfo = {
  name: '以樂(lè)之名',
  say: function() {
    setTimeout(() => {
      console.log(this.name);
    })
  }
};
myInfo.say(); // 以樂(lè)之名
// 箭頭函數(shù)找到say: function(){}棍丐,因此this的作用域來(lái)自myInfo

TIPS: setTimeout/setInterval/alert的調(diào)用者都是全局對(duì)象

"箭頭函數(shù)的this始終指向函數(shù)定義時(shí)的this误辑,而非執(zhí)行(調(diào)用)時(shí)的this。箭頭函數(shù)中的this必須通過(guò)作用域鏈一層一層向外查找歌逢,來(lái)確定this指向巾钉。"

擴(kuò)展:箭頭函數(shù)的書(shū)寫(xiě)規(guī)則

1. 箭頭函數(shù)只能用函數(shù)表達(dá)式,不能用函數(shù)聲明式寫(xiě)法(不包括匿名函數(shù))
// 函數(shù)表達(dá)式
const getName = (name) => { return 'myName: ' + name };

// 匿名函數(shù)
setTimeout((name) => {
  console.log(name);
}, 1000)
2. 如果參數(shù)只有一個(gè)秘案,可不加括號(hào)()砰苍;如果沒(méi)有參數(shù)或多個(gè)參數(shù)需加括號(hào)()
// 只有一個(gè)參數(shù)
const getName = name => {
  return `myName: ${name}`;
}

// 無(wú)參數(shù)
const getName = () => {
  return 'myName: "以樂(lè)之名"';
}

// 多參數(shù)
const getName = (firstName, lastName) => {
  return `myName: ${firstName} ${lastName}`;
}
3. 函數(shù)體只有一個(gè)可不加花括號(hào){}
const getName = name => return `myName: ${name}`;
4. 函數(shù)體沒(méi)有花括號(hào){}潦匈,可不寫(xiě)return,會(huì)自動(dòng)返回
const getName = name => `myName: ${name}`;

參考文檔:

本文首發(fā)Github,期待Star吼旧!
https://github.com/ZengLingYong/blog

作者:以樂(lè)之名
本文原創(chuàng)凰锡,有不當(dāng)?shù)牡胤綒g迎指出。轉(zhuǎn)載請(qǐng)指明出處圈暗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寡夹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子厂置,更是在濱河造成了極大的恐慌菩掏,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昵济,死亡現(xiàn)場(chǎng)離奇詭異智绸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)访忿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)瞧栗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人海铆,你說(shuō)我怎么就攤上這事迹恐。” “怎么了卧斟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵殴边,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我珍语,道長(zhǎng)锤岸,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任板乙,我火速辦了婚禮是偷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘募逞。我一直安慰自己蛋铆,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布放接。 她就那樣靜靜地躺著刺啦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪透乾。 梳的紋絲不亂的頭發(fā)上洪燥,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天磕秤,我揣著相機(jī)與錄音,去河邊找鬼捧韵。 笑死市咆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的再来。 我是一名探鬼主播蒙兰,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼芒篷!你這毒婦竟也來(lái)了搜变?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤针炉,失蹤者是張志新(化名)和其女友劉穎挠他,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體篡帕,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殖侵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镰烧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拢军。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖怔鳖,靈堂內(nèi)的尸體忽然破棺而出茉唉,到底是詐尸還是另有隱情,我是刑警寧澤结执,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布度陆,位于F島的核電站,受9級(jí)特大地震影響昌犹,放射性物質(zhì)發(fā)生泄漏坚芜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一斜姥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧沧竟,春花似錦铸敏、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至糕非,卻和暖如春蒙具,著一層夾襖步出監(jiān)牢的瞬間球榆,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工禁筏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留持钉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓篱昔,卻偏偏與公主長(zhǎng)得像每强,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子州刽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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

  • 函數(shù)和對(duì)象 1空执、函數(shù) 1.1 函數(shù)概述 函數(shù)對(duì)于任何一門(mén)語(yǔ)言來(lái)說(shuō)都是核心的概念。通過(guò)函數(shù)可以封裝任意多條語(yǔ)句穗椅,而且...
    道無(wú)虛閱讀 4,543評(píng)論 0 5
  • 特別說(shuō)明匹表,為便于查閱邢羔,文章轉(zhuǎn)自https://github.com/getify/You-Dont-Know-JS...
    殺破狼real閱讀 689評(píng)論 0 1
  • 關(guān)于 this this 關(guān)鍵字是 JavaScript 中最復(fù)雜的機(jī)制之一。它是一個(gè)很特別的關(guān)鍵字桑孩,被自動(dòng)定義在...
    游學(xué)者灬墨槿閱讀 571評(píng)論 1 2
  • 1.概念 在JavaScript中拜鹤,this 是指當(dāng)前函數(shù)中正在執(zhí)行的上下文環(huán)境,因?yàn)檫@門(mén)語(yǔ)言擁有四種不同的函數(shù)調(diào)...
    BluesCurry閱讀 1,127評(píng)論 0 2
  • 任何技術(shù)的發(fā)展到最后被廣泛應(yīng)用時(shí)流椒,最終的目的是為了提升原來(lái)的效率和提高原來(lái)的生產(chǎn)率敏簿,很多高精尖的技術(shù)是為了人類(lèi)的未...
    趙程沖閱讀 231評(píng)論 0 0