js this用法

參考鏈接

基本含義

var showName = function() {
    console.log('My name is', this.name);
};
var zs = {
        name: 'Zhang San',
        describe: showName
    },
    ls = {
        name: 'Li Si',
        describe: showName
    };
zs.describe();      // My name is Zhang San
ls.describe();      // My name is Li Si
showName();         // My name is
name = 'window';        // 等價于 window.name = 'window'; 和this.name = 'window'; 因為此時window===this
showName();         // My name is window

以上示例中實際都是執行的showName方法,但是由于環境不同,輸出的結果也不同,根本原因是不同情況下的this是不一樣的。
zs.describe(); // this === zs
ls.describe(); // this === ls
showName(); // this === window

綁定機制導致的this不同指代

<input type="button" name="按鈕1" onclick="showName()" value="按鈕1">
<input id="btn1" type="button" name="按鈕2"  value="按鈕2">
<input id="btn2" type="button" name="按鈕3" value="按鈕3">
<script>
window.name = 'window';
var showName = function() {
    console.log('My name is', this.name);
};
var btn1 = document.getElementById('btn1');
btn1.onclick = showName;
var btn2 = document.getElementById('btn2');
btn2.addEventListener('click', showName, false);
</script>

點擊三個按鈕,控制臺輸出結果分別是什么呢?
第一個為:My name is window 第二個為:My name is 按鈕2 ,第三個為My name is 按鈕3
這是為什么呢?這個和綁定事件的機制有關系。第一種形式是HTML事件,onclick="showName()"表示在點擊時執行showName方法,此時執行環境為全局環境,this為window,所以輸出window。
第二種和第三種分別為DOM0級事件和DOM2級事件,其實質是給點擊事件指定了一個回調函數,其為showName。在點擊事件的回調函數中,this是指當前這個dom元素,因此輸出的值為這兩個按鈕的name屬性。

使用場合

  • this的使用是很廣泛的,其作用也非常強大。我們可以將this的使用歸為一下幾類。
    1.構造函數
function Person(name, gender) { this.name = name;
    this.gender = gender; }
Person.prototype.showSelf = function() {
    return this; }
zs = new Person('zs', 'male'); // {name: "zs", gender: "male"}
zs.showSelf(); // {name: "zs", gender: "male"}
zs === zs.showSelf(); // true

2.對象的方法

var box = {
    name: 'box',
    getName: function() {
        return this.name;
    }
};
var bag = {
    name: 'bag'
};
bag.getName = box.getName;
bag.getName(); // "bag"

雖然bag.getName實際是對box.getName的一個引用, 由于運行時使用的是bag.getName(), 此時是在bag對象下運行的, this也就指的是bag了。
再看一點奇怪的:

// 注意 box.getName 沒有括號
(false || box.getName)(); // window
(false ? alert : box.getName)(); // window

上面這兩種情況下, 輸出的都不再是box對象的name屬性, 而是window( 之前設置了window.name = 'window')。
表示此時方法內部的this指向的是瀏覽器頂層對象window。
可以這么理解:
box對象指向了一個地址M1, box.getName作為box的一個方法, 但本身也是對象, 它自己也有一個地址M2, 只有通過box.getName() 調用時, 是從M1中調用M2, 所以this指向的是box。 上面兩種情況都是直接拿到M2來調用, 此時和M1已經沒有任何關系了, this的指向當前代碼塊所在的對象。

ES6箭頭函數

ES6中新增的箭頭函數里面所使用的this和之前介紹的情況都不一樣了, 在箭頭函數中this不隨其運行環境的改變而改變, 而是在聲明箭頭函數時, 就已經固定下來了。 箭頭函數中this的指向就是聲明箭頭函數是所在的對象。

先看一個常規的例子:

function foo() {
    setTimeout(function() {
        console.log('name:', this.name);
    }, 100);
}
foo(); // name: window
foo.call({ name: 'an obj' }); // name: window

定義一個函數foo內部使用定時器調用一個匿名函數, 此時函數有多層了, this的指向應該是全局對象window, 輸出結果證明了這一點。 使用foo.call結果也相同的原因是, call替換的是foo函數內的this指向, 而輸出的this是在定時器的回調中的, 故結果依然是window。

我們再看一下箭頭函數中這一點的表現:

function arrow_foo() {
  setTimeout(() => {
      console.log('name:', this.name);
  }, 100);
}
arrow_foo(); // name: window
arrow_foo.call({ name: 'an obj' }); // name: an obj

我們發現結果, 居然和上面不一樣了。 為什么呢? 我們將其轉化成ES5的結果來看一下, 上面代碼轉化后的結果是這樣的:

function arrow_foo() {
    var $__1 = this;
    setTimeout(function() {
        console.log('name:', $__1.name);
    }, 100);
}
arrow_foo();
arrow_foo.call({ name: 'an obj' });

看一下轉換后的結果, 原因就一目了然了, 箭頭函數中this直接固定成了其定義時所在的對象, 此處為foo。 實際在箭頭函數中的所有this都是一個對象, 這個對象就是其定義時所在對象的this, 上面轉換后的結果中在foo中首先使用一個變量記錄下this, 而在箭頭函數中的this被替換成了之前存儲this的那個變量。
因此直接運行時, this是指全局對象, 而使用call時, 將foo內的this替換成了指定的對象 { name: 'an obj' },從而輸出的上面的結果。

使用注意點

1.避免多層this

由于this的指向是不確定的, 所以切勿在函數中包含多層的this。

