為什么要改變this指向?
我們知道bind,call,Apply的作用都是用來改變this指向的,那為什么要改變this指向呢?請看下面的例子:
var name="lucy";
let obj={
name:"martin",
say:function () {
console.log(this.name);
}
};
obj.say(); //martin,this指向obj對象
setTimeout(obj.say,0); //lucy,this指向window對象
可以觀察到,正常情況下 say 方法中的 this 是指向調用它的 obj 對象的,而定時器 setTimeout 中的 say 方法中的 this 是指向window對象的(在瀏覽器中),這是因為 say 方法在定時器中是作為回調函數來執行的,因此回到主棧執行時是在全局執行上下文的環境中執行的,但我們需要的是 say 方法中 this 指向obj對象,因此我們需要修改 this 的指向。
作用
call、apply、bind作用是改變函數執行時的上下文,簡而言之就是改變函數運行時的this指向
那么什么情況下需要改變this的指向呢?下面舉個例子
var name = "lucy";
var obj = {
name: "martin",
say: function () {
console.log(this.name);
}
};
obj.say(); // martin,this 指向 obj 對象
setTimeout(obj.say,0); // lucy,this 指向 window 對象
從上面可以看到,正常情況say方法輸出martin
但是我們把say放在setTimeout方法中,在定時器中是作為回調函數來執行的,因此回到主棧執行時是在全局執行上下文的環境中執行的,這時候this指向window,所以輸出lucy
我們實際需要的是this指向obj對象,這時候就需要該改變this指向了
setTimeout(obj.say.bind(obj),0); //martin,this指向obj對象
區別
下面再來看看apply、call、bind的使用
apply
apply接受兩個參數,第一個參數是this的指向,第二個參數是函數接受的參數,以數組的形式傳入,且當第一個參數為null、undefined的時候,默認指向window(在瀏覽器中),使用apply方法改變this指向后原函數會立即執行,且此方法只是臨時改變thi指向一次。
先上一個例子,比較好理解
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.apply(foo, ['kevin', 18]);
// kevin
// 18
// 1
值得注意的事情是,當使用apply()方法時,控制臺會打印出我們所希望打印的內容,也就是bar函數執行了,在改變其this指向后,執行改變后的函數,關于這一點,call()方法的執行也是一樣的。
顯然我們傳入的數組,會被正確解析為bar中所需要的參數
日常用法:改變this指向
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"張三"
}
fn.apply(obj,[1,2]); // this會變成傳入的obj,傳入的參數必須是一個數組;
fn(1,2) // this指向window
當第一個參數為null、undefined的時候,默認指向window(在瀏覽器中)
fn.apply(null,[1,2]); // this指向window
fn.apply(undefined,[1,2]); // this指向window
call
call方法的第一個參數也是this的指向,后面傳入的是一個參數列表
跟apply一樣,改變this指向后原函數會立即執行,且此方法只是臨時改變this指向一次
調用call的方式如下
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.call(foo, 'kevin', 18);
// kevin
// 18
// 1
有了call為什么還要apply呢,經過一番查閱后,了解到使用call的速度比apply快,那疑惑就更嚴重了,apply還有存在的意義嗎?我們不妨假設當需要傳入的參數很多時,一般都是存放在數組中,如果只有call的話,就需要我們對數組做一些處理,轉換成參數列表的形式,但是使用apply的話就減輕了工作量,可以這樣認為吧,apply時call的語法糖。(為什么參數多的時候會存放在數組里面捏?本孩認為,數組可以被認為一個整體,不易出現丟參的情況,而且數組有長度,可以輕而易舉的知道到底有多少個參數)
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"張三"
}
fn.call(obj,1,2); // this會變成傳入的obj,傳入的參數必須是一個數組;
fn(1,2) // this指向window
同樣的,當第一個參數為null、undefined的時候,默認指向window(在瀏覽器中)
fn.call(null,[1,2]); // this指向window
fn.call(undefined,[1,2]); // this指向window
bind
bind方法和call很相似,第一參數也是this的指向,后面傳入的也是一個參數列表(但是這個參數列表可以分多次傳入)
改變this指向后不會立即執行,而是返回一個永久改變this指向的函數
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.bind(foo, 'kevin', 18);
// 沒有任何輸出
為什么會沒有輸出呢?上文已經提到了,這就是和bind、call的區別了
bind不會執行bar函數,只是改變了其this指向,并且給它傳入了參數,添加一行代碼呢?如下:
var foo = {
value: 1
};
function bar(name, age) {
console.log(name)
console.log(age)
console.log(this.value);
}
bar.bind(foo, 'kevin', 18);
bar();
// undefined
// undefined
// undefined
對的,你沒看錯,輸出了三個undefined,因為雖然調用了bar(),但this指向了window且沒有傳入任何參數,理所當然都是undefined了,可見使用了bind后并不會改變原函數,其實call和apply也都不會改變原參數,也就是說call和apply是一次性的,這里為什么我沒說bind呢,因為他會返回一個函數,如果我們保存這個函數,它就不是一次性的了,如果再執行這個函數就可以達到我們想要的效果了,注意:執行apply和call時,就如同執行原函數,返回值和原函數相同,如果原函數不返回值,則apply和call也不會返回任何值!
function fn(...args){
console.log(this,args);
}
let obj = {
myname:"張三"
}
const bindFn = fn.bind(obj); // this 也會變成傳入的obj ,bind不是立即執行需要執行一次
bindFn(1,2) // this指向obj
fn(1,2) // this指向window
小結
從上面可以看到,apply、call、bind三者的區別在于:
- 三者都可以改變函數的this對象指向
- 三者第一個參數都是this要指向的對象,如果如果沒有這個參數或參數為undefined或null,則默認指向全局window
- 三者都可以傳參,但是apply是數組,而call是參數列表,且apply和call是一次性傳入參數,而bind可以分為多次傳入
- bind是返回綁定this之后的函數,apply、call 則是立即執行
實現
實現bind的步驟,我們可以分解成為三部分:
- 修改this指向
- 動態傳遞參數
// 方式一:只在bind中傳遞函數參數
fn.bind(obj,1,2)()
// 方式二:在bind中傳遞函數參數,也在返回函數中傳遞參數
fn.bind(obj,1)(2)
- 兼容new關鍵字
整體實現代碼如下:
Function.prototype.myBind = function (context) {
// 判斷調用對象是否為函數
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 獲取參數
const args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根據調用方式,傳入不同綁定值
return fn.apply(this instanceof Fn ? new fn(...arguments) : context, args.concat(...arguments));
}
}
給大家分享我收集整理的各種學習資料,前端小白交學習流程,入門教程等回答-下面是學習資料參考。