JS中 call、apply 和 bind
在JS中,這三者都是用來改變函式的this物件的指向的(關於this指向,請參考我的文章JS中 this 到底指向誰?)
關於這三者的區別,如下內容:
call() 與 apply()
這裡為什麼把他們兩個放在一起說呢?
其實對於 call apply 兩者而言,作用完全一樣,只是接受引數的方式不太一樣
- call()接收的是引數列表
- apply()接收引數陣列
例如:有一個函式定義如下
let Test = function(arg1,arg2){ };
複製程式碼
可以通過如下方式來呼叫:
Test.call(this,arg1,arg2);
Test.apply(this,[arg1,arg2])
複製程式碼
在沒學this之前,通常會有如下的問題:
let m = {
call:"Hello...",
invoke:function(){
console.log(this.call);
}
}
let n = m.invoke;
n(); // undefined
複製程式碼
這裡我們想要列印m裡面的call,但是卻列印出來undefined,如果執行 m.invoke() 是可以的
let m = {
call:"Hello...",
invoke:function(){
console.log(this.call);
}
}
m.invoke() // Hello...
複製程式碼
這裡能夠列印出所需的內容,因為這裡的this指向的是函式m,那麼為什麼上面的不行這個問題就需要我們瞭解this的指向問題
雖然這種方法可以達到我們的目的,但是有時候我們不得不將這個物件儲存到另外的一個變數中,那麼就可以通過call apply與bind 實現
1.call()
let m = {
call:"Hello...",
invoke:function(){
console.log(this.call);
}
}
let n = m.invoke;
n.call(m)
複製程式碼
通過在call方法,給第一個引數新增要把n新增到哪個環境中,簡單來說,this就會指向那個物件
call方法除了第一個引數以外還可以新增多個引數,如下:
let m = {
call:"Hello...",
invoke:function(x,y){
console.log(this.call);
console.log(x+y);
}
}
let n = m.invoke;
n.call(m,1,2);
複製程式碼
call的原理
function f1(a, b, c) {
console.log("f1...")
return a + b + c
}
Function.prototype.call = function (context) {
// 處理context可能為空的情況
context = context ? Object(context) : window
context.fn = this;
let args = [];
for (let i = 1; i < arguments.length; i++) {
args.push(arguments[i])
}
let r = context.fn(...args)
delete context.fn
return r
}
let obj
console.log(f1.call(obj, 1, 2, 3))
複製程式碼
2.apply()
apply方法和call方法有些相似,它也可以改變this的指向,和上面第一個例子一樣,只需把call換成apply就可以了
同樣apply也可以有多個引數,但是不同的是,第二個引數必須是一個陣列,如下:
let m = {
call:"Hello...",
invoke:function(x,y){
console.log(this.call);
console.log(x+y);
}
}
let n = m.invoke;
// n.call(m,1,2);
n.apply(m,[1,2])
複製程式碼
注:如果call()和apply()的第一個引數是null,在非嚴格模式下,第一個引數為null或者undefined時會自動替換為指向全域性物件(window),
bind()
bind方法和call、apply方法有些不同,但是他也是可以改變this指向的,接下來就看看有哪些不同之處
let m = {
call:"Hello...",
invoke:function(){
console.log(this.call);
}
}
let n = m.invoke;
n.bind(m);
複製程式碼
執行結果發現沒有結果列印,這就是不同,實際上bind方法返回的是一個修改過後的函式
let m = {
call:"Hello...",
invoke:function(){
console.log(this.call);
}
}
let n = m.invoke;
let t = n.bind(m);
console.log(t); // 這裡列印的是一個函式
複製程式碼
如果執行函式t,那麼能不能列印出物件m裡的call呢?
let m = {
call:"Hello...",
invoke:function(){
console.log(this.call);
}
}
let n = m.invoke;
let t = n.bind(m);
t(); // Hello...
複製程式碼
這裡是可以列印出想要的結果的,同樣bind也可以有多個引數,並且引數可以執行的時候再次新增,但是要注意的是,引數是按照形參的順序進行的
let m = {
call:"Hello...",
invoke:function(x,y,z){
console.log(this.call);
console.log(x,y,z); //1,2,3
}
}
let n = m.invoke;
let t = n.bind(m,1);
t(2,3);
複製程式碼
bind原理
function f(name, age, n, a) {
console.log(name, age, n, a)
console.log(this)
}
let obj = { name: "wangcai" }
Function.prototype.bind = function (context) {
let that = this;
let newArr = Array.prototype.slice.call(arguments, 1);
return function () {
let newArr2 = Array.prototype.slice.call(arguments)
return that.apply(context, newArr.concat(newArr2))
}
}
let newF = f.bind(obj, "hello", "world", "lalala")
newF("xxxx")
複製程式碼
總結:
-
apply 、 call 、bind 三者都是用來改變函式的this物件的指向的
-
apply 、 call 、bind 三者第一個引數都是this要指向的物件,也就是想指定的上下文;
-
apply 、 call 、bind 三者都可以利用後續引數傳參;
-
bind是返回對應函式,便於後面呼叫;apply、call則是立即呼叫