」this「在JavaScript中很常見,用起來也很」香「,每當咱們想訪問一個值或者設置一個值,常會用到它,原生和框架都是,但它同時又讓人困惑,使咱們寫的代碼達不到預期效果,甚至引發bug,本文咱們就來看看,this的指向遵循哪些規律。編程
先看幾個看似正常卻錯誤的理解。數組
好比下面這段代碼:app
function add(num){
//記錄add被調用的次數
this.count++;
console.log("計數" + num)
}
add.count = 0;
add(1)
add(2)
console.log(add.count) // 0
複製代碼
能夠看到,結果跟預期的不同,count未發生改變。框架
實際上,這段代碼無心間建立了一個全局變量 count,在執行add的時候,並未改動add的count。ide
仍是先看代碼函數
function one(){
var a = 2;
this.two();
}
function two(){
console.log(this.a)
}
one(); // a is not defined
複製代碼
這種狀況略特殊,由於並不老是錯的,這裏one調用two可以成功,但卻沒法所以利用two裏面的this來訪問one裏面的a,這是作不到的。學習
看了兩個錯誤用法,怎樣是對的呢?接着往下看。ui
this指代變量或者方法和對象之間的從屬關係,但它是在運行時進行綁定,而不是編寫時,這取決於函數調用時的多種條件,因此,this的綁定和函數聲明的位置無關,取決於函數調用的方式。this
上面這句話不難理解,由於在哪調用函數彷佛顯而易見,其實否則,在某些編程模式下,調用位置可能被隱藏,這時咱們就要順着調用的路徑找到調用位置,而後判斷它符合哪一種綁定規則。編碼
function get(){
console.log(this.a);
}
var a = 2;
get(); // 2
複製代碼
這種屬於獨立的函數調用,應用的是函數的默認綁定,this指向全局對象。
怎麼判斷默認綁定呢,由於它是使用不帶任何修飾的函數引用進行調用的。但也要注意,只有在非嚴格模式下,this才綁定到全局,不然會提示undefined。
看調用位置是否被某個對象擁有或者包含。
function get(){
console.log(this.a);
}
var obj = {
a:2,
get
}
obj.get() // 2
複製代碼
這裏咱們把get函數放在了obj內,但嚴格來講,它仍不屬於obj,只是被obj包含和調用,這個時候,this就指向了obj,this.a 就和 obj.a等價了。
只是這種狀況須要注意的是,它有個就近的規則,就是它只屬於離this最近的一層。像下面這段:
function get(){
console.log(this.a);
}
var obj2 = {
a:3,
get
}
var obj1 = {
a:2,
obj2
}
obj1.obj2.get(); // 3
複製代碼
這段代碼中,this綁定在了obj2上,而不是一直向上追溯。
一個常見的問題就是被隱式綁定的函數丟失了綁定對象,致使應用默認綁定,this就到了全局或者undefined。
第一種狀況:方法傳遞
function get(){
console.log(this.a);
}
var obj = {
a:3,
get
}
var getNum = obj.get;
var a = "global";
getNum(); // global
複製代碼
這裏咱們會發現,咱們把obj的get方法給了getNum,卻不是想象中的效果,其實咱們上面就說了,這裏的get並不真實屬於obj,而只是在調用時,this被綁定到了obj,不信你能夠像下面這麼改一下:
var a = "global";
var getNum = obj.get();
console.log(getNum) // 3
複製代碼
又是3,跟指望的一致,這就是細微差異致使結果的不一樣。
第二種狀況:回調
function get(){
console.log(this.a);
}
function doGet(get){
get();
}
var obj = {
a:3,
get
}
var a = "global";
doGet(obj.get); // global
複製代碼
這種狀況跟上面的相似,由於參數傳遞就是一種隱式賦值,這個時候,執行get方法的時候,仍是全局的get,this綁定的就是全局對象了。
回調很經常使用,因此,由於回調函數而丟失this的狀況也常見,甚至於,調用回調函數的函數可能會修改this,這就讓代碼行爲更加地難以捕捉。
因此有什麼好辦法彌補這些不肯定問題的發生麼。
經過上面的例子能夠看到,要想把一個函數的this綁定到對象上,須要下面兩個條件:
若是不想這麼作呢?
JavaScript中的函數都有一些有用的特性,能夠用來解決這個問題,好比:call 和 apply,由於能夠直接指定this的綁定對象,因此稱之爲顯式綁定。
可看以下代碼:
function get(){
console.log(this.a);
}
var obj = {
a:3,
}
get.call(obj); // 3
複製代碼
經過get.call(),咱們把this強制綁定到了obj上。
從綁定this的角度看,call和apply的區別只是參數的形式不一樣,call能夠直接寫參數,而apply須要以數組的形式傳參。
遺憾的是,這仍沒法解決上面提到的綁定丟失問題,但它的一種變通方法能夠解決。
function get(){
console.log(this.a);
}
var obj = {
a:3,
}
var getNum = function(){
get.call(obj);
}
getNum() // 2
複製代碼
能夠看到,這裏建立了一個函數給getNum,而後函數內部進行顯式地綁定,這樣以來,不論再怎樣調用getNum,get的this都不會變了。
由於上面提到的綁定方式較爲經常使用,ES5直接提供了一個內置的方法——Function.prototype.bind。
function get(){
console.log(this.a);
}
var obj = {
a:3,
}
var getNum = get.bind(obj);
getNum() // 3
複製代碼
bind()會返回一個硬編碼的新函數,把指定的參數設置爲this的上下文並調用原函數。
有一些庫,或者ES新版本的內置函數,會提供一個可選參數,它的做用和bind()相似,確保回調函數使用指定的this,好比:
let arr = [1,2,3];
function get(el){
console.log(el,this.id);
}
var obj = {
id:"item",
}
arr.forEach(get,obj);
// 1 "item" 2 "item" 3 "item"
複製代碼
相似的這些函數實際就是經過call()或者apply()實現了顯式綁定,這樣能夠少寫一些代碼。
咱們很熟悉的一句話:」沒有對象就new一個「。
JavaScript 中有一些內置對象函數和自定義函數的構造調用,都會用到new關鍵字,它會經歷如下過程:
就會有以下代碼的效果:
function Get(a){
this.a = a;
}
var getNum = new Get(2);
console.log(getNum.a) // 2
複製代碼
這是你們熟悉的用法,只是中間經歷的過程不是表面那麼簡單,須要理解一下。
new是本文提到的最後一種能夠影響this綁定的方法,下面看看這些規則的優先級。
顯然的,默認綁定優先級是最低的,它能夠輕易被改變,因此,咱們主要關心隱式綁定和顯式綁定的優先級。可按照以下順序進行判斷:
var getNum = new Get();
複製代碼
var getNum = get.call(obj);
複製代碼
var getNum = obj.get();
複製代碼
曾有人問我,ES6當中比較喜歡哪一個設計,我沒多想,就說箭頭函數,而後他問我,它跟以前的函數有什麼不一樣,this綁定就是它們的不一樣之一。
箭頭函數不使用this的那幾種規則,而是根據外層函數/全局做用域來決定。
function get(){
return (a) =>{
console.log(this.a);
}
}
var obj1 = {
a:3,
}
var obj2 = {
a:2,
}
var getNum = get.call(obj1);
getNum.call(obj2); // 3,不是2
複製代碼
由於get內部建立的箭頭函數會捕獲調用get時的this,getNum也會跟着一塊兒綁定到obj1,且這種綁定沒法被修改。
這種就像bind方法同樣,確保函數被綁定到指定對象,它取代了傳統的this機制。
實際上,在ES6以前,咱們經常使用另一種方法來實現它。
function get(){
var self = this; // 就是這裏
console.log(self.a);
}
var obj = {
a:2
}
get.call(obj); //2
複製代碼
this綁定是個看似簡單,又有一點複雜的東西,除了默認綁定和構造函數調用能夠比較天然地理解,其餘都繞了那麼一點彎兒,但也不用怕,只要通過反覆地思考和實踐,掌握它們就是本能反應了。
本文儘可能全面,仍不免疏漏,鑑於篇幅太長會增長學習負擔,索性沒有說起某些特殊狀況,多數場景已經夠用,歡迎交流探討。
下篇見!~
博客連接:this指向知多少