JavaScript中的this指向,call、apply、bind的簡單實現(xiàn)
JavaScript中的this
this是JavaScript中一個特殊關(guān)鍵字,用于指代當(dāng)前執(zhí)行上下文中的對象。它的難以理解之處就是值不是固定的,是再函數(shù)被調(diào)用時根據(jù)調(diào)用場景動態(tài)確定的,主要根據(jù)函數(shù)的調(diào)用方式來決定this指向的對象。this 的值在函數(shù)被調(diào)用時動態(tài)確定,以下是幾種常見的情況:
- 全局上下文中:當(dāng)在全局作用域中調(diào)用函數(shù)時,this 指向全局對象。在瀏覽器環(huán)境中,這個全局對象是 window 對象。
console.log(this); // 在瀏覽器中,輸出為 Window 對象
- 函數(shù)作為對象的方法:當(dāng)函數(shù)作為對象的方法被調(diào)用時,this 指向調(diào)用該方法的對象。
const obj = {
property: 'value',
printProperty: function() {
console.log(this.property);
}
};
obj.printProperty(); // 輸出 'value'
- 構(gòu)造函數(shù)中:在使用 new 關(guān)鍵字創(chuàng)建實例時,構(gòu)造函數(shù)內(nèi)部的 this 指向即將創(chuàng)建的新實例。
function Person(name) {
this.name = name;
}
const person = new Person('Alice');
console.log(person.name); // 輸出 'Alice'
- 顯式綁定:使用call、apply、bind方法可以顯式指定this的綁定對象。
function greet(message) {
console.log(`${message}, ${this.name}!`);
}
const person = { name: 'Bob' };
greet.call(person, 'Hello'); // 輸出 'Hello, Bob!'
- 箭頭函數(shù):箭頭函數(shù)不綁定this,它會捕獲外層作用域的this值作為自己的this。
const obj = {
method: function() {
const arrowFunc = () => {
console.log(this === obj);
};
arrowFunc();
}
};
obj.method(); // 輸出 true
- class中的this:類中的this默認(rèn)指向類的實例對象。
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
}
const rect = new Rectangle(10, 20);
console.log(rect.width); // 輸出 10
- 事件綁定事件綁定中的this是指向觸發(fā)事件的dom元素。
const button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button);
});
// 在按鈕點擊時輸出 true
如何改變this指向
改變 this 指向是在 JavaScript 中常見的需求,特別是當(dāng)你想要在不同的上下文中調(diào)用函數(shù)時。以下是幾種常見的方法來改變 this 指向:
- 使用 call 方法:call 方法允許你顯式地指定函數(shù)內(nèi)部的 this 值,并且傳遞參數(shù)列表。第一個參數(shù)是要綁定的 this 值,后面的參數(shù)是函數(shù)的參數(shù)。
function greet(message) {
console.log(`${message}, ${this.name}!`);
}
const person = { name: 'Alice' };
greet.call(person, 'Hello'); // 輸出 'Hello, Alice!' 這里把this綁定給person對象
- 使用 apply 方法:apply 方法與 call 類似,但它接受一個數(shù)組或類數(shù)組對象作為參數(shù),其中的元素將作為函數(shù)參數(shù)傳遞。
function greet(message) {
console.log(`${message}, ${this.name}!`);
}
const person = { name: 'Bob' };
greet.apply(person, ['Hi']); // 輸出 'Hi, Bob!'
- 使用 bind 方法:bind 方法創(chuàng)建一個新函數(shù),將 this 值永久地綁定,并可以預(yù)先設(shè)置部分參數(shù)。原函數(shù)不會受到影響。
function greet(message) {
console.log(`${message}, ${this.name}!`);
}
const person = { name: 'Charlie' };
const greetPerson = greet.bind(person);
greetPerson('Hey'); // 輸出 'Hey, Charlie!'
- 使用箭頭函數(shù):箭頭函數(shù)不會綁定獨立的 this 值,而是捕獲其外部函數(shù)的 this 值。
const obj = {
method: function() {
const arrowFunc = () => {
console.log(this === obj);
};
arrowFunc();
}
};
obj.method(); // 輸出 true
call和apply區(qū)別
- bind 方法:
- bind 方法創(chuàng)建一個新函數(shù),將原函數(shù)的 this 值永久綁定到指定的對象,并可以在調(diào)用時傳遞參數(shù)。
- 它不會立即執(zhí)行原函數(shù),而是返回一個新的函數(shù),需要手動調(diào)用新函數(shù)以執(zhí)行原函數(shù)。
- bind 方法不會改變原函數(shù)的上下文,而是返回一個新函數(shù)。
- call 方法:
call 方法立即調(diào)用函數(shù),并指定函數(shù)內(nèi)部的 this 值,同時可以傳遞參數(shù)列表。
它的第一個參數(shù)是要綁定的 this 值,后續(xù)的參數(shù)會作為函數(shù)的參數(shù)傳遞。
apply 方法:
apply 方法也立即調(diào)用函數(shù),并指定函數(shù)內(nèi)部的 this 值,但參數(shù)傳遞方式不同。
它的第一個參數(shù)是要綁定的 this 值,第二個參數(shù)是一個數(shù)組(或類數(shù)組對象),其中的元素會作為函數(shù)的參數(shù)傳遞。
實現(xiàn)call、apply、bind
這里實現(xiàn)簡化版的,核心思路是:
- 將函數(shù)設(shè)為傳入對象的一個屬性
- 執(zhí)行該函數(shù)
- 刪除該函數(shù)(臨時函數(shù)調(diào)用完成刪除,防止內(nèi)存泄漏,以免context 對象造成污染)
- 返回結(jié)果或傳入的this
call
js
Function.prototype.myCall = function(context, ...args) {
context = context || window;
const fn = Symbol();
context[fn] = this;
const result = context[fn](...args);
delete context[fn];
return result;
}
apply
js
Function.prototype.myApply = function(context, args) {
context = context || window;
const fn = Symbol();
context[fn] = this;
let result;
if(args) {
result = context[fn](...args);
} else {
result = context[fn]();
}
delete context[fn];
return result;
}
bind
js
Function.prototype.myBind = function(context, ...outerArgs) {
context = context || window;
const _this = this;
return function(...innerArgs) {
context[fn] = _this;
const result = context[fn](...outerArgs, ...innerArgs);
delete context[fn];
return result;
}
}