this 指向問題是入坑前端必須瞭解知識點,如今迎來了ES6時代,由於箭頭函數的出現,因此感受有必要對 this 問題梳理一下,遂有此文前端
在非箭頭函數下, this 指向調用其所在函數的對象,並且是離誰近就是指向誰(此對於常規對象,原型鏈, getter & setter等都適用);構造函數下,this與被建立的新對象綁定;DOM事件,this指向觸發事件的元素;內聯事件分兩種狀況,bind綁定, call & apply 方法等, 容如下一步一步討論。箭頭函數也會穿插其中進行討論。瀏覽器
在全局環境下,this 始終指向全局對象(window), 不管是否嚴格模式;app
console.log(this.document === document); // true // 在瀏覽器中,全局對象爲 window 對象: console.log(this === window); // true this.a = 37; console.log(window.a); // 37
普通函數內部的this分兩種狀況,嚴格模式和非嚴格模式。函數
嚴格模式下,this 的值默認爲全局對象window測試
function f1(){ return this; } f1() === window; // true
非嚴格模式, this的值爲undefinedui
function f2(){ "use strict"; // 這裏是嚴格模式 return this; } f2() === undefined; // true
對象內部方法的this指向調用這些方法的對象,this
//1 var o = { prop: 37, f: function() { return this.prop; } }; console.log(o.f()); //37 var a = o.f; console.log(a()): //undefined var o = {prop: 37}; function independent() { return this.prop; } o.f = independent; console.log(o.f()); // logs 37 //2 o.b = { g: independent, prop: 42 }; console.log(o.b.g()); // logs 42
原型鏈中的方法的this仍然指向調用它的對象,與以上討論一致;看個例子,spa
var o = { f : function(){ return this.a + this.b; } }; var p = Object.create(o); p.a = 1; p.b = 4; console.log(p.f()); // 5
能夠看出, 在p中沒有屬性f,當執行p.f()時,會查找p的原型鏈,找到 f 函數並執行,但這與函數內部this指向對象 p 沒有任何關係,只需記住誰調用指向誰。prototype
以上對於函數做爲getter & setter 調用時一樣適用。code
構造函數中的this與被建立的新對象綁定。
注意:當構造器返回的默認值是一個this引用的對象時,能夠手動設置返回其餘的對象,若是返回值不是一個對象,返回this。
function C(){ this.a = 37; } var o = new C(); console.log(o.a); // logs 37 function C2(){ this.a = 37; return {a:38}; } var b = new C2(); console.log(b.a); // logs 38
以上兩個例子內部的this都指向對象o, 看到這裏的你不妨在控制檯執行下以上代碼,看看對象 o 和 b ,這些是屬於構造函數的內容了,此處很少作介紹。(C2函數中的this.a = 37 對整個過程徹底沒有影響的, 能夠被忽略的)。
當函數經過Function對象的原型中繼承的方法 call() 和 apply() 方法調用時, 其函數內部的this值可綁定到 call() & apply() 方法指定的第一個對象上, 若是第一個參數不是對象,JavaScript內部會嘗試將其轉換成對象而後指向它。
例子:
function add(c, d){ return this.a + this.b + c + d; } var o = {a:1, b:3}; add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34 function tt() { console.log(this); } // 返回對象見下圖(圖1) tt.call(5); // Number {[[PrimitiveValue]]: 5} tt.call('asd'); // String {0: "a", 1: "s", 2: "d", length: 3, [[PrimitiveValue]]: "asd"}
bind方法在ES5引入, 在Function的原型鏈上, Function.prototype.bind
。經過bind方法綁定後, 函數將被永遠綁定在其第一個參數對象上, 而不管其在什麼狀況下被調用。
function f(){ return this.a; } var g = f.bind({a:"azerty"}); console.log(g()); // azerty var o = {a:37, f:f, g:g}; console.log(o.f(), o.g()); // 37, azerty
DOM事件處理函數
// 被調用時,將關聯的元素變成藍色 function bluify(e){ //在控制檯打印出所點擊元素 console.log(this); //阻止時間冒泡 e.stopPropagation(); //阻止元素的默認事件 e.preventDefault(); this.style.backgroundColor = '#A5D9F3'; } // 獲取文檔中的全部元素的列表 var elements = document.getElementsByTagName('*'); // 將bluify做爲元素的點擊監聽函數,當元素被點擊時,就會變成藍色 for(var i=0 ; i<elements.length ; i++){ elements[i].addEventListener('click', bluify, false); }
以上代碼建議在網頁中執行如下,看下效果。
內聯事件
內聯事件中的this指向分兩種狀況:
頁面的代碼塊
在瀏覽器內顯示三個按鈕
依次點擊上邊的三個按鈕後在控制檯的輸出結果,
建議本身操做一遍以便於更好的理解。
對於延時函數內部的回調函數的this指向全局對象window(固然咱們能夠經過bind方法改變其內部函數的this指向)
看下邊代碼及截圖
//默認狀況下代碼 function Person() { this.age = 0; setTimeout(function() { console.log(this); }, 3000); } var p = new Person();//3秒後返回 window 對象 ============================================== //經過bind綁定 function Person() { this.age = 0; setTimeout((function() { console.log(this); }).bind(this), 3000); } var p = new Person();//3秒後返回構造函數新生成的對象 Person{...}
因爲箭頭函數不綁定this, 它會捕獲其所在(即定義的位置)上下文的this值, 做爲本身的this值,
由於箭頭函數能夠捕獲其所在上下文的this值 因此
function Person() { this.age = 0; setInterval(() => { // 回調裏面的 `this` 變量就指向了指望的那個對象了 this.age++; }, 3000); } var p = new Person();
以上代碼能夠獲得咱們因此但願的值,下圖能夠看到,在setTimeout中的this指向了構造函數新生成的對象,而普通函數指向了全局window對象
var adder = { base : 1, add : function(a) { var f = v => v + this.base; return f(a); }, addThruCall: function inFun(a) { var f = v => v + this.base; var b = { base : 2 }; return f.call(b, a); } }; console.log(adder.add(1)); // 輸出 2 console.log(adder.addThruCall(1)); // 仍然輸出 2(而不是3,其內部的this並無由於call() 而改變,其this值仍然爲函數inFun的this值,指向對象adder
bind() & apply() 讀者能夠自行測試
對因而否嚴格模式示例代碼(能夠複製進控制檯進行驗證)
var f = () => {'use strict'; return this}; var p = () => { return this}; console.log(1,f() === window); console.log(2,f() === p()); //1 true //2 true
以上的箭頭函數都是在方法內部,總之都是以非方法的方式使用,若是將箭頭函數當作一個方法使用會怎樣呢?
上例子
var obj = { i: 10, b: () => console.log(this.i, this), c: function() { console.log( this.i, this) } } obj.b(); // undefined window{...} obj.c(); // 10 Object {...}
能夠看到,做爲方法的箭頭函數this指向全局window對象,而普通函數則指向調用它的對象