在給我們專案組的其他程式介紹 js 的時候,我準備了很多的內容,但看起來效果不大,果然光講還是不行的,必須動手。前幾天有人問我關於程式碼裡call() 函數的用法,我請他去看書,這裡推薦用js 寫伺服器的程式猿看《javascript程式精簡》 這本書,crockford大神果然不是蓋的。之後我在segmentfault上又看到了類似的問題,那邊解答之後乾脆這裡記一筆。
首先,關於 js 定義類別或物件的方法,請參考w3school 的這裡的這裡,寫的非常詳細和清晰,我不再贅言了。
為了介紹 bind、call、apply 這三個函數的用法,我不得不介紹 js 裡函數的一些設定。關於這部分推薦通讀 《javascript程式設計精粹》 的第四章,這裡我所說的在書裡都能找到。
關於這三個函數的詳細介紹,可以參考 MDN 的文檔:bind、call、apply。
下面開始搬磚,修改自我之前在segmentfault 上的答案:
js 裡函數呼叫有4 種模式:方法呼叫、正常函數調用、建構器函數呼叫、apply/call 呼叫。
同時,無論哪種函數呼叫除了你宣告時定義的形參外,還會自動加入 2 個形參,分別是 this 和arguments。
arguments 不涉及上述 3 個函數,所以這裡只談 this。 this 的值,在上面 4 中呼叫模式下,分別會綁定不同的值。分別來說一說:
方法呼叫:
這個很好理解,函數是一個物件的屬性,例如
var a = { v : 0, f : function(xx) { this.v = xx; } } a.f(5);
這個時候,上面函數裡的this 就綁定的是這個物件a。所以 this.v 可以取到物件 a 的屬性 v。
正常函數呼叫:仍然看程式碼
function f(xx) { this.x = xx; } f(5);
這個時候,函數f 裡的this 綁定的是全域對象,如果是在瀏覽器運行的解釋器中,一般來說是window 物件。所以這裡 this.x 訪問的其實是 window.x ,當然,如果 window 沒有 x 屬性,那麼你這麼一寫,按照 js 的坑爹語法,就是給 window 物件添加了一個 x 屬性,同時賦值。
建構子函式呼叫:
建構子一直是我認為是js 裡最坑爹的部分,因為它和js 最初設計的基於原型的物件導向實作方式格格不入,就好像是特意為了迎合大家已經被其他基於類別的面相物件實現給慣壞了的習慣。
如果你在一個函數前面帶上new 關鍵字來調用,那麼js 會創建一個prototype 屬性是此函數的一個新對象,同時在調用這個函數的時候,把this 綁定到這個新物件上。當然 new 關鍵字也會改變return 語句的行為,不過這裡就不談了。看程式碼
function a(xx) { this.m = xx; } var b = new a(5);
上面這個函數和正常呼叫的函數寫法上沒什麼差別,只不過在呼叫的時候函數名稱前面加了關鍵字new罷了,這麼一來,this 綁定的就不再是前面講到的全域對象了,而是這裡說的創建的新對象,所以說這種方式其實很危險,因為光看函數,你不會知道這個函數到底是準備拿來當構造函數用的,還是一般函數用的。所以我們可以看到,在jslint 裡,它會要求你寫的所有建構函數,也就是一旦它發現你用了new 關鍵字,那麼後面那個函數的首字母就必須大寫,這樣透過函數首字母大寫的方式來區分,我個人只有一個看法:坑爹:)
apply/call 呼叫:
我们知道,在 js 里,函数其实也是一个对象,那么函数自然也可以拥有它自己的方法,有点绕,在js 里,每个函数都有一个公共的 prototype —— Function,而这个原型自带有好几个属性和方法,其中就有这里困惑的 bind、call、apply 方法。先说 apply 方法,它让我们构造一个参数数组传递给函数,同时可以自己来设置 this 的值,这就是它最强大的地方,上面的 3 种函数调用方式,你可以看到,this 都是自动绑定的,没办法由你来设,当你想设的时候,就可以用 apply()了。apply 函数接收 2 个参数,第一个是传递给这个函数用来绑定 this 的值,第二个是一个参数数组。
看代码
function a(xx) { this.b = xx; } var o = {}; a.apply(o, [5]); alert(a.b); // undefined alert(o.b); // 5
是不是很神奇,函数 a 居然可以给 o 加属性值。当然,如果你 apply 的第一个参数传递 null,那么在函数 a 里面 this 指针依然会绑定全局对象。
call() 方法和 apply() 方法很类似,它们的存在都是为了改变 this 的绑定,那 call() 和apply() 有什么区别呢?就我个人看来,没啥鸟区别。。。开玩笑!刚刚说了,上面 apply() 接收两个参数,第一个是绑定 this 的值,第二个是一个参数数组,注意它是一个数组,你想传递给这个函数的所有参数都放在数组里,然后 apply() 函数会在调用函数时自动帮你把数组展开。而 call()呢,它的第一个参数也是绑定给 this 的值,但是后面接受的是不定参数,而不再是一个数组,也就是说你可以像平时给函数传参那样把这些参数一个一个传递。
所以如果一定要说有什么区别的话,看起来是这样的
function a(xx, yy) { alert(xx, yy); alert(this); alert(arguments); } a.apply(null, [5, 55]); a.call(null, 5, 55);
仅此而已。
最后再来说 bind() 函数,上面讲的无论是 call() 也好, apply() 也好,都是立马就调用了对应的函数,而 bind() 不会, bind() 会生成一个新的函数,bind() 函数的参数跟 call() 一致,第一个参数也是绑定 this 的值,后面接受传递给函数的不定参数。 bind() 生成的新函数返回后,你想什么时候调就什么时候调,看下代码就明白了
var m = { "x" : 1 }; function foo(y) { alert(this.x + y); } foo.apply(m, [5]); foo.call(m, 5); var foo1 = foo.bind(m, 5); foo1();
末了来个吐槽,你在 js 里想定义一个函数,于是你会这么写:
function jam() {};
其实这是 js 里的一种语法糖,它等价于:
var jam = function() {};
然后你想执行这个函数,脑洞大开的你会这么写:
function jam() {}();
但是这么写就报错了,其实这种写法也不算错,因为它确实是 js 支持的函数表达式,但是同时 js 又规定以function 开头的语句被认为是函数语句,而函数语句后面是肯定不会带 () 的,所以才报错,于是聪明的人想出来,加上一对括号就可以了。于是就变成了这样:
1(function jam() {}());
这样就定义了一个函数同时也执行它,详情参加 ECMAScript 的 Expression Statement 章节。
以上是詳細介紹JavaScript中bind、call、apply函數用法的詳細內容。更多資訊請關注PHP中文網其他相關文章!