this是JavaScript這門語言中極其重要的一個知識點,特別是關於面向對象的相關的寫法,能夠說掌握了this的特性,至關於掌握了一大半JavaScript面向對象的編寫能力。總的來講,JavaScript中的this大概有7種狀況,理解到位了這些狀況,基本上就掌握了這部分相關的內容,全部的高級寫法,都是基於這些狀況的演變。這7種狀況分別是:
數組
咱們所說的全局環境,其實指的就是window這個對象,也就是咱們在瀏覽器中每打開一個頁面,都會生成的一個window。先來看看最簡單的全局調用。瀏覽器
function fn1() {
console.log( this );
}
fn1(); // window
// 至關於
window.fn1();
複製代碼
咱們都知道,全局下使用var聲明的變量,都會隱式的被建立爲window對象的屬性和方法。因此,當你看到一個函數被調用而沒有前綴的時候(也就是說不是經過"."符號來調用),這其實就是全局對象window在調用它。所以,此時函數內部的this是指向window對象的。再來看個變化版本。bash
let o = {
name: 'abc',
fn: function() {
console.log( this.a );
}
}
let fn2 = o.fn;
fn2(); // undefined
複製代碼
是的,雖然fn2拿到的是對象o裏面的一個方法,可是,萬變不離其宗,在執行fn2()的時候,仍然是沒有前綴的,那是誰在調用fn2的?固然是window對象。因此這裏的this也指向window。app
咱們如今知道,全局對象window調用的函數,內部的this就是指向window。可是這裏有個問題須要注意一下。JavaScript有嚴格模式和非嚴格模式之分(嚴格模式就在代碼的頂部加上一句"use strict")。在這兩種狀況下,this的指向是有區別的。
非嚴格模式下this指向咱們已經討論過了,指的是window對象,而嚴格模式下的全局調用,this指向的是undefined。
框架
"use strict"
function fn1() {
console.log( this );
}
fn1(); // undefined
複製代碼
JavaScript中對於事件的處理是採用異步回調的方式,對一個元素綁定一個回調函數,當事件觸發的時候去執行這個函數。而對於回調函數的綁定,有下面幾種狀況:異步
<div id="div1" onclick="console.log( this )"></div>
複製代碼
點擊元素div1後,咱們發現控制檯打印的是"<div id="div1" onclick="console.log( this )">",能夠知道的是,元素內聯所執行的語句中的this,指向的是元素自己。可是,有一個特例,來改動一下方式。函數
<div id="div1" onclick="(function () {console.log( this )}()"></div>
複製代碼
看明白了嗎,元素內聯的是一個匿名自執行函數,這個時候匿名自執行函數中的this,就不是指向元素自己了,而是window對象!雖然這種寫法很無聊,但這就是內聯寫法咱們須要注意的一個點。咱們能夠這樣理解,匿名自執行函數有獨立的做用域,至關因而window在調用它。這種狀況,知道就好,無需太花力氣死磕。ui
let div1 = document.getElementById("div1");
div1.onclick = function() {
console.log( this ); // div1
}
複製代碼
這是經過動態綁定的方式,給元素添加了事件,這種狀況下,當回調函數執行的時候,是元素div1在調用它,因此此時函數內部的this,是指向元素div1的。this
let div1 = document.getElementById("div1");
div1.addEventListener("click", function() {
console.log( this ); // div1
}, false);
複製代碼
一樣的,經過事件監聽器的方式綁定的回調函數,內部的this也是指向div1。因此咱們能夠總結一下得知:事件處理函數中的this,指向的是觸發這個事件的元素。spa
在JavaScript中,對象是能夠有屬性和方法的,這個方法,其實就是函數。既然是函數,那麼內部確定也會有this,做爲對象方法中的this,究竟是指的什麼呢?看個簡單的例子。
var name = 'aaa';
let obj = {
name: 'jack',
fn: function() {
console.log( this.name );
}
}
let f1 = obj.fn;
obj.fn(); // jack
f1(); // aaa
複製代碼
做爲對象的方法調用的函數,它內部的this,就指向這個對象。在這個例子中,當經過obj.fn()的形式調用fn函數的時候,它內部的this指的就是obj這個對象了。至於第二種狀況,先把obj.fn賦值給f1,而後經過執行f1來執行函數的狀況,咱們在上面已經說過,這個時候,實際上是window對象在調用f1,所以它內部的this就是指向window對象,於是打印的就是'aaa'。
若是是一個對象中嵌套着比較深的方法,它內部的this又是什麼呢?
let person = {
name: 'jack',
eat: {
name: 'apple',
fn1: function() {
console.log( this.name );
},
obj: {
name: 'grape',
fn2: function() {
console.log( this.name );
}
}
}
}
person.eat.fn1(); // apple
person.eat.obj.fn2(); // grape
複製代碼
這裏遵照一個就近原則:若是是經過對象方法的方式調用函數,則函數內部的this指向離它最近一級的那個對象。在這個例子中,person.eat.fn1()這種調用,fn1中的this指的就是eat這個對象;person.eat.obj.fn2()這種調用方式,fn2中的this,指的就是obj這個對象。
構造函數其實就是普通的函數,只是它內部通常都書寫了許多this,能夠經過new的方式調用來生成實例,因此咱們通常都用首字母大寫的方式,來區分構造函數和通常的函數。構造函數,是JavaScript中書寫面向對象的重要方式。
function Fn1(name) {
this.name = name;
}
let n1 = new Fn1('abc');
n1.name; // abc
複製代碼
這是一個很是簡單的構造函數書寫方式,以及對構造函數的調用。構造函數中的this,以及new調用的這種方式,其實都是爲了可以創造實例服務的,不然也就沒有意義了。那麼,構造函數中的this也就很清楚了:它指向構造函數所創造的實例。當經過new方法調用構造函數的時候,構造函數內部的this就指向這實例,並將相應的屬性和方法"生成"給這個實例。經過這個方法,生成的實例纔可以獲取屬性和方法。
凡事總有例外嘛,構造函數中有這樣一種例外,咱們看看。
function Fn1(name) {
this.name = name;
return null;
}
function Fn2(name) {
this.name = name;
return {a: '123'};
}
let f1 = new Fn1("ttt");
console.log( f1 ); // {name: "ttt"}
let f2 = new Fn2("ggg");
console.log( f2 ); // {a: "123"}
複製代碼
f1是經過new Fn1建立的一個實例,這沒有問題。但f2爲何不是咱們所想的結果呢? 當構造函數內部return的是一個對象類型的數據的時候,經過new所獲得的,就是構造函數return出來的那個對象;當構造函數內部return的是基本類型數據(數字,字符串,布爾值,undefined,null),那麼對於建立實例沒有影響。
原型鏈函數中個this,其實跟構造函數中的this同樣,也是指向建立的那個實例。
function Fn() {
this.name = '878978'
}
Fn.prototype.sum = function() {
console.log(this)
return this;
}
let f5 = new Fn();
let f6 = new Fn();
console.log( f5 === f5.sum() ); // true
console.log( f6 === f6.sum() ); // true
複製代碼
咱們知道,JavaScript中getter和setter是做爲對對象屬性讀取和修改的一種劫持。能夠分別在讀取和設置對象相應屬性的時候觸發。
let obj = {
n: 1,
m: 2,
get sum() {
console.log(this.n, this.m);
return '正在嘗試訪問sum...';
},
set sum(k) {
this.m = k;
return '正在設置obj屬性sum...';
}
}
obj.sum; // 1,2
obj.sum = 5; // 正在設置obj屬性sum..
複製代碼
getter和setter中的this,規則跟做爲對象方法調用時候函數內部的this指向是同樣的,它指的就是這個對象自己。
箭頭函數是ES6中新推出的一種函數簡寫方法,跟ES5函數最大的區別,就要數它的this規則了。在ES5的函數中,this都是在函數調用的時候,才能肯定具體的this指向。而箭頭函數,實際上是沒有this的,可是它內部的這個所謂this,在箭頭函數書寫的時候,就已經綁定了(綁定父級的this),而且沒法改變。看個例子。
let div1 = document.getElementById("div");
div1.onclick = function() {
setTimeout(() => {
console.log( this ); // div1
}, 500);
}
複製代碼
咱們知道,setTimeout中所綁定的回調函數,實際上是window在調用它,因此它內部的this指向的是window。可是,當回調函數是箭頭函數的寫法的時候,內部的this居然是div1!這在箭頭函數書寫的時候,就已經決定了它內部的this指向,就是它父級的this。而它父級函數做用域中的this,其實就是元素div1。做爲對象方法的箭頭函數,其實也是相似的道理。
var name = 'aaa';
let obj = {
name: 'jack',
fn1: () => {
console.log( this.name );
}
}
obj.fn1(); // aaa
複製代碼
沒錯,仍是那句話,當咱們寫下箭頭函數的時候,它內部的this就已經肯定了,而且沒法修改(call, apply, bind)。這個例子中,箭頭函數最近的父級做用域顯然是全局環境window,所以它的this就指向window。
說到JavaScript中的this,就無法不說call, apply, bind這三個方法。在全部JavaScript函數的高級用法,或者是JavaScript框架中,都會有這三個方法的蹤跡。這三個方法都是Function.prototype上的方法,因此全部的函數都默認繼承了這三個方法。如今具體說說這三個方法的分別用途。
call方法能夠實現對函數的當即調用,而且顯示的指定函數內部的this以及傳參。
let obj = {
color: 'green'
}
function Fn() {
console.log( this.color );
}
Fn(); // undefined
Fn.call(obj); // green
複製代碼
call能夠實現對函數的當即調用,而且改變函數內部的this指向。上面的例子中,直接調用函數Fn的時候,它內部的this指向window對象,所以打印的是undefined;當經過call指定函數內部的this指向obj的時候,它就能獲取到obj上的屬性和方法了。call調用還能實現調用時候的傳參,請看。
let obj = {
color: 'blue'
}
function Fn(height, width) {
console.log(`the tree is ${this.color}, and the tall is ${height}, width is ${width}`);
}
Fn.call(obj, 20, 3); // the tree is blue, and the tall is 20, width is 3
複製代碼
apply的做用和call是如出一轍的,都是實現對函數內部this的改變,惟一的區別就是傳參的方式不同:call是經過一個一個參數的方式傳遞參數,而apply是經過數組的形式傳遞多個參數。
let obj = {
color: 'orange'
}
function Fn(height, width) {
console.log(`the tree is ${this.color}, and the tall is ${height}, width is ${width}`);
}
Fn.apply(obj, [16, 7]); // the tree is orange, and the tall is 16, width is 7
複製代碼
call和apply都是實現對函數的當即調用,而且改變函數內部this的指向,若是說我只想改變函數內部的this,而不執行函數,該怎麼辦?這個時候,就須要用到bind。
let person = {
name: 'jack'
}
function Person() {
console.log(this.name);
}
let p1 = Person.bind(person);
p1(); // 'jack'
複製代碼
當一個函數執行完bind方法後,會返回一個新的函數,而這個新的函數跟原函數相比,內部的this指向被顯示的改變了。可是不會當即執行新的函數,而是在你須要的時候纔去調用。 可是有一點須要注意,返回的新函數p1,它內部的this就沒法再改變了。接着上面的例子。
let animal = {
name: 'animal'
}
let p2 = p1.bind();
p2(); // 'jack'
複製代碼
p2的this依然是指向obj,而非animal。