在JavaScript中,this關鍵字可以說是最複雜的機制之一。對this的作用機制缺乏比較深入的理解很容易在實際開發中出現問題。 1、this的作用 為什麼要在JavaScript中使用this呢?因為this提供了一種簡明的方式來隱式傳遞一個對象引用,可以讓函數介面設計的簡單且容易復用: 通過 ...
在JavaScript中,this關鍵字可以說是最複雜的機制之一。對this的作用機制缺乏比較深入的理解很容易在實際開發中出現問題。
1、this的作用
為什麼要在JavaScript中使用this呢?因為this提供了一種簡明的方式來隱式傳遞一個對象引用,可以讓函數介面設計的簡單且容易復用:
function display() { console.log(this.name); } var obj1 = {name: "obj1"}; var obj2 = {name: "obj2"}; display.call(obj1); // "obj1" display.call(obj2); // "obj2"
通過call方法,我們可以在調用display函數時為this傳入不同的對象。如果不使用this關鍵字,那麼上面的函數就需要顯示增加一個調用時上下文參數:
function display(context) { console.log(context.name); } var obj1 = {name: "obj1"}; var obj2 = {name: "obj2"}; display(obj1); // "obj1" display(obj2); // "obj2"
實際上這不夠簡潔,當使用模式比較複雜時,顯示的上下文傳遞會讓代碼變得混亂複雜。使用this關鍵字,我們可以在調用時為this傳入不同的對象引用,保證了方法的使用靈活性。
2、this的使用複雜性
this使用機制複雜,在開發容易出問題的根本原因在於:this是在運行時綁定,而不是在編寫時綁定,this實際值取決於函數調用時的上下文。this的綁定和函數聲明的位置沒有關係,只取決於函數的調用方式。在JavaScript中,當函數被調用時,會創建一個活動記錄(執行時上下文),這個記錄包含函數在何處調用、函數的調用方法和傳入參數等信息,this會記錄其中一個屬性。判斷this實際綁定值,關鍵在於分析函數實際調用的位置。
3、this綁定規則
前面說了函數的實際調用位置決定了this的綁定值。在JavaScript中,this有4種綁定規則。
3.1、new綁定
在JavaScript中使用new調用函數會自動執行下麵的操作:
(1)創建一個新的對象
(2)對新對象執行原型鏈接
(3)新對象會被綁定到函數的this
(4)如果函數沒有返回其他對象,那麼新對象會被返回
new綁定容易理解,下麵是一段常見的用new調用函數創建對象的代碼:
function Book(name, author, isbn) { this.name = name; this.author = author; this.isbn = isbn; } let book = new Book("Zakas", "ES6", 12345); console.log(book.name); // "Zakas"
3.2、隱式綁定
當對象內部包含一個指向函數的屬性,並且在調用時通過這個屬性間接引用函數(obj.prop()的形式),那麼函數內的this會隱式指向這個對象,也即隱式綁定:
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
在調用位置上,函數是通過obj.foo來引用的,可以說函數被調用時obj對象擁有或包含它。此時,this綁定在obj這個上下文對象上。
3.3、顯示綁定
在某些情況下,我們希望函數內的this綁定在某些指定的對象上,這稱為顯示綁定。在JavaScript中可以使用call和apply為函數顯示指定this綁定。call和apply的第一個參數是一個對象,這個對象會被綁定到this上:
function foo() { console.log(this.a); } var obj = { a:2 }; foo.call(obj); // 2
使用bind也可以讓this綁定在指定對象上,bind綁定也是一種顯示綁定,又稱為硬綁定:
function foo(something) { this.a = something; } var obj1 = {}; var bar = foo.bind(obj1); bar(2); console.log(obj1.a); // 2
3.4、預設綁定
當使用獨立函數調用(func()形式),會發生預設綁定,可以把這條規則看成是無法使用其他規則時的預設規則。看下麵的示例代碼:
function foo() { console.log( this.a ); } var a = 2; foo(); // 2
當調用foo時,使用預設綁定規則,this被綁定到全局對象上。在strict模式下,this會綁定到undefined。
4、綁定優先順序
上面4種綁定規則獨立使用的話,判斷this的綁定值並不複雜。但實際函數調用時,可能多條綁定規則都可以使用,那麼這時就要根據每個規則的綁定優先順序來判斷this實際的綁定值。接下來看各種綁定規則的優先順序。
4.1、預設綁定優先順序最低
預設綁定的優先順序最低,這個容易理解。因為當無法使用其他的綁定規則時才會使用預設規則。
4.2、顯示綁定優先順序高於隱式綁定
function foo() { console.log(this.a); } var obj1 = {a: 2, foo: foo}; var obj2 = {a: 3, foo: foo}; obj1.foo(); // 2 obj1.foo.call(obj2); // 3
上面的代碼中,obj1.foo()使用隱式綁定規則,this綁定到obj1對象上。obj1.foo.call()可同時使用隱式綁定和顯示綁定規則,顯示綁定優先順序高於隱式綁定,this綁定到obj2對象上。
4.3、new綁定優先順序高於隱式綁定
function foo(something) { this.a = something; } var obj1 = { foo: foo }; var obj2 = {}; obj1.foo(2); console.log(obj1.a); // 2 obj1.foo.call(obj2, 3); console.log(obj2.a); // 3 var bar = new obj1.foo(4); console.log(obj1.a); // 2 console.log(bar.a); // 4
上面代碼中,new obj1.foo(4)可同時使用new綁定和隱式綁定。由bar.a的值為4可以知道,new綁定優先順序高於隱式綁定。
4.4、new綁定優先順序高於顯示綁定
function foo(something) { this.a = something; } var obj1 = {}; var bar = foo.bind(obj1); bar(2); console.log(obj1.a); // 2 var baz = new bar(3); console.log(obj1.a); // 2 console.log(baz.a); // 3
上面代碼中,new bar(3)可同時使用new綁定和bind綁定。baz.a的值為3,說明new綁定優先順序高於隱式綁定。
4.5、綜述
現在可以根據this綁定優先順序判斷函數在調用位置實際綁定的值。實際可以按照下麵的順序判斷:
(1、函數是否在new中調用?如果是的話this綁定新創建的對象。調用例子:var bar = new foo()。
(2、函數是否通過apply、call顯示綁定或者bind硬綁定?如果是,this綁定指定的對象。調用例子:var bar = foo.call(obj)。
(3、函數是否在某個上下文對象中調用(隱式綁定)?如果是,this綁定在上下文對象上。調用例子:var bar = obj.foo()。
(4、如果都不是的話,使用預設綁定。在嚴格模式下,this綁定到undefined,在非嚴格模式下,綁定到全局對象。調用例子:var bar = foo()。
5、箭頭函數中的this
ES6中引入了箭頭函數,箭頭函數使用操作符=>定義。箭頭函數不使用上面4種this綁定規則,而是根據外層作用域來決定this:
function foo() { return (a) => { console.log(this.a); }; } var obj1 = {a:2}; var obj2 = {a:3}; var bar = foo.call(obj1); bar.call(obj2); // 2
foo內部的箭頭函數創建時,foo函數內this綁定到obj1上,bar(箭頭函數)的this也會綁定到obj1上,箭頭函數內的this是不能被修改的。