var box = {
    name: 'box',
    size: {
        width: 300,
        height: 300
    },
    show: function() {
        console.log('name', this.name);
        (function() {
            console.log('size', this.size);
        })();
    }
};
box.show();
// name box
// size undefined

我們本意是想在show方法內部輸出name, 并輸出size, 但是結果卻并不是想要的這樣, 這是因為在立即執行的函數內部, this的執行不再是box對象而變成了頂層對象window, 因此第二行輸出為undefined。
解決方法為, 在外層用一個變量記錄下this, 在要使用的地方使用那個變量。

改寫上列子

var box = {
    name: 'box',
    size: {
        width: 300,
        height: 300
    },
    show: function() {
        console.log('name', this.name);
        var that = this;
        (function() {
            console.log('size', that.size);
        })();
    }
};
box.show();
// name box
// size Object {width: 300, height: 300}

還用一種做法是JavaScript提供的嚴格模式use strict, 如果函數內部的this直接指向了頂層對象會直接報錯。

var box = {
    name: 'box',
    size: {
        width: 300,
        height: 300
    },
    show: function() {
        'use strict'
        console.log('name', this.name);
        (function() {
            console.log('size', this.size);
        })();
    }
};
box.show();
// Uncaught TypeError: Cannot read property 'size' of undefined(…)

2.避免在回調函數中使用this

通常回調函數中的this都有其特定的, 如果在回調函數中使用this, 應該需要了解其含義, 否則可能出現意料之外的結果。
事件處理函數作為一種特殊的回調函數, 其this是指當前的DOM對象, 最開始的例子已經說明了這個問題。
回調函數本身是一個函數, 其作為另一個函數的參數傳遞進去, 然后在那個函數內部執行, 這本身已經構成了多層this, 此時this的指向是不確定的, 需要慎用。

3.綁定this的方法

  • function.prototype.call()

使用函數的call方法,可以指定函數內部this的指向,使其在指定的作用域中運行。

var obj = {};
var f = function() {
   return this;
};
f() === this; // true this === window
f.call(obj) === obj; // true

上面代碼中, 在全局環境運行函數f時, this指向全局環境; call方法可以改變this的指向, 指定this指向對象obj, 然后在對象obj的作用域中運行函數f。
call方法的第一個參數為一個對象, 其表示要為函數所指定的運行上下文環境的對象( 當指定為undefined或null是默認傳入window), 之后的參數依次作為原函數的參數。

  • function.prototype.apply()

使用函數的apply方法同樣可以指定函數運行的環境, 作用和call相同, 使用方法也類似, 都是第一個參數傳入要指定的上下文對象。 不同點在于, apply方法最多接收兩個參數, 第二個參數為一個數組( 無論原函數需要的參數是何種類型, 此數組中的每個元素將依次傳遞給原函數), 表示傳遞給原函數的參數, 而call可以接收多個參數, 從第二個參數開始, 之后的所有參數都傳遞給原函數。
由于apply第二個參數接收的是數組, 其有很多巧用。 由于此文重點是描述this關鍵字, 就不再贅述了。

  • function.prototype.bind()

ES5中有bind這樣一個方法, 也可以指定函數的運行環境, 但是和call、 apply有所不同, bind方法可接收一個參數, 用于指定函數運行的上下文環境, 返回一個函數作為綁定指定上下文環境后的新函數。
這樣bind和call、 apply的區別就出來了: 前者是根據指定的上下文環境返回一個新函數, 而后兩者是使用指定的上下文壞境運行原函數。
其實bind和jQuery.proxy() 類似, 雖然沒有后者處理多種情況, 但作為JavaScript原生方法, 更輕量、 高效。
用本文最開始的例子來演示此方法的使用, 某對象下有某方法, 我們要將此對象這個方法作為作為一個事件處理函數, 但不希望方法內部的this被改變:

< input id = "btn1" type = "button" name = "按鈕1" value = "張三的名字是" > 
< input id = "btn2" type = "button" name = "按鈕2" value = "張三的名字是" > 
< script > 
window.name = 'window';
var showName = function() { 
    console.log(this.name); 
};
var zs = { name: '張三' };
var btn1 = document.getElementById('btn1');
btn1.addEventListener('click', showName, false);
var btn2 = document.getElementById('btn2');
btn2.addEventListener('click', showName.bind(zs), false); 
< /script>

這樣點擊第二個按鈕,將可以正確輸出張三的名字。
bind
第一個參數為一個對象,為undefined或null是默認傳入window
除此之外,bind還可以接收額外參數,用于在生成新函數時,從原函數的第一個參數開始替換一部分參數(和jQuery.proxy()
類似)。比如原函數要接收兩個參數,使用bind產生新函數時,除了第一個參數的外,可以再傳入一個參數,此參數將替換原函數的第一個參數,這樣生成的新函數就只用接收一個參數了,詳情見jQuery 工具方法簡析jQuery.proxy( function, context [, additionalArguments ] )

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 請移步:https://blog.cdswyda.com/post/20161019
    依韻宵音閱讀 606評論 5 19
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,993評論 19 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,779評論 18 399
  • 前言 javascript中的this,constructor ,prototype,都是老生常談的問題,深入理解...
    Myselfyan閱讀 429評論 0 1
  • HTML HTML5標簽 媒體查詢head部分寫法 Doctype作用? 嚴格模式與混雜模式如何區分?它們有何意義...
    Mayo_閱讀 670評論 0 8