今天看博客時,看到了這樣的一段js代碼:
var bind = Function.prototype.call.bind(Function.prototype.bind);
我想突然看到這樣的一段代碼,即使js能力再強的人,可能也需要花點時間去理解。像我這樣的菜鳥就更不用說了。其實,原文已經對這端代碼做出了解釋,但我還是想用我的想法去解釋這段代碼。
上面那段代碼涉及到了call、bind,所以我想先區別一下call、apply、bind的用法。這三個方法的用法非常相似,將函數綁定到上下文中,即用來改變函數中this的指向。舉個例子:
var zlw = { name: "zlw", sayHello: function (age) { console.log("hello, i am ", this.name + " " + age " years old"); } }; var xlj = { name: "xlj", }; zlw.sayHello(24);// hello, i am zlw 24 years old
下面看看call、apply方法的用法:
zlw.sayHello.call(xlj, 24);// hello, i am xlj 24 years old zlw.sayHello.apply(xlj, [24]);// hello, i am xlj 24 years old
結果都相同。從寫法上我們就能看出二者之間的異同。相同之處在於,第一個參數都是要綁定的上下文,后面的參數是要傳遞給調用該方法的函數的。不同之處在於,call方法傳遞給調用函數的參數是逐個列出的,而apply則是要寫在數組中。
我們再來看看bind方法的用法:
zlw.sayHello.bind(xlj, 24)(); //hello, i am xlj 24 years old zlw.sayHello.bind(xlj, [24])(); //hello, i am xlj 24 years old
bind方法傳遞給調用函數的參數可以逐個列出,也可以寫在數組中。bind方法與call、apply最大的不同就是前者返回一個綁定上下文的函數,而后兩者是直接執行了函數。由於這個原因,上面的代碼也可以這樣寫:
zlw.sayHello.bind(xlj)(24); //hello, i am xlj 24 years old zlw.sayHello.bind(xlj)([24]); //hello, i am xlj 24 years old
bind方法還可以這樣寫 fn.bind(obj, arg1)(arg2) 。
用一句話總結bind的用法:該方法創建一個新函數,稱為綁定函數,綁定函數會以創建它時傳入bind方法的第一個參數作為this,傳入bind方法的第二個以及以后的參數加上綁定函數運行時本身的參數按照順序作為原函數的參數來調用原函數。
現在回到開始的那段代碼:
var bind = Function.prototype.call.bind(Function.prototype.bind);
我們可以這樣理解這段代碼:
var bind = fn.bind(obj)
fn 相當於 Function.prototype.call , obj 相當於 Function.prototype.bind 。而 fn.bind(obj) 一般可以寫成這樣 obj.fn ,為什么呢?因為 fn 綁定了 obj , fn 中的 this 就指向了 obj 。我們知道,函數中 this 的指向一般是指向調用該函數的對象。所以那段代碼可以寫成這樣:
var bind = Function.prototype.bind.call;
大家想一想 Function.prototype.call.bind(Function.prototype.bind) 返回的是什么?
console.log(Function.prototype.call.bind(Function.prototype.bind)) // call()
返回的是 call 函數,但這個 call 函數中的上下文的指向是 Function.prototype.bind 。這個 call 函數可以這樣用
var bind = Function.prototype.call.bind(Function.prototype.bind); var zlw = { name: "zlw" }; function hello () { console.log("hello, I am ", this.name); } bind(hello, zlw)() // hello, I am zlw
大家可能會感到疑惑,為什么是這樣寫 bind(hello, zlw) 而不是這樣寫 bind(zlw, hello) ?既然 Function.prototype.call.bind(Function.prototype.bind) 相當於 Function.prototype.bind.call ,那么先來看下 Function.prototype.bind.call 怎么用。 call 的用法大家都知道:
Function.prototype.bind.call(obj, arg)
其實就相當於 obj.bind(arg) 。我們需要的是 hello 函數綁定對象 zlw ,即 hello.bind(zlw) 也就是 Function.prototype.bind.call(hello, zlw) ,所以應該這樣寫 bind(hello, zlw) 。
現在又有一個疑問,既然 Function.prototype.call.bind(Function.prototype.bind) 相當於 Function.prototype.bind.call ,我們為什么要這么寫:
var bind = Function.prototype.call.bind(Function.prototype.bind);
而不直接這樣寫呢:
var bind = Function.prototype.bind.call;
先來看一個例子:
var name = "xlj"; var zlw = { name: "zlw" hello: function () { console.log(this.name); } };
zlw.hello(); // zlw
var hello = zlw.hello; hello(); // xlj
有些人可能會意外, hello() 的結果應該是 zlw 才對啊。其實,將 zlw.hello 賦值給變量 hello ,再調用 hello() , hello 函數中的 this 已經指向了 window ,與 zlw.hello 不再是同一個上下文,而全局變量 name 是 window 的一個屬性,所以結果就是 xlj 。再看下面的代碼:
var hello = zlw.hello.bind(zlw); hello(); // zlw
結果是 zlw ,這時 hello 函數與 zlw.hello 是同一個上下文。其實上面的疑惑已經解開了,直接這樣寫:
var bind = Function.prototype.bind.call;
bind 函數中的上下文已經與 Function.prototype.bind.call 中的不一樣了,所以使用 bind 函數會出錯。而這樣寫
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind 函數中的上下文與 Function.prototype.call.bind(Function.prototype.bind) 中是一樣的。
關於這個這段代碼的解釋這到這邊了,感覺語言組織能力不是很好,文章寫得有些啰嗦了。文中可能會有錯誤,希望大家指正。