JavaScript中this關鍵字

this一直是js中一個老生常談的東西,可是咱們究竟該如何來理解它呢?
在《JavaScript高級程序設計》中,對this的解釋是:javascript

this對象是在運行時基於函數的執行環境綁定的。html

咱們來逐字解讀這句話:java

  • this是一個對象
  • this的產生與函數有關
  • this與執行環境綁定

說通俗一點就是,「誰調用的這個函數,this就是誰」。web


1、函數直接調用中的this

舉個栗子:數組

var x = 1;

function testThis() {
    console.log(this.x);
}

testThis();  //1複製代碼

js中有一個全局對象window,直接調用函數testThis時,就至關於調用window下的testThis方法,包括直接聲明的變量也都是掛載在window對象下的。瀏覽器

var x = 1;
function testThis() {
    this.innerX = 10;
    return 1;
}
testThis() === window.testThis();  // true
innerX === window.innerX;  // true
x === window.x;  // true複製代碼

同理,在匿名函數中使用this也是指向的window,由於匿名函數的執行環境具備全局性。app

(function () {
    console.log(this); //window
})();複製代碼

可是呢,凡事都有例外,js的例外就是嚴格模式。在嚴格模式中,禁止this關鍵字指向全局對象。函數

(function () {
 'use strict';
    console.log(this); //undefined
})();複製代碼

2、對象方法調用中的this

再舉個栗子:ui

var person = {
    "name": "shenfq",
    "showName": function () {
        console.log(this.name);
    } 
};

person.showName(); // 'shenfq'複製代碼

此時,showName方法中的this指向的是對象person,由於調用showName的是person對象,因此showName方法中的 this.name 其實就是 person.name。this

可是若是咱們換個思路,把showName方法賦值給一個全局變量,而後在全局環境下調用。

var name = 'global',
    person = {
        "name": "shenfq",
        "showName": function () {
            console.log(this.name);
        } 
    },
    showGlobalName = person.showName;
showGlobalName(); // 'global'複製代碼

能夠看到,在全局環境中調用showName方法時,this就會指向window。

再換個思路,若是showName方法被其餘對象調用呢?

var person = {
        "name": "shenfq",
        "showName": function () {
            console.log(this.name);
        } 
    },
    animal = {
        "name": "dog",
        "showName": person.showName
    };

animal.showName(); // 'dog'複製代碼

此時的name又變成了animal對象下的name,再複雜一點,若是調用方法的是對象下的一個屬性,而這個屬性是另個對象。

function showName () {
    console.log(this.name);
}
var person = {
    "name": "shenfq",
    "bodyParts": {
        "name": "hand",
        "showName": showName
    },
    "showName": showName
};

person.showName(); // 'shenfq'
person.bodyParts.showName(); // 'hand'複製代碼

雖然調用showName方法的最源頭是person對象,可是最終調用的是person下的bodyParts,因此方法寫在哪一個對象下其實不重要,重要的是這個方法最後被誰調用了,this指向的永遠是最終調用它的那個對象。講來說去,this也就那麼回事,只要知道函數體的執行上下文就能知道this指向哪兒,這個規則在大多數狀況下都適用,注意是大多數狀況,少部分狀況後面會講。

最後一個思考題,當方法返回一個匿名函數,這個匿名函數裏面的this指向哪裏?

var name = 'global',
    person = {
        "name": "shenfq",
        "returnShowName": function () {
            return function () {
                console.log(this.name);
            }
        } 
    };

person.returnShowName()(); // 'global'複製代碼

答案一目瞭然,匿名函數無論寫在哪裏,只要是被直接調用,它的this都是指向window,由於匿名函數的執行環境具備全局性。


3、new構造函數中的this

仍是先舉個栗子:

function Person (name) {
    this.name = name;
}
var global = Peson('global'),
    xiaoming = new Person('xiaoming');

console.log(window.name); // 'global'
console.log(xiaoming.name); // 'xiaoming'複製代碼

首先不使用new操做符,直接調用Person函數,這時的this任然指向window。當使用了new操做符時,這個函數就被稱爲構造函數。

所謂構造函數,就是用來構造一個對象的函數。構造函數老是與new操做符一塊兒出現的,當沒有new操做符時,該函數與普通函數無區別。

對構造函數進行new操做的過程被稱爲實例化。new操做會返回一個被實例化的對象,而構造函數中的this指向的就是那個被實例化的對象,好比上面例子中的xiaoming。

關於構造函數有幾點須要注意:

  1. 實例化對象默認會有constructor屬性,指向構造函數;
function Person (name) {
    this.name = name;
}
var xiaoming = new Person('xiaoming');

console.log(xiaoming.constructor); // Person複製代碼
  1. 實例化對象會繼承構造函數的原型,能夠調用構造函數原型上的全部方法;
function Person (name) {
    this.name = name;
}
Person.prototype = {
    showName: function () {
        console.log(this.name);
    }
};
var xiaoming = new Person('xiaoming');

