全文約 2000 字,讀完大約須要 5 分鐘git
今天咱們就來啃下這個你可能懼怕但又不得不去吃的瓜,this
!github
首先, this 在大多數狀況下是一個對象,也有多是 undefined 或其餘值。數組
什麼狀況下,this
是 undefined
?函數運行在嚴格模式下,應用默認綁定規則的時候:markdown
var a = 1;
function foo() {
"use strict";
console.log(this.a);
};
foo(); // Uncaught TypeError: Cannot read property 'a' of undefined
複製代碼
原理其實很簡單,由於規範定義了嚴格模式下,不能將全局對象 Window
用於默認綁定。而大多數狀況下,咱們說的 this
,其實就是一個對象,因此肯定 this
的指向,本質上就是要找到這個對象。app
因此接下來我就來教你們如何 「找對象」 🤣 。函數
找對象最重要的是什麼?是否是得先經過各類途徑(社交,搭訕,相親...)去認識對象,途徑越多,咱們找到對象的概率就越大,對吧,這裏也是同樣,因此咱們須要儘量的瞭解 this
的綁定規則。oop
ECMAScript 5規範 定義的 this
的綁定規則,有 4 種。ui
教科書會告訴咱們,幾乎全部的規則都會有一個默認的狀況,this
this
綁定也不例外,默認綁定的規則爲:es5
非嚴格模式下,this 指向全局對象,嚴格模式下,this 會綁定到 undefined。
var a = 1;
function foo() {
console.log(this.a);
};
function bar() {
"use strict";
console.log(this.a);
};
foo(); // 1,非嚴格模式下,this 指向全局對象 Window,這裏至關於 Window.a
bar(); // Uncaught TypeError: Cannot read property 'a' of undefined,嚴格模式下,this 會綁定到 undefined,嘗試從 undefined 讀取屬性會報錯
複製代碼
若是函數在調用位置有上下文對象,this 就會隱式地綁定到這個對象上,
提及來有點晦澀,直接看例子:
var a = 1;
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo, // <-- foo 的調用位置
};
obj.foo(); // 2,foo 在調用位置有上下文對象 obj,this 會隱式地綁定到 obj,this.a 至關於 obj.a
複製代碼
這個規則可能會讓你想起關於 this
常常聽到的一句話,this 依賴於調用函數前的對象。
須要注意的是,隱式綁定在某些狀況下可能會致使綁定丟失,具體來講有兩種狀況,
第一種是使用函數別名調用時:
var a = 1;
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo,
};
var bar = obj.foo;
bar(); // 1,賦值並不會改變引用自己,使用函數別名調用時,bar 雖然是 obj.foo 的一個引用,可是實際上引用的仍是 foo 函數自己,因此這裏隱式綁定並無生效, this 應用的是默認綁定
複製代碼
第二種是函數做爲參數傳遞時:
function foo() {
console.log(this.a);
};
function bar(fn) {
fn(); // <-- 調用位置
};
var a = 1;
var obj = {
a: 2,
foo: foo,
};
bar(obj.foo); // 1, 參數傳遞也是一種隱式賦值,即便傳入的是函數,這裏至關於 fn = obj.foo,因此 fn 實際上引用的仍是 foo 函數自己,this 應用默認綁定
複製代碼
咱們知道 call
,apply
,bind
等方法能夠改變 this
的指向,經過傳入參數就能夠指定 this
的綁定值,夠不夠顯式 ?這種明目張膽的綁定 this
的規則就叫顯式綁定。
call
和 apply
的區別只是接受的參數格式不一樣,call
接受一個參數列表,apply
接受一個參數數組,但二者的第一個參數都是相同的,都是 綁定的 this 值:
function foo() {
console.log(this.a);
};
var a = 1;
var obj = { a: 2 };
foo.call(obj); // 2,調用時顯式地將 foo 的 this 綁定爲 obj 對象,因此這裏的 this.a 至關於 obj.a
foo.apply(obj); // 2,同理
複製代碼
前文咱們提到隱式綁定可能會致使綁定丟失,顯式綁定也不例外,
思考一下,如何才能解決綁定丟失的問題?
答案其實很簡單,只須要在調用函數的內部使用顯式綁定,強制地將 this
綁定到對象:
function foo() {
console.log(this.a);
};
var obj = {
a: 2,
foo: foo,
};
function bar(fn) {
fn.call(obj);
};
var a = 1;
bar(obj.foo); // 2,
複製代碼
這其實就是 bind 的實現原理,與 call
,apply
不一樣,bind
調用後不會執行,而是會返回一個硬綁定的函數,因此經過 bind
能夠解決綁定丟失的問題。bind
也是顯式綁定,咱們來回顧下 bind
的用法:
function foo() {
console.log(this.a);
};
var obj = { a: 2 };
var a = 1;
var bar = foo.bind(obj);
bar(); // 2,bar 是經過 bind 返回後的一個硬綁定函數,其內部應用了顯式綁定
複製代碼
此外,還須要注意的是,將 null
,undefined
做爲第一個參數傳入 call
,apply
,bind
,調用時會被忽略,實際應用的是默認綁定規則,即嚴格模式下,this
爲 undefined
,非嚴格模式下爲全局對象。
先來回顧下 new 的實現原理,
function _new() {
let obj = new Object(); // 1. 建立一個空對象
let Con = [].shift.call(arguments); // 2. 得到構造函數
obj.__proto__ = Con.prototype; // 3. 連接到原型
let result = Con.apply(obj, arguments); // 4. 綁定 this,執行構造函數
return typeof result === 'object' ? result : obj; // 5. 返回 new 出來的對象
}
複製代碼
在使用 new
來調用函數時,會建立一個連接到函數原型的對象,並把它綁定到函數調用的 this
,因此應用了 new
綁定規則後,不會被任何方式修改 this
指向:
function foo(a) {
this.a = a;
};
var bar = new foo(2);
bar.a; // 2,new 會返回一個對象,這個對象綁定到構造函數的 this
複製代碼
ES6 中新增了一種函數類型,箭頭函數,箭頭函數中 this
不會應用上述規則,而是根據最外層的詞法做用域來肯定 this,簡單來講,箭頭函數的 this
就是它外面第一個不是箭頭函數的函數的 this:
function foo() {
return () => {
return () => {
console.log(this.a);
};
};
};
foo()(); // undefined,箭頭函數調用時,this 取決於最外層的第一個不是箭頭函數的函數,這裏就是 foo 函數,非嚴格模式下,默認綁定全局對象 Window,this.a 至關於 Window.a,輸出 undefined
複製代碼
this
綁定的優先級爲:new綁定 > 顯式綁定 > 隱式綁定 > 默認綁定。
根據綁定規則和優先級,咱們能夠總結出 this
判斷的一些通用模式,
new
調用?call
,apply
,bind
調用?本文首發於個人 博客,才疏學淺,不免有錯誤,文章有誤之處還望不吝指正!
若是有疑問或者發現錯誤,能夠在相應的 issues 進行提問或勘誤
若是喜歡或者有所啓發,歡迎 star,對做者也是一種鼓勵
(完)