🚩 這個專欄是一個 JS 進階系列,當前內容為 JS 執行機制,建議按順序閱讀
執行上下文&作用域
詞法環境&變量環境
this(上下文對象) 🔹
概述
🌍 前提概要:
在上文 執行上下文&作用域 中,我們已經知道
- 執行上下文的
thisValue
和this
等價- 全局執行上下文的
this
指向全局對象
而函數執行上下文中的 thisValue
指向 ,我卻一筆帶過 ,現在打算放在這篇文章幫它整明白!
💡 我們每次調用函數時,解析器都會將一個 上下文對象 作為 隱含參數 傳遞進函數。 使用 this 來引用上下文對象,根據函數的 調用方式 不同,this 的值也不同。
函數中的 this
普通函數調用
在普通函數調用中,this
指向全局對象(非嚴格模式)或 undefined
(嚴格模式)。
🌰舉個栗子
let a = {b: function () {let func = function () {console.log(this); //Window}func(); //普通函數調用},}
a.b(); //打印 Window
方法調用
當函數作為對象的方法調用時,this
指向調用該方法的對象。
🌰舉個栗子
const obj = {name: 'Alice',greet: function() {console.log(this.name);}
};obj.greet(); // 輸出: Alice
構造函數調用
當函數作為構造函數調用(使用 new
關鍵字)時,this
指向新創建的對象。
🌰舉個栗子
function Person(name) {this.name = name;
}const alice = new Person('Alice');
console.log(alice.name); // 輸出: Alice
箭頭函數中的 this
箭頭函數沒有自己的 this
,它會捕獲其所在上下文的 this
值。這意味著箭頭函數中的 this
在定義時就已經確定,不會因為調用方式而改變。
🌰舉個栗子
const obj = {name: 'Alice',greet: function() {setTimeout(() => {console.log(this.name);}, 100);}
};obj.greet(); // 輸出: Alice
類中的 this
類本質上是基于構造函數和原型繼承的語法糖 , 在類的方法中,this
指向類的實例。
🌰舉個栗子
class Person {constructor(name) {this.name = name;}greet() {console.log(this.name);}
}const alice = new Person('Alice');
alice.greet(); // 輸出: Alice
使用 call
、apply
和 bind
call、apply、bind 方法:這三種方法可以明確指定 this
的值 , 即顯式設置this
call、apply、bind 原理
call
作用:
call
方法用于調用一個函數,并設置函數執行時的 this
值,同時可以傳遞參數列表。
語法:
func.call(thisArg, arg1, arg2, ...)
thisArg
:函數執行時的this
值。arg1, arg2, ...
:傳遞給函數的參數列表。
原理:
- 將函數的
this
綁定到thisArg
。 - 立即執行函數,并傳入參數。
🌰舉個例子:
const person1 = {name: '張三',introduce: function (city, country) {console.log(`${this.name} is from ${city}, ${country}`);},
};
const person2 = {name: '李四',
};
person1.introduce.call(person2, '上海', '中國')
打印:李四 is from 上海, 中國
apply
作用:
apply
方法與 call
類似,用于調用一個函數并設置 this
值,但它的參數是以數組(或類數組對象)的形式傳遞的。
語法:
func.apply(thisArg, [argsArray])
thisArg
:函數執行時的this
值。argsArray
:傳遞給函數的參數數組。
原理:
apply
的原理與 call
幾乎相同,唯一的區別是參數傳遞方式。
🌰仍然是上面那個例子:
參數傳遞 必須是參數數組
person1.introduce.apply(person2, ['上海', '中國']);
bind
作用:
bind
方法用于創建一個新函數,并將原函數的 this
值綁定到指定的 thisArg
。與 call
和 apply
不同,bind
不會立即執行函數,而是返回一個綁定了 this
的新函數。
語法:
func.bind(thisArg, arg1, arg2, ...): newFunc
thisArg
:新函數執行時的this
值。arg1, arg2, ...
:預先傳遞給新函數的參數(可選)。return newFunc
: 返回一個綁定了this
的新函數。
原理:
bind
的原理可以理解為:
- 返回一個新函數。
bind
不會改變原函數,而是返回一個新的函數。
- 新函數執行時,
this
被綁定到thisArg
。- 新函數的
this
值會被固定為傳入的 第一個參數。例如f.bind(obj)
,實際上可以理解為obj.f()
,這時,f 函數體內的this
自然指向的是obj
- 新函數的
- 可以預先傳遞部分參數(柯里化)。
- 可以在調用新函數時,傳入額外的參數,這些參數會在調用時被傳遞給原函數。
預設參數
案例1:
function multiply(a, b) {return a * b;
}
const double = multiply.bind(null, 2); // 固定第一個參數為2
console.log(double(5)); // 輸出: 10
在這個例子中:
null
是固定的this
值,因為我們不需要設置this
。2
是預設的第一個參數(對應原函數的a
),而在調用double(5)
時,5
會作為第二個參數(對應原函數的b
)傳遞。double
函數實際上是multiply
函數的一個“封裝”,它將第一個參數固定為2
,而this
的值被設置為null
,這在這里并不重要,因為multiply
函數并沒有使用this
。
案例 2
function f(y, z){return this.x + y + z;
}
let m = f.bind({x : 1}, 2);
console.log(m(3));
//6
在這個例子中:
- 參數
{x : 1}
:- 這里
bind
方法會把它的第一個實參綁定給f
函數體內的this
,所以這里的this
即指向{x : 1}對象
,
- 這里
- **參數 **
2
:- 從第二個參數起,會依次傳遞給原始函數,這里的第二個參數
2
,即是 f 函數的y
參數,
- 從第二個參數起,會依次傳遞給原始函數,這里的第二個參數
- **參數 **
3
:- 最后調用 m(3)的時候,這里的 3 便是最后一個參數 z 了,所以執行結果為 1 + 2 + 3 = 6
分步處理參數的過程其實是一個典型的 函數柯里化 的過程(Curry)
案例
分析代碼:
let a = {b: function () {let func = function () {console.log(this.c);//undefined}func(); //普通函數調用},c : 'Hello!'}
a.b(); // undefined
undefined
的 原因:
😃
func
函數是在a.b()
方法內定義的,調用時是作為一個普通函數調用,而不是作為對象的方法調用。這導致this
的指向變成了全局對象(在瀏覽器中是window
),而不是對象a
。
🤔在學習完所有this指向的場景后,你能想出不同的方法去使得this.c
能正常訪問嗎?
解決方法
賦值
可以通過賦值的方式將 this 賦值給 that
let a = {b: function () {let self = this //將 this 賦值給 thatlet func = function () {console.log(self.c);//修改成self}func();},c: 'Hello!'}
a.b(); //輸出: Hello!
使用箭頭函數
let a = {b: function () {let func = () => {console.log(this.c);}func();},c: 'Hello!'
}
a.b(); // 輸出: Hello!
使用call / apply
let a = {b: function () {let func = function () {console.log(this.c);}func.call(this); //call},c: 'Hello!'
}
a.b(); // 輸出: Hello!
使用 bind方法
//寫法一
let a = {b: function() {let func = function() {console.log(this.c);}.bind(this); //返回新函數 覆蓋 func();},c: 'Hello!'
}
a.b(); // 輸出: Hello!//寫法二
let a = {b : function(){let func = function(){console.log(this.c);}func.bind(this)(); //立即執行},c : 'Hello!'
}a.b(); // 輸出: Hello!