xiaoming.showName(); // 'xiaoming'複製代碼
  1. 若是構造函數返回了一個對象,那麼實例對象就是返回的對象,全部經過this賦值的屬性都將不存在
function Person (name, age) {
    this.name = name;
    this.age  = age;
    return {
        name: 'innerName'
    };
}
Person.prototype = {
    showName: function () {
        console.log(this.name);
    }
};
var xiaoming = new Person('xiaoming', 18);

console.log(xiaoming); // {name: 'innerName'}複製代碼

4、經過call、apply間接調用函數時的this

又一次舉個栗子:

var obj = {
    "name": "object"
}

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

test.call(obj);   // 'object'
test.apply(obj);  // 'object'複製代碼

callapply方法都是掛載在Function原型下的方法,全部的函數都能使用。

這兩個函數既有相同之處也有不一樣之處:

  • 相同的地方就是它們的第一個參數會綁定到函數體的this上,若是不傳參數,this默認仍是綁定到window上。
  • 不一樣之處在於,call的後續參數會傳遞給調用函數做爲參數,而apply的第二個參數爲一個數組,數組裏的元素就是調用函數的參數。

語言很蒼白,我只好寫段代碼:

var person = {
    "name": "shenfq"
};
function changeJob(company, work) {
    this.company = company;
    this.work    = work;
};

changeJob.call(person, 'NASA', 'spaceman');
console.log(person.work); // 'spaceman'

changeJob.apply(person, ['Temple', 'monk']);
console.log(person.work); // 'monk'複製代碼

有一點值得注意,這兩個方法會把傳入的參數轉成對象類型,無論傳入的字符串仍是數字。

var number = 1, string = 'string';
function getThisType () {
    console.log(typeof this);
}

getThisType.call(number); //object
getThisType.apply(string); //object複製代碼

5、經過bind改變函數的this指向

最後舉個栗子:

var name = 'global',
    person = {
        "name": "shenfq"
    };
function test () {
    console.log(this.name);
}

test(); // global

var newTest = test.bind(person);
newTest(); // shenfq複製代碼

bind方法是ES5中新增的,和call、apply同樣都是Function對象原型下的方法-- Function.prototype.bind ,因此每一個函數都能直接調用。bind方法會返回一個與調用函數同樣的函數,只是返回的函數內的this被永久綁定爲bind方法的第一個參數,而且被bind綁定後的函數不能再被從新綁定。

function showName () {
    console.log(this.name);
}
var person = {"name": "shenfq"},
    animal = {"name": "dog"};

var showPersonName = showName.bind(person),
    showAnimalName = showPersonName.bind(animal);

showPersonName(); //'shenfq'
showAnimalName(); //'shenfq'複製代碼

能夠看到showPersonName方法先是對showName綁定了person對象,而後再對showPersonName從新綁定animal對象並無生效。

6、箭頭函數中的this

真的是最後一個栗子:

var person = {
    "name": "shenfq",
    "returnArrow": function () {
        return () => {
            console.log(this.name);
        }
    }
};

person.returnArrow()(); // 'shenfq'複製代碼

箭頭函數是ES6中新增的一種語法糖,簡單說就是匿名函數的簡寫,可是與匿名函數不一樣的是箭頭函數中的this表示的是外層執行上下文,也就是說箭頭函數的this就是外層函數的this。

var person = {
    "name": "shenfq",
    "returnArrow": function () {
        let that = this;
        return () => {
            console.log(this == that);
        }
    }
};

person.returnArrow()(); // true複製代碼

補充:

事件處理函數中的this:

var $btn = document.getElementById('btn');
function showThis () {
    console.log(this);
}
$btn.addEventListener('click', showThis, false);複製代碼

點擊按鈕能夠看到控制檯打印出了元素節點。

事件結果
事件結果

其實事件函數中的this默認就是綁定事件的元素,調用事件函數時能夠簡單理解爲

$btn.showThis()

只要單擊了按鈕就會已這種方式來觸發事件函數,因此事件函數中的this表示元素節點,這也與以前定義的「誰調用的這個函數,this就是誰」相吻合。

eval中的this:

eval('console.log(this)'); //window
var obj = {
    name: 'object',
    showThis: function () {
        eval('console.log(this)');
    }
}
obj.showThis(); // obj複製代碼

eval是一個能夠動態執行js代碼的函數,能將傳入其中的字符串看成js代碼執行。這個方法通常用得比較少,由於很危險,想一想動態執行代碼,什麼字符串都能執行,可是若是用得好也能帶來很大的便利。

eval中的this與箭頭函數比較相似,與外層函數的this一致。

固然這隻針對現代瀏覽器,在一些低版本的瀏覽器上,好比ie七、低版本webkit,eval的this指向會有些不一樣。

eval也能夠在一些特殊狀況下用來獲取全局對象(window、global),使用 (1,eval)('this')


先寫這麼多,有須要再補充 ^ _ ^

參考:

  1. this - JavaScript | MDN
  2. Javascript的this用法
  3. (1,eval)('this') vs eval('this') in JavaScript?

原文連接

相關文章
相關標籤/搜索