this關鍵字是JavaScript中最複雜的機制之一,是一個特別的關鍵字,被自動定義在全部函數的做用域中,可是不少JavaScript開發者並非很是清楚它究竟指向的是什麼。node
請先回答第一個問題,如何準確地判斷this指向的是什麼?面試
再看一道題。控制檯打印出來是什麼?【瀏覽器運行環境】瀏覽器
var number = 5 var obj = { number: 3, fn1: (function () { var number; this.number *= 2; number = number * 2; number = 3; return function() { var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number) } })() }
<br> this不是指向自身!this就是一個指針,指向調用函數的對象。 閉包
<br><br> 爲了可以一眼看出this指向的是什麼,首先須要知道this的綁定規則有哪些:app
默認綁定,再不能應用其餘綁定規則時使用的默認規則,一般是獨立函數調用。函數
function sayHi () { console.log('Hello,',this.name); } var name = 'make' sayHi();
在調用sayHi()時,應用了默認綁定,this指向全局對象(非嚴格模式下),嚴格模式下,this指向undefined,undefined上沒有this對象,會拋出錯誤。學習
上面的代碼,若是在瀏覽器環境中運行,那麼結果就是Hello,makethis
可是若是在node環境中運行,結果就是Hello,undefined.這是由於node中name並非掛在全局對象上的。指針
函數的調用是在某個對象上觸發的,即調用位置上存在上下文對象。典型的形式爲XXX.fun().先看代碼:rest
function sayHi () { console.log('Hello,',this.name); } var person = { name: 'make', sayHi: sayHi } var name = 'kang'; person.sayHi();
打印的結果是Hello,make
sayHi函數聲明在外部,嚴格來講並不屬於person,可是在調用sayHi時,調用位置會使person的上下文來引用函數,隱式綁定會把函數調用中的this(即此例sayHi函數中的this)綁定到這個上下文對象(即此例中的person)。
須要注意的是: 對象屬性鏈中只有最後一層會影響到調用位置。
function sayHi () { console.log('Hello,',this.name); } var person2 = { name: 'name2', sayHi: sayHi } var person1 = { name: 'name1', friend: person2 } person1.friend.sayHi();
結果是:Hello,name2。
由於只有最後一層會肯定this指向的是什麼,無論有多少層,在判斷this的時候,只關注最後一層,即此處的friend。
隱式綁定有一個大陷阱,綁定很容易丟失(或者說容易給人形成誤導,覺得this指向的是什麼,可是實際上並不是如此)。
function sayHi () { console.log('Hello,',this.name); } var person = { name: 'name1', sayHi: sayHi } var name = 'name2'; var Hi = person.sayHi; Hi();
結果是:Hello,name2
這是爲何呢,Hi直接指向了sayHi的引用,在調用的時候,跟person沒有半毛錢的關係,針對此類問題,建議你們緊緊記住這個格式:XXX.fn(); fn()前若是什麼都沒有,那麼確定不是隱式綁定,可是也不必定就是默認綁定!!!
除了上面的這種丟失以外,隱式綁定的丟失是發生在回調函數中(事件回調也是其中一種),看下面的例子:
function sayHi () { console.log('Hello,',this.name); } var person1 = { name: 'name1', sayHi: function () { setimeout(function(){ console.log('Hello,',this.name); } } } var person2 = { name: 'name2', sayHi:sayHi } var name = 'name3'; person1.sayHi(); setTimeout(person2.sayHi,100); setTimeout(function(){ person2.sayHi(); },200)
結果爲:
Hello,name3 Hello.name3 Hello,name2
顯示綁定就是經過call,apple,bind的方式,顯式的指定this所指向的對象(注意:《你不知道的JavaScript》中將bind單獨做爲了硬綁定講解了)。
call,apple和bind的第一個參數,就是對應函數的this所指向的對象。call和apply的做用同樣,只是傳參方式不一樣。call和apply都會執行對應的函數,而bind方法不會。
function sayHi () { console.log('Hello',this.name); } var person = { name: 'name1', sayHi: sayHi } var name = 'name' var Hi = person.sayHi; Hi.call(person); // Hi.apply(person)
輸出結果爲:Hello,name1.由於使用硬綁定明確將this綁定在了person上。
那麼,使用了硬綁定,是否是意味着不會出現隱式綁定所遇到的綁定丟失呢?答案是:並非!!!
function sayHi () { console.log('Hello',this.name); } var person = { name: 'name1', sayHi: sayHi } var name = 'name'; var Hi = function(fn){ fn(); } Hi.call(person,person.sayHi);
輸出的結果是Hello,name.緣由很簡單,Hi.call(person,person.sayHi)的確是將this綁定到Hi中的this了。可是在執行fn的時候,至關於直接調用了sayHi方法(記住:person.sayHi已經被賦值給fn了,隱式綁定也丟了),沒有指定this的值,對應的是默認綁定。
若是但願綁定不會丟失,要怎麼作?很簡單,調用fn的時候,也給他硬綁定。
function sayHi() { console.log('Hello,',this.name); } var person = { name = 'name1', sayHi: sayHi } var name = 'name'; var Hi = function(fn) { fn.call(this); } Hi.call(person,person.sayHi);
此時,輸出的結果爲:Hello,name1,由於person被綁定到Hi函數中的this上,fn又將這個對象綁定給了sayHi的函數,這時,sayHi中的this指向的就是person對象。
JavaScript和C++不同,並無類,在JavaScript中,構造函數只是使用new操做符時被調用的函數,這些函數和普通的函數並無什麼不一樣,他不屬於某個類,也不可能實例化出一個類。任何一個函數均可以使用new來調用,所以其實並不存在構造函數,而只有對於函數的「構造調用」。
所以,咱們使用 new 來調用函數的時候,就會更新對象到這個函數的this上。
function sayHi(name) { this.name = name; } var Hi = new sayHi('make'); console.log('Hello,',Hi.name);
輸出結果爲Hello,make,緣由是由於在var Hi = new sayHi('make');這一步,會將sayHi的this綁定到Hi對象上。
this有四種綁定規則,可是若是同時應用了多種規則,怎麼辦?
顯然,須要瞭解那一種綁定方式的優先級更高嗎,這四種綁定的優先級爲:
new 綁定 > 顯示綁定 > 隱式綁定 > 默認綁定
凡事都有例外,this的規則也是這樣。
若是咱們將null或者是undefined做爲this的綁定對象傳入call/apply或者是bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
function sayHi() { console.log('Hello,',this.name); } var person = { name: 'name', sayhi: sayHi } var name1 = 'name1'; var Hi = function(fn) { fn(); } Hi.call(null,parson.sayHi);
輸出的結果是Hello,name1,由於這時實際應用的是默認綁定規則。
箭頭函數是ES6中新增的,它和普通函數有一些區別,箭頭函數沒有本身的this,它的this繼承與外層代碼庫中的this。箭頭函數在使用時,須要注意如下幾點:
OK,看一下箭頭函數的this是什麼:
var obj = { hi: function() { console.log(this); return () => { console.log(this); } }, sayHi: function() { return function() { console.log(this); return () => { console.log(this); } } }, say: () => { console.log(this); } } let hi = obj.hi(); // 輸出 obj 對象 hi(); // 輸出 obj 對象 let sayHi = obj.sayHi(); let fun1 = sayHi(); // 輸出 window fun1(); // 輸出 window obj.say(); // 輸出 window
若是說箭頭函數中的this是定義時所在的對象,這樣的結果顯示不是你們預期的,按照這個定義,say中的this應該是obj纔對。
分析上面的執行結果:
obj.hi();對應了this的默認綁定規則,this綁定在obj上,因此輸出obj。
hi();這一步執行的就是箭頭函數,箭頭函數繼承上一個代碼庫的this,剛剛咱們得出上一層的this是obj,顯然這裏的this就是obj。
執行sayHi();這一步,前面說過這種隱式綁定丟失的狀況,這個時候this執行的是默認綁定,this指向的是全局對象window。
fun1();這一步執行的是箭頭函數,若是按照以前的理解,this指向的是箭頭函數定義時所在的對象,那麼這兒顯然是說不通。OK,按照箭頭函數的this是繼承與外層代碼庫的this就很好理解了。外層代碼庫剛剛分析了,this指向的是window,所以這兒的輸出結果也是window。
obj.say();執行的是箭頭函數,當前代碼塊obj中是不存在this的,只能往上找,就找到了全局的this,指向的是window。
依舊是前面的代碼,來看看箭頭函數中的this真的是靜態嗎? 非也!!!
var obj = { hi: function() { console.log(this); return () => { console.log(this); } }, sayHi: function() { return function() { console.log(this); return () => { console.log(this); } } }, say: function() { console.log(this); } } let sayHi = obj.sayHi(); let fun1 = sayHi(); // 輸出 window fun1(); // 輸出 window let fun2 = sayHi.bind(obj)(); // 輸出 obj fun2(); // 輸出 obj
能夠看出,fun1和fun2對應的是一樣的箭頭函數,可是this的輸出結果是不同的。
函數是否在 new 中調用(new綁定),若是是,那麼this綁定的是新建立的對象。
函數是否經過call,apply調用,或者使用了bind(硬綁定),若是是,那麼this綁定的就是指定的對象。
函數是否在某個上下文對象中調用(隱式模式),若是是的話,this綁定的是那個上下文對象。通常是obj.foo()。
若是以上都不是,那麼使用默認綁定。若是在嚴格模式下,則綁定到undefined,不然會綁定到全局對象。
若是把Null或者undefined做爲this的綁定對象傳入call、apply或者bind,這些值在調用時會被忽略,實際應用的是默認綁定規則。
若是是箭頭函數,箭頭函數的this繼承的是外層代碼塊的this。
var number = 5; var obj = { number: 3; fn: (function() { var number; this.number *= 2; number = number * 2; number = 3; return function() { var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number); } })() } var myFun = obj.fn; myFun.call(null); obj.fn(); console.log(window.number);
咱們來分析一下,這段代碼的執行過程:
window.number *= 2; //window.nuumber 的值是 10(var number 定義的全局變量是掛在window上的) number = number * 2; // number的值是NaN; 注意這邊定義了一個number,可是沒有賦值,number的值是undefined;Number(undefined) -> NaN number = 3; // number 的值爲3
fn: function() { var num = this.number; this.number *= 2; console.log(num); number *= 3; console.log(number); }
執行時:
var num = this.number; // num=10;此時this指向的是window this.number *= 2; // window.number = 20 console.log(num); // 輸出結果爲 10 number *= 3; // number=9;這個number對應閉包中的number;閉包中number的值是3 console.log(number); // 輸出結果是 9
var num = this.number; //num = 3;此時this指向的是obj thia.number *= 2; // obj.number = 6; console.log(num); // 輸出結果爲 3; number *= 3; // number=27;這個number對應的閉包中的number;比保重的number的值此時是 9 console.log(number); // 輸出結果是 27
所以,組中結果爲:
10 9 3 27 20