不管是否在嚴格模式下,全局執行環境中(任何函數體外部)this
都指向全局對象html
var name = '以樂之名'; this.name; // 以樂之名
函數內部,this
的值取決於函數被調用的方式(被誰調用)前端
var name = '無名氏'; function getName() { console.log(this.name); } getName(); // 無名氏 調用者是全局對象 var myInfo = { name: '以樂之名', getName: getName }; myInfo.getName(); // 以樂之名 調用者是myInfo對象
"this的指向是在運行時進行綁定的,而不是代碼書寫(函數聲明)時肯定!!!"git
"看誰用",this的指向取決於調用者,這也是不少文章提到過的觀點。"誰調用,this指向誰",只是這句話稍有偏頗,某些狀況不見得都適用。github
生活栗子:你的錢並不必定是你的錢,只有當你使用消費了纔是你的錢 。
("看誰用"),借出去的錢就不是你的了。。。segmentfault
回到正文,咱們先經過棧,來理解什麼是調用位置?數組
JavaScript中函數的調用是以棧的方式來存儲,棧頂是正在運行的函數,函數調用時入棧,執行完成後出棧。瀏覽器
function foo() { // 此時的棧:全局 -> foo,調用位置在foo bar(); } function bar() { // 此時的棧:全局 -> foo -> bar,調用位置在bar baz(); } function baz() { // 此時的棧:全局 -> foo -> bar -> baz,調用位置在baz // ... } foo();
代碼中雖然函數存在多層嵌套使用,但處於棧頂的只有正在執行的函數,也即調用者只有頂層的那一個(或最後一個),理清調用位置(調用者)有助於咱們理解this
。前端工程師
call/apply/bind
)new
綁定(new
建立實例)this
會指向全局對象(瀏覽器全局對象是window
,NodeJS全局對象是global
);this
指向undefined
// 非嚴格模式 function getName() { console.log(this.name); // this指向全局對象 } getName(); // "",並不會報錯,若是外部有全局變量name,則會輸出對應值 // 嚴格模式 function getName() { "use strict" console.log(this.name); // this指向undefined } getName(); // TypeError: Cannot read property 'name' of undefined
TIPS: 嚴格模式中,對函數中this的影響,只在函數內聲明瞭嚴格模式纔會存在,若是是調用時聲明嚴格模式則不會影響。app
function getName() { console.log(this.name); } // 調用時聲明嚴格模式 "use strict"; getName(); // ""
隱式綁定中,函數通常做爲對象的屬性調用,帶有調用者的執行上下文。所以this
值取決於調用者的上下文環境。若是存在多層級屬性引用,只有對象屬性引用鏈中最頂層(最後一層)會影響調用位置,而this
的值取決於調用位置。文章開頭以棧來理解調用者的例子。函數
function getName() { return this.name; } var myInfo = { name: '以樂之名', getName: getName }; var leader = { name: '大神組長' man: myInfo }; leader.man.getName(); // '以樂之名' // man 指向 myInfo,最頂層(最後一層)對象爲 myInfo
apply/call
方法二者相似,均可以顯示綁定this
,二者的區別是參數傳遞的方式不一樣。apply/call
第一個參數都爲要指定this
的對象,不一樣的是apply
第二個參數接受的是一個參數數組,而call
從第二個參數開始接受的是參數列表。
apply語法:func.apply(thisArg, [argsArray])call語法: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); // ...展開運算符
TIPS: 若是thisArg爲原始值(數字,字符串,布爾值),this
會指向該原始值的自動包裝對象,如Number
, String
, Boolean
等
func.apply(1); // func中的this -> Number對象;
bind
是ES5新增的方法,跟apply/call
功能同樣,能夠顯示綁定this。
bind語法:function.bind(thisArg[, arg1[, arg2[, ...]]])bind()方法建立一個新的函數,在調用時設置this關鍵字爲提供的值,並在調用新函數時,將給定參數列表做爲原函數的參數序列的前若干項。
-- 《Function.prototype.bind() | MDN》
"bind與apply/call的區別:apply/call傳入this並當即執行函數,而bind傳入this則返回一個函數,並不會當即執行,只有調用返回的函數纔會執行原始函數"。
bind
方法是函數柯里化的一種應用,看過上篇《前端進擊的巨人(五):學會函數柯里化(curry) 》的小夥伴,應該還記得"函數柯里化的特色:延遲執行,部分傳參,返回一個可處理剩餘參數的函數"。
bind
相較apply/call
的優勢,能夠經過部分傳參提早對this進行一次"永久綁定",也就是說this
只需綁定一次,省卻每次執行都要進行this
綁定的操做。
function getName() { return this.name; } var myInfo = { name: '以樂之名', job: '前端工程師' }; var getName = getName.bind(myInfo); getName(); // '以樂之名'; getName(); // '以樂之名'; // 一次性綁定,以後調用無需再修改this
TIPS: 函數柯里化能夠用於參數預設,像一次性操做(判斷/綁定)等。
有關函數柯里化的詳解,請回閱:《前端進擊的巨人(五):學會函數柯里化(curry) 》。
經過new
操做符能夠實現對函數的構造調用。JavaScript中自己並無"構造函數",一個函數若是沒有使用new
操做符調用,那麼它就是個普通函數,new Func()
其實是對函數Func
的"構造調用"。
在瞭解構造函數中的this
前,有必要先了解下new
實例化對象的過程。
__proto__
會指向函數的prototype
)this
會指向這個新對象,並對this
屬性進行賦值return
,通常不會有return
)// 正常不帶return的構造函數 function People(name, sex) { this.name = name; this.sex = sex; } var man = new People('亞當', '男'); var woman = new People('夏娃', '女'); // 實例化對象成功
// 構造函數帶了return function People(name, sex) { return 1; // 返回的是Number對象 } function People(name, sex) { return 'hello world'; // 返回的是String對象 } function People(name, sex) { return function() {} } function People(name, sex) { return {}; } // 以上並未正確實例化對象
構造函數自定義return
,會形成new
沒法完成正確的實例化操做。若是返回值爲基本類型,則返回其包裝對象Number/String/Bollean
。
TIPS: 原型鏈中的this指向其實例化的對象
People.prototype.say = function() { console.log(`個人名字:${this.name}`); }; var man = new People('亞當', '男'); man.say(); // 個人名字:亞當
顯示綁定 / new
綁定 > 隱式綁定 > 默認綁定
TIPS: new
沒法跟apply/call
同時使用
new
操做符使用(new
綁定)? YES --> this
綁定的是new
建立的新對象call/apply/bind
(顯示綁定)? YES --> this
綁定的是指定的對象this
綁定的是那個上下文對象undefined
,不然指向全局對象箭頭函數的this
機制不一樣於傳統的this
機制,它採起的是另一種機制,詞法做用域的this
斷定規則。
// 例子一 var name = '無名氏'; var myInfo = { name: '以樂之名', getName: () => { console.log(this.name); } }; var getName = myInfo.getName; window.getName(); // 無名氏 myInfo.getName(); // 無名氏 // myInfo是在全局環境定義的,所以根據詞法做用域,this指向全局對象 // 例子二 var name = '無名氏'; var myInfo = { name: '以樂之名', say: () => { setTimeout(() => { console.log(this.name); }) } }; myInfo.say(); // 無名氏 // 箭頭函數經過做用域鏈來逐層查找this,最終找到全局變量myInfo,this指向全局對象 // 例子三 var name = '無名氏'; var myInfo = { name: '以樂之名', say: function() { setTimeout(() => { console.log(this.name); }) } }; myInfo.say(); // 以樂之名 // 箭頭函數找到say: function(){},所以this的做用域來自myInfo
TIPS: setTimeout/setInterval/alert
的調用者都是全局對象
"箭頭函數的this
始終指向函數定義時的this
,而非執行(調用)時的this
。箭頭函數中的this
必須經過做用域鏈一層一層向外查找,來肯定this
指向。"
// 函數表達式 const getName = (name) => { return 'myName: ' + name }; // 匿名函數 setTimeout((name) => { console.log(name); }, 1000)
()
;若是沒有參數或多個參數需加括號()
// 只有一個參數 const getName = name => { return `myName: ${name}`; } // 無參數 const getName = () => { return 'myName: "以樂之名"'; } // 多參數 const getName = (firstName, lastName) => { return `myName: ${firstName} ${lastName}`; }
{}
const getName = name => return `myName: ${name}`;
{}
,可不寫return,會自動返回const getName = name => `myName: ${name}`;
參考文檔:
本文首發Github,期待Star!
https://github.com/ZengLingYong/blog
做者:以樂之名 本文原創,有不當的地方歡迎指出。轉載請指明出處。