this一直是js中一個老生常談的東西,可是咱們究竟該如何來理解它呢?
在《JavaScript高級程序設計》中,對this的解釋是:javascript
this對象是在運行時基於函數的執行環境綁定的。html
咱們來逐字解讀這句話:java
說通俗一點就是,「誰調用的這個函數,this就是誰」。web
舉個栗子:數組
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
})();複製代碼
再舉個栗子: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,由於匿名函數的執行環境具備全局性。
仍是先舉個栗子:
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。
關於構造函數有幾點須要注意:
function Person (name) {
this.name = name;
}
var xiaoming = new Person('xiaoming');
console.log(xiaoming.constructor); // Person複製代碼
function Person (name) {
this.name = name;
}
Person.prototype = {
showName: function () {
console.log(this.name);
}
};
var xiaoming = new Person('xiaoming');
xiaoming.showName(); // 'xiaoming'複製代碼
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'}複製代碼
又一次舉個栗子:
var obj = {
"name": "object"
}
function test () {
console.log(this.name);
}
test.call(obj); // 'object'
test.apply(obj); // 'object'複製代碼
call與apply方法都是掛載在Function原型下的方法,全部的函數都能使用。
這兩個函數既有相同之處也有不一樣之處:
語言很蒼白,我只好寫段代碼:
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複製代碼
最後舉個栗子:
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對象並無生效。
真的是最後一個栗子:
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')。
先寫這麼多,有須要再補充 ^ _ ^