call()、apply()、bind()的用法

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

來自: http://wwsun.github.io/posts/javascript-functions.html

JavaScript中的函數不僅是一種類似于Java中方法的語言功能,它還可以作為對象而存在。 本文將要探討JavaScript中函數的一些特殊用法,包括 call 、 apply 、 bind 三個原型方法。

Statement

  • 作者: 景莊 ,Web開發者,主要關注JavaScript、Node.js、React、Docker等。

函數基礎

JavaScript中的函數是一種類似于Java中方法的語言功能,不過它可以獨立于類進行定義。

函數式編程:由于JavaScript支持匿名函數,因此可以將函數作為對象來使用, 所以JavaScript不僅支持過程式編程(面向對象也是過程式編程的一種),還支持函數式編程。

上下文

函數的每次 調用 都會擁有一個特殊值——本次調用的上下文(context)——這就是 this 關鍵字的值。 如果函數掛載在一個對象上,作為對象的一個屬性,就稱它為對象的方法。當通過這個對象來調用函數時,該對象就是此次調用的上下文, 也就是該函數的 this 的值。

需要注意, this 是一個關鍵字,不是變量,也不是屬性名。JavaScript的語法不允許給 this 賦值。

函數是一種對象

JavaScript中的函數和Java中的方法或C語言中的函數最大不同在于,JavaScript中的函數也是一種對象。 但這并不意味著,所有的對象都是函數。函數是一種包含了可執行代碼,并能夠被其他代碼調用的特殊的對象。

和變量不同,關鍵字 this 沒有作用域的限制,嵌套的函數不會從調用它的函數中繼承 this 。 - 如果嵌套函數作為方法調用,其 this 的值指向調用它的對象。 - 如果嵌套函數作為函數調用,其 this 的值不是全局對象(非嚴格模式下)就是 undefined (嚴格模式下)。

很多人誤以為調用嵌套函數時 this 會指向掉i用外層函數的上下文。如果你想訪問這個外部函數的 this 值, 需要將 this 的值保存都在一個變量中,這個變量和內部函數都同在一個作用域內。例如:

var o = {
  m: function() {
    var self = this;
    console.log(this==o); // true
    f();

    function f() {
      console.log(this === o); // false,this的值是全局對象或undefined
      console.log(self === o); // true
    }
  }
}

閉包

JavaScript的函數可以嵌套在其他函數中定義,這樣它們就可以訪問它們被定義時所處的作用域中的任何變量。 這意味著JavaScript函數構成了一個閉包(closure),它給JavaScript帶來了非常強勁的編程能力。

作為值的函數

在JavaScript中,函數不僅是一種語法,也是值,也就是說,可以將函數賦值給變量,存儲在對象的屬性或數組的元素中, 作為參數傳入另外一個函數等。

bind、call、apply

每一個函數都包含一個 prototype 屬性,這個屬性是指向一個對象的引用,這個對象稱作“原型對象”。 每一個函數都包含不同的原型對象。當將函數用作構造函數的時候,新創建的對象會從原型對象上繼承屬性。

Function.prototype.call() 和 Function.prototype.apply()

call() 和 apply() 可以看作為某個對象的方法,通過調用方法的形式來間接調用函數。 它們的第一個參數是要調用函數的母對象,它是調用上下文,在函數體內通過 this 來獲得對它的引用。 apply() 方法和 call() 方法的作用相同,只不過函數傳遞的方式不一樣,它的實參都放入在一個數組中。

舉個例子, 以對象o的方法的形式調用函數f(),并傳入兩個參數 ,可以使用這樣的代碼:

var o = {};

function f(a, b) {
  return a + b;
}

f.call(o, 1, 2);        // 將函數f作為o的方法,實際上就是重新設置函數f的上下文
f.apply(o, [1, 2]);

再舉一個來自MDN的 例子使用call方法調用匿名函數

在下例中的for循環體內,我們創建了一個匿名函數,然后通過調用該函數的call方法,將每個數組元素作為指定的this值執行了那個匿名函數。 這個匿名函數的主要目的是給每個數組元素對象添加一個print方法,這個print方法可以打印出各元素在數組中的正確索引號。 當然,這里不是必須得讓數組元素作為this值傳入那個匿名函數(普通參數就可以),目的是為了演示call的用法。

var animals = [
  {species: 'Lion', name: 'King'},
  {species: 'Whale', name: 'Fail'}
];

for (var i = 0; i < animals.length; i++) {
  (function (i) { 
    this.print = function () { 
      console.log('#' + i  + ' ' + this.species + ': ' + this.name); 
    } 
    this.print();
  }).call(animals[i], i);
}

Function.prototype.bind()

bind() 是在ES5中新增的方法,從名字可以看出,這個方法的主要作用就是將函數綁定到某個對象。 當在函數 f() 上調用 bind() 方法并后竄入一個對象 o 作為參數,這個方法將返回一個新函數: (以函數調用的方式)調用新的函數將會把原始的函數 f() 作為 o 的方法來調用。例如:

function f(y) {
  return this.x + y;
}

var o = {
  x: 1
};

var g = f.bind(o);  // 通過調用 g(x) 來調用 o.f(x)
g(2); // 3

其實我們可以輕松的實現 bind() 方法:

// 返回一個函數,通過調用它來調用o中的方法f(),傳遞它所有的實參
function bind(f, o) {
  if (f.bind) return f.bind(o); // 如果bind()方法存在,使用bind()方法
  else return function () {
    return f.apply(o, arguments);
  }
}

函數式編程

JavaScript并非函數式編程語言,但在JavaScript中可以像操控對象一樣操控函數,也就是說可以在JavaScript中應用函數式編程技術。

使用函數處理數組

假設有一個數組,數組元素都是數字,我們想要計算這些元素的平均值和標準差。

var data = [1, 1, 3, 5, 5];
var sum = function(x, y) {
  return x + y;
};
var square = function(x) {
  return x * x;
};

var mean = data.reduce(sum)/data.length;
var deviations = data.map(x => x - mean);
var stddev = Math.sqrt(deviations.map(square).reduce(sum)/(data.length - 1));

高階函數

高階函數就是操作函數的函數,它接收一個或多個函數作為參數,并返回一個新函數。舉個例子:

function not(f) {
  return function () {
    var result = f.apply(this, arguments);
    return !result;
  };
}

// 判斷x是否為偶數的函數
var even = function(x) {
  return x % 2 === 0;
};

var odd = not(even);            // 一個新函數,所做的事情和even()相反
[1, 1, 3, 5, 5].every(odd);     // true,每個函數都是奇數

函數 not() 是個高階函數,因為它返回一個新的函數,這個新函數將它的實參傳入 f() ,并返回 f 的返回值的邏輯非。

References

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