高階前端進階,為什麼要使用call、apply、bind?
技術標籤:高階前端進階
前言:
call
、apply
、bind
這3個方法的用處都是更改this
指向,在學習call
、apply
、bind
之前,需要先了解this
,所以本文會先對this
進行講解。
通過本文可以瞭解:
-
this
是什麼 -
call
、apply
、bind
是如何實現的 -
call
、apply
、bind
的用處
什麼是this
定義:
當前執行上下文(global
、function
或 eval
)的一個屬性,在非嚴格模式下,總是指向一個物件,在嚴格模式下可以是任意值。
說白了,this
就是一個變數,他指向一個物件或者一個引數,我們通常需要使用this來設定或獲取變數、物件、方法,最重要的就是能夠分辨出當前使用的this的指向。接下來我們來看看不同情況下this
重點:this
的指向是呼叫時決定的。
1. 全域性上下文
- 在全域性上下文中,
this
指向的是window
console.log(this==window)
//true
2.函式呼叫
- 直接呼叫
this
指向window
function test(){
console.log(this)
}
test();
//window
function test(){
test1()
function test1(){
console.log(this)
}
}
test();
//window
3.通過物件呼叫
this
指向呼叫方法的物件
const obj = { a:1, test:function(){ console.log(this) } } console.log(obj.test()) //obj
4.箭頭函式
this
與所在上下文this
指向相同
const fun1 = ()=>{
console.log(this)
}
console.log(fun1())
//window
function fn1(){
console.log(this)
return ()=>{
console.log(this)
}
}
console.log(fn1()())
//window
//window
5.建構函式
this
指向構造出的物件
function Test(){ this.name = 'test'; } let t = new Test(); console.log(t.name) //test
call
、apply
、bind
原生實現解析
1.call
function.call(thisArg, arg1, arg2, …)。
第一個引數為要更改的物件,arg1,arg2…為傳入引數
原生實現
//call函式原生模擬實現
//給context一個預設值,如果沒有傳入,預設指向window,用...來將傳入引數轉為陣列
Function.prototype.call = function(context = window,...args){
//將呼叫call的函式掛在傳入的物件上,起到更改this的目的
context.fun = this;
//執行方法,並傳入引數
const result = context.fun(...args);
//執行完畢刪除剛建立的自定義方法,防止汙染
delete context.fun;
//返回結果
return result
}
let obj = {
a:1
}
function test(){
console.log(this.a)
}
test.call(obj);
//輸出
//1
2.apply
func.apply(thisArg, [argsArray])。 第一個引數為要更改的物件,第二個引數為func執行時傳入的陣列引數
//apply函式原生模擬實現
//給context一個預設值,如果沒有傳入,預設指向window,用...來將傳入引數轉為陣列
Function.prototype.apply = function(content=window,args){
//將呼叫call的函式掛在傳入的物件上,起到更改this的目的
content.fn = this;
//執行方法,並傳入引數
let result = content.fn(...args);
//執行完畢刪除剛建立的自定義方法,防止汙染
delete content.fn;
//返回結果
return result;
}
let obj = {
a:1
}
function test(){
console.log(this.a)
}
test.apply(obj);
call/apply方法基本一樣,唯一的區別在於call傳入的是多個引數,apply傳入的為陣列
3.bind
function.bind(thisArg[, arg1[, arg2[, …]]])。第一個引數為要更改的物件,arg1, arg2, …為傳入的引數
- 與
call
、apply
的不同在於,call
、apply
呼叫後會立即執行,返回值依賴於呼叫他們的函式。而bind
,呼叫後會返回原函式,擁有指定的this
以及初始引數。
//bind函式原生模擬實現
//給context一個預設值,如果沒有傳入,預設指向window,用...來將傳入引數轉為陣列
Function.prototype.bind = function(oThis) {
//校驗呼叫者是否為方法
if (typeof this !== "function") {
throw new TypeError("error");
}
//擷取入參內的引數,並轉為陣列
const aArgs = Array.prototype.slice.call(arguments, 1);
//臨時儲存呼叫bind的物件
const fToBind = this;
//中間方法
const fNOP = function() {};
//返回的新函式
const fBound = function() {
//這裡判斷this指向
//如果this指向中間方法,則說明返回的函式fBound通過new呼叫了,則這裡的this不更改指向,否則指向bind時傳入的物件
//執行方法並將bind時傳入的引數與新函式呼叫時傳入的引數合併,傳入執行方法。
//例:如果通過let t = new test1()
//此時 這裡的this指向t,通過t的原型鏈可以找到fNOP的原型物件,這時候不能更改this為其他值
return fToBind.apply(this instanceof fNOP && oThis ?
this :
oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
//中間方法的原型指向呼叫bind的原型物件
fNOP.prototype = this.prototype;
//將返回函式的原型物件的隱式原型指向中間方法的原型物件
fBound.prototype = new fNOP();
return fBound;
};
let obj = {
a: 1
}
function test() {
console.log(this.a)
}
let test1 = test.bind(obj);
test1()
//輸出
//1
到這裡,call
、apply
、bind
的實現方式已經介紹完畢。
為什麼要更改this指向
接下來用幾個示例說明為什麼要使用call
、apply
、bind
更改this
指向
例1:
var obj = {
fn1: function() {
},
fn2: function() {
},
fn3: function(cb) {
//這裡呼叫的this.fn3傳入的function,由於是直接呼叫,所以this指向的是window
cb();
},
list: function() {
this.fn3(function() {
console.log(this)
this.fn1();
this.fn2();
});
}
};
obj.list();
執行這段程式碼,控制檯會報個錯誤,this.fn1 is not a function
,我們檢視this.fun3
中的this
,可以知道,這裡的this
指向的是window
,去window
中查詢fn1
方法,的確是查不到。
這時候我們就需要更改方法中的this
指向了。
1、使用call
var obj = {
fn1: function() {
console.log('fn1')
},
fn2: function() {
console.log('fn2')
},
fn3: function(cb) {
cb.call(this);
console.log('fn3')
},
list: function() {
this.fn3(function() {
this.fn1();
this.fn2();
});
}
};
obj.list();
//輸出
//fn1
//fn2
//fn3
2、使用bind
var obj = {
fn1: function() {
console.log('fn1')
},
fn2: function() {
console.log('fn2')
},
fn3: function(cb) {
cb();
console.log('fn3')
},
list: function() {
this.fn3(function() {
this.fn1();
this.fn2();
}.bind(this));
}
};
obj.list();
//輸出
//fn1
//fn2
//fn3
例2:
function test(){
console.log(typeof arguments)
//通過輸出可以看到,arguments是類陣列物件
//如果我們想要把傳入的引數轉為陣列,就需要借用call,將slice方法掛到arguments上,從而實現我們要的功能
let args = Array.prototype.slice.call(arguments,0)
console.log(args);
}
test(1,2,3);
//輸出
//object
//[1,2,3]
通過call
方法可以將不存在當前物件的方法掛在到自己物件上,從而實現我們需要的功能。
例3:
const arr = [1,2,3,4];
//通過apply方法,將min方法掛到window上,然後將arr轉為字串組傳入min方法
const min = Math.min.apply(null,arr);
console.log(min)
//輸出
//1
通過這3個示例,可以看到call
、apply
、bind
的使用場景。
結尾:
call
、apply
都是立即執行,call
傳入的是多個引數,apply
傳入的是陣列。
bind
是返回一個新函式,擁有this
以及初始引數。
再此,對call
、apply
、bind
的介紹就結束了,希望本篇文章對大家有所啟示,謝謝觀看~
如果想獲取更多內容,可以掃描下方二維碼,一起學習,一起進步。