1. 程式人生 > >JS中this指向的更改

JS中this指向的更改

### JS中this指向的更改 JavaScript 中 [ this 的指向問題]( https://www.cnblogs.com/laozhenHome/p/13262592.html ) 前面已經總結過,但在實際開中, 很多場景都需要改變 `this` 的指向。 現在我們討論更改 `this` 指向的問題。 #### call更改this指向 call 的使用語法:func.call(thisArg, arg1, arg2, ...) call 方法需要一個指定的 `this` 值( this要指向的物件 )和一個或者多個引數。提供的 `this` 值會更改呼叫函式內部的 `this` 指向。 ```javascript // 使用 call 方法改變呼叫函式執行上下文的 this 指向 var animal = '小貓'; var times = '15小時'; function greet() { let str = this.animal + '睡覺時間一般為:' + this.times; console.log(str); } var dogObj = { animal: '小狗', times: '8小時' }; var pigObj = { animal: '小豬', times: '13小時' } greet(); // 小貓睡覺時間一般為:15小時 greet.call(dogObj); // 小狗睡覺時間一般為:8小時 greet.call(pigObj); // 小豬睡覺時間一般為:13小時 greet.call(); // 小貓睡覺時間一般為:15小時 ``` 當直接呼叫函式 greet 時,函式 greet 內部的 `this` 指向的是全域性物件 Window。 函式 greet 呼叫 call() 方法並傳遞物件 dogObj 時,函式 greet 內部的 `this` 就指向了物件 dogObj 。 函式 greet 呼叫 call() 方法並傳遞物件 pigObj 時,函式 greet 內部的 `this` 就指向了物件 pigObj 。 call()不傳參的話,在嚴格模式下,`this` 的值將會是 undefined;否則將會指向全域性物件 Window。 匿名函式呼叫call方法: ```javascript var books = [{ name: 'CSS選擇器', price: 23 }, { name: 'CSS世界', price: 35 }, { name: 'JavaScript語言設計', price: 55 }]; for (var i = 0; i < books.length; i++) { (function (i) { // 這裡this指向的是call繫結的陣列的每一個元素物件 this.printf = function () { console.log(`${i} ${this.name}: ¥${this.price}`); } this.printf(); }).call(books[i], i); } // 列印結果如下: // 0 CSS選擇器: ¥23 // 1 CSS世界: ¥35 // 2 JavaScript語言設計: ¥55 ``` call實現繼承: ```javascript // 實現兩個數相加的建構函式 function CalcA(){ this.add = function(a, b){ return a + b; } } // 實現兩個數相減的建構函式 function CalcS(){ this.sub = function(a, b){ return a - b; } } // 計算建構函式 function Calc(){ console.log(this); // Calc {} CalcA.call(this); CalcS.call(this); console.log(this); // Calc {add: ƒ, sub: ƒ} } var calc = new Calc(); console.log(calc.add(2, 3)); // 5 console.log(calc.sub(10, 1));// 9 ``` 建構函式 Calc 通過 call 方法使建構函式 CalcA、CalcS中的 `this` 指向了 Calc 自己,從而繼承了它們的屬性及方法。所以,建構函式 Calc 生成的例項物件也能夠訪問建構函式 CalcA、CalcS中的屬性及方法。 #### apply方法更改this指向 apply 的使用語法:func.apply(thisArg, [argsArray]) apply 的用法與 call 方法類似,只不過 call 方法接受的是引數列表,而 apply 方法接受的是一個數組或者類陣列物件。上面的例子完全可以將 call 更換為 apply,只不過 apply 方法只能接受兩個引數,而且第二個引數是一個數組或者類陣列物件。 #### bind方法更改this指向 bind 的使用語法:func.bind(thisArg, arg1, arg2, ...) bind 的引數與 call 相同,但是 bind 返回的是一個改變this指向後的函式例項。 ```javascript var petalNum = 100; function Flower() { this.petalNum = Math.ceil(Math.random() * 10) + 1; } Flower.prototype.declare = function() { console.log(this); console.log('this is a beautiful flower with ' + this.petalNum + ' petals'); } Flower.prototype.bloom = function() { console.log(this); // Flower {petalNum: 7} // 如果不繫結 this 就會指向 Window 全域性物件 window.setTimeout(this.declare, 1000); // bind 繫結 this,指向 Flower 的原型物件 window.setTimeout(this.declare.bind(this), 2000); } var flower = new Flower(); flower.bloom(); ``` 例項物件 flower 呼叫 bloom 方法後,bloom 內的 `this` 指向建構函式的原型物件。 1 秒後延遲函式呼叫建構函式的 declare 方法, 此時執行函式 declare 中的 `this` 指向 Window 。列印的結果如下: ```javascript // Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …} // this is a beautiful flower with 100 petals ``` 2 秒後延遲函式呼叫建構函式的 declare 方法,此時執行函式 declare 通過 bind 將 this(建構函式的原型物件)繫結。列印的結果如下: ```javascript // 注意,此時petalNum的值時隨機取的。 // Flower {petalNum: 7} // this is a beautiful flower with 7 petals ``` 這裡將 bind換 成 call,apply 會導致立即執行,延遲效果會失效。 #### ES6的箭頭函式更改this指向 箭頭函式中的 `this` 是在定義函式的時候繫結,而不是在執行函式的時候繫結。 所謂定義時候繫結,就是指 `this` 是繼承自父執行上下文的 `this`。 ```javascript var a = 1; var obj = { a: 2, f1: function(){ console.log(this.a) }, f2: () => { console.log(this.a) } } obj.f1(); // 2 obj.f2(); // 1 ``` obj.f1() 執行後列印的是 2,這裡好理解,obj 呼叫 f1 函式,那麼函式中的 `this` 就指向呼叫物件 obj。可以看出,這裡 `this` 是在執行函式的時候繫結的。 obj.f2() 執行後列印的是 1。f2 是箭頭函式,那麼函式中的 `this` 是繼承自父執行上下文的 `this`。這裡箭頭函式的父級是物件 obj,obj 的執行上下文就是全域性物件 Window,那麼箭頭函式中的 `this` 就指向了全域性物件了。 再看一個例子: ```javascript var a = 11; function test() { this.a = 22; let b = () => { console.log(this.a) } b(); } test(); // 22 ``` 按著定義的理解,應該打印出 11 才對呀,因為箭頭函式父級的執行上下文就是 Window 全域性物件,此時列印的是全域性物件的 a。 先不要著急,先慢慢分析,上面的分析是對的,箭頭函式的 `this` 就是指向 Window 物件。test 函式在全域性環境下呼叫時其內部的 `this` 就指向了全域性 Window 物件,程式碼中的 `this.a = 22;`就將全域性中的 a 重新賦值了,所以箭頭函式在全劇物件中找到的 a 值就是 22。我們可以在控制檯上輸入 `window.a` 檢視全域性物件中的 a 值,結果列印 22,所以我們就不難理解箭頭函式中列印的結果為什麼是 22 了。如果將程式碼中的 `this.a = 22;` 修改為 `var a = 22;`,那麼箭頭函式中列印的結果就是 11 了。 箭頭函式會繼承外層函式呼叫的 `this` 繫結,這和 `var self = this;`的繫結機制一樣。箭頭函式中,`this` 指向固定化,箭頭函式根本就沒有自己的 `this`, 所以也就不能用作建構函式使用了。