談談javascript的Function中那些隱藏的屬性/方法:caller/callee/apply/call/bind

jopen 8年前發布 | 9K 次閱讀 JavaScript開發 JavaScript

javascript的Function中有不少不那么常用,又或者用了也是 知其然而不知其所以然 的屬性/方法,本文就來談談這一系列屬性/方法: caller / callee / apply / call / bind 。

caller 屬性

直接上DEMO比較好理解:

// caller demo {
function callerDemo() {
    if (callerDemo.caller) {
      var a = callerDemo.caller.toString();
      console.log(a);
    } else {
      console.log("this is a top function");
    }
}
function handleCaller() {
    callerDemo();
}

handleCaller();    //"function handleCaller() { callerDemo(); }"
callerDemo();    //"this is a top function"

我們先來按照思路一步一步來看這段代碼:

  1. 首先我們看到定義了倆function:handleCaller和callerDemo,并且還可以看出handleCaller函數里是調用了callerDemo函數的。

  2. 在callerDemo函數里,我們看到了本文介紹的主角之一: caller 屬性,并且可以看出這 caller 屬性是函數對象本身的一個成員屬性。

  3. 在callerDemo函數里,有一段判斷 caller 屬性是否存在的代碼,這段代碼有什么意義呢?這就要看最后的結果了:直接調用callerDemo()發現此時 callerDemo.caller 是為空的,而反觀通過調用handleCaller()并在其內部調用callerDemo()則 callerDemo.caller 不為空。這說明只有在函數里調用函數,才會生成 caller 屬性,而直接在全局環境里調用函數則不會生成。

  4. 繼續看 var a = callerDemo.caller.toString();console.log(a); ,這里打印出來的居然是handleCaller整個函數體,這說明,此時的 callerDemo.caller 實際上就是對于handleCaller這個函數對象的一個引用。

這么分析下來, caller 屬性就很容易明白了:

  • caller 屬性是幫助我們在當前函數里獲取調用當前函數的某個未知函數,之所以稱 未知函數 ,是因為我們在寫一個函數時,很可能根本不知道哪個函數會調用到我們的這個函數。

  • 在全局環境中調用函數是不會生成此 caller 屬性,因為不符合此屬性的存在意義/價值(見上條)。

  • 只有在當前函數的內部(上下文環境)才能調用當前函數的 caller 屬性,不能從外部調用。

callee 屬性

還是先放代碼:

function calleeDemo() { 
    console.log(arguments.callee); 
} 

有了上文對 caller 屬性的認知, callee 屬性就很好理解了,它實際上就是對當前函數對象的一個引用。有以下的點需要注意:

  • callee 屬性隸屬于Function的一個隱藏對象—— arguments 中,這個 arguments 對象大家應該不陌生,表示的就是當前函數傳入的參數,一般用于函數不限制參數數量的傳參。

  • 與 caller 屬性一樣,也是要在當前函數的內部(上下文環境)才有效。

  • 可配合 caller 屬性一起使用: arguments.callee.caller ,這樣就可以完全忽略到具體的函數名了。

  • 函數遞歸時用起來比用函數名調用函數更帶感!

apply / call 方法

這倆方法性質一樣,只是用法稍有不同,因此放在一起來介紹。還記得我上一篇文章《 javascript如何判斷變量的數據類型 》中介紹的利用 Object.prototype.toString.call 來判斷數據類型的方法么:

function type(obj) {
  return Object.prototype.toString.call(obj).slice(8, -1);    //換成用apply方法亦可
}
  • apply / call 方法的意義在于 借用 其它對象的成員方法來對目標對象執行操作。

  • 借用 的過程中, apply / call 方法會改變被借用的成員方法的上下文環境:把 this 這一與上下文環境高度相關的變量指向目標對象,而非原來的對象。看下面的這段代碼:

function Point(x, y){
    this.x = x;
    this.y = y;
}
Point.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
}
var p = new Point(0,0);
var circle = {x:1, y:1, r:1};    //只是一個普通的Object對象
p.move.call(circle, 2, 1);    //借用了Point類對象中的move方法
//p.move.apply(circle, [2, 1]);    //等價于p.move.call(circle, 2, 1);

這里的circle只是一個普通的Object對象,不含任何自定義的成員方法,但通過 apply / call 方法,可以借用Point類對象定義的move方法來幫助circle達到目的(本例其實是圓心在坐標軸上的移動)。在 借用 Point類對象的move方法時,move方法中的this就不再指向p,而是指向circle了,達到了上下文環境改變的效果。

另外,從代碼里也可以看出, call 方法與 apply 方法的區別僅在于: call 方法直接把需要傳入的參數列在目標對象其后,而 apply 方法則以數組的形式整體傳入。

bind 方法

bind 方法與 apply / call 方法也非常類似,相當于稍微再封裝了一下,仍以上述DEMO作為案例:

function Point(x, y){
    this.x = x;
    this.y = y;
}
Point.prototype.move = function(x, y) {
    this.x += x;
    this.y += y;
}
var p = new Point(0,0);
var circle = {x:1, y:1, r:1};
// p.move.call(circle, 2, 1);
// p.move.apply(circle, [2, 1]);
var circleMove = p.move.bind(circle, 2, 1);    //此時并不執行move方法
circleMove();    //此時才執行

從上面這段DEMO可以看出, bind 方法其實是給 apply / call 方法緩了一下,也可以說是封裝了一下方便后續調用,其實質上相當于下面的這段代碼:

function circleMove() {
    p.move.call(circle, 2, 1);
}
circleMove();

bind 方法兼容性適應

bind 方法,即 Function.prototype.bind ,屬于 ECMAScript 5 ,IE從 IE 10 版本才開始支持,那怎么做兼容性適應呢?

if(typeof Function.prototype.bind !== 'function') {
  Function.prototype.bind = function() {
    var thatFunc = this;
    var args = [];
    for(var i = 0; i < arguments.length; i++) {
      args[i] = arguments[i];
    }

return function() {
  var context = args.shift();
  thatFunc.apply(context, args);
}

} }</pre>

其思路是利用 apply 方法來封裝成 bind 方法。

</div>

來自: http://hao.jser.com/archive/9130/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!