JavaScript中call,apply,bind以及this,你瞭解多少?

說明

在JavaScript中調用一個函數將暫停當前函數的執行,傳遞控制權和參數給新函數。除了聲明時定義形參,每一個函數接收兩個附加的參數:this和arguments。參數this在面向對象中很是重要,它取決於調用的模式。在JavaScript中共有四種**調用模式:**方法調用模式、函數調用模式、構造器調用模式、和apply(),call()方法調用模式。這些模式在如何初始化關鍵參數this存在差別。本文首先要提到的是this,拋開this單獨去說這些方法是沒有意義的。而後是如何妙用call,apply,bind這些方法去改變this的指向。javascript

目錄

this在不一樣模式下的意義; 借雞下蛋之妙用call,apply; 深刻理解bind函數; this在不一樣模式下的意義:java

  1. 全局上下文 在全局運行上下文中(在任何函數體外部),this 指代全局對象,不管是否在嚴格模式下。例如在瀏覽器環境中任何定義在全局的屬性,方法都將成爲全局對象window的屬性和方法。
console.log(this.document === document); // true
// 在瀏覽器中,全局對象爲 window 對象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
複製代碼
  1. 函數上下文 在函數內部,this的值取決於函數是如何調用的。
//直接調用
function f1(){
  return this;
}
f1() === window; // true
//this的值不是由函數調用設定。由於代碼不是在嚴格模式下執行,this 的值老是一個對象且默認爲全局對象
function f2(){
 "use strict"; // 這裏是嚴格模式
  return this;
}
f2() === undefined; // true
//在嚴格模式下,this 是在進入運行環境時設置的。若沒有定義,this的值將維持undefined狀態。也可能設置成任意值。
複製代碼
  1. 對象方法中的this 當以對象裏的方法的方式調用函數時,它們的 this 是調用該函數的對象. 下面的例子中,當 o.f() 被調用時,函數內的this將綁定到o對象。
var o = {
  prop: 38,
  f: function() {
    return this.prop;
  }
};
console.log(o.f()); // logs 38
複製代碼

注意,在何處或者如何定義調用函數徹底不會影響到this的行爲。在上一個例子中,咱們在定義o的時候爲其成員f定義了一個匿名函數。可是,咱們也能夠首先定義函數而後再將其附屬到o.f。這樣作this的行爲也一致:數組

var o = {prop: 37};
function independent() {
  return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
複製代碼

這說明this的值只與函數 f 做爲 o 的成員被調用有關係。 相似的,this 的綁定只受最靠近的成員引用的影響。在下面的這個例子中,咱們把一個方法g看成對象o.b的函數調用。在此次執行期間,函數中的this將指向o.b。事實上,這與對象自己的成員沒有多大關係,最靠近的引用纔是最重要的。瀏覽器

o.b = {
  g: independent,
  prop: 42
};
console.log(o.b.g()); // logs 42
複製代碼
  1. 原型鏈中的this 相同的概念在定義在原型鏈中的方法也是一致的。若是該方法存在於一個對象的原型鏈上,那麼this指向的是調用這個方法的對象,表現得好像是這個方法就存在於這個對象上同樣。
var o = {
  f : function(){ 
    return this.a + this.b; 
  }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f()); // 5
複製代碼

在這個例子中,對象p沒有屬於它本身的f屬性,它的f屬性繼承自它的原型。可是這對於最終在o中找到f屬性的查找過程來講沒有關係;查找過程首先從p.f的引用開始,因此函數中的this指向p。也就是說,由於f是做爲p的方法調用的,因此它的this指向了p。這是JavaScript的原型繼承中的一個有趣的特性。 5. getter 與 setter 中的 this 再次,相同的概念也適用時的函數做爲一個 getter 或者 一個setter調用。做爲getter或setter函數都會綁定 this 到從設置屬性或獲得屬性的那個對象。閉包

function modulus(){
  return Math.sqrt(this.re * this.re + this.im * this.im);
}
var o = {
  re: 1,
  im: -1,
  get phase(){
    return Math.atan2(this.im, this.re);
  }
};
Object.defineProperty(o, 'modulus', {
  get: modulus, enumerable:true, configurable:true});
console.log(o.phase, o.modulus); // logs -0.78 1.4142
複製代碼
  1. 構造函數中的 this 當一個函數被做爲一個構造函數來使用(使用new關鍵字),它的this與即將被建立的新對象綁定。 注意:當構造器返回的默認值是一個this引用的對象時,能夠手動設置返回其餘的對象,若是返回值不是一個對象,返回this。
function C(){
  this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
  this.a = 37;
  return {a:38};
}
o = new C2();
console.log(o.a); // logs 38
複製代碼
  1. DOM事件處理函數中的 this 當函數被用做事件處理函數時,它的this指向觸發事件的元素(一些瀏覽器在動態添加監聽器時不遵照這個約定,除非使用addEventListener )。
// 被調用時,將關聯的元素變成藍色
function bluify(e){
  console.log(this === e.currentTarget); // 老是 true
  // 當 currentTarget 和 target 是同一個對象是爲 true
  console.log(this === e.target);        
  this.style.backgroundColor = '#A5D9F3';
}
// 獲取文檔中的全部元素的列表
var elements = document.getElementsByTagName('*');
// 將bluify做爲元素的點擊監聽函數,當元素被點擊時,就會變成藍色
for(var i=0 ; i<elements.length ; i++){
  elements[i].addEventListener('click', bluify, false);
}
複製代碼

借雞下蛋之妙用call,apply: apply fun.apply(thisArg[, argsArray])方法在指定 this 值和參數(參數以數組或類數組對象的形式存在)的狀況下調用某個函數。app

  • thisArg 在 fun 函數運行時指定的 this 值。須要注意的是,指定的 this 值並不必定是該函數執行時真正的 this 值,若是這個函數處於非嚴格模式下,則指定爲 null 或 undefined 時會自動指向全局對象(瀏覽器中就是window對象),同時值爲原始值(數字,字符串,布爾值)的 this 會指向該原始值的自動包裝對象。
  • argsArray 一個數組或者類數組對象,其中的數組元素將做爲單獨的參數傳給 fun 函數。若是該參數的值爲null 或 undefined,則表示不須要傳入任何參數

在調用一個存在的函數時,你能夠爲其指定一個 this 對象。 this指當前對象,也就是正在調用這個函數的對象。使用apply,你能夠只寫一次這個方法而後在另外一個對象中繼承它,而不用在新對象中重複寫該方法。apply 與 call() 很是類似,不一樣之處在於提供參數的方式。apply 使用參數數組而不是一組參數列表。apply 可使用數組字面量.你也可使用 arguments 對象做爲 argsArray 參數。arguments 是一個函數的局部變量。 它能夠被用做被調用對象的全部未指定的參數。 這樣,你在使用apply函數的時候就不須要知道被調用對象的全部參數。 你可使用arguments來把全部的參數傳遞給被調用對象。 被調用對象接下來就負責處理這些參數。dom

  1. 使用apply來連接構造器
Function.prototype.construct = function (aArgs) {
  var oNew = Object.create(this.prototype);
  this.apply(oNew, aArgs);
  return oNew;
};
//另外一種可選的方法是使用閉包
Function.prototype.construct = function(aArgs) {
  var fConstructor = this, fNewConstr = function() { 
    fConstructor.apply(this, aArgs); 
  };
  fNewConstr.prototype = fConstructor.prototype;
  return new fNewConstr();
};
複製代碼
  1. 使用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);
}
複製代碼
  1. 使用call方法調用匿名函數而且指定上下文的'this'
function greet() {
  var reply = [this.person, 'Is An Awesome', this.role].join(' ');
  console.log(reply);
}
var i = {
  person: 'Douglas Crockford', role: 'Javascript Developer'
};
greet.call(i); // Douglas Crockford Is An Awesome Javascript Developer
當一個函數的函數體中使用了this關鍵字時,經過全部函數都從Function對象的原型中繼承的call()方法和apply()方法調用時,它的值能夠綁定到一個指定的對象上。

 function add(c, d){
   return this.a + this.b + c + d;
 }
 var o = {a:1, b:3};
 // The first parameter is the object to use as 'this', subsequent parameters are passed as 
 // arguments in the function call
 add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
 // The first parameter is the object to use as 'this', the second is an array whose
 // members are used as the arguments in the function call
 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
複製代碼

使用 call 和 apply 函數的時候要注意,若是傳遞的 this 值不是一個對象,JavaScript 將會嘗試使用內部 ToObject 操做將其轉換爲對象。所以,若是傳遞的值是一個原始值好比 7 或 'foo' ,那麼就會使用相關構造函數將它轉換爲對象,因此原始值 7 經過new Number(7)被轉換爲對象,而字符串'foo'使用 new String('foo') 轉化爲對象,例如函數

function bar() {
   console.log(Object.prototype.toString.call(this));
 }
 bar.call(7); // [object Number]
複製代碼

bind函數 fun.bind(thisArg[, arg1[, arg2[, ...]]])bind() 函數會建立一個新函數(稱爲綁定函數),新函數與被調函數(綁定函數的目標函數)具備相同的函數體(在 ECMAScript 5 規範中內置的call屬性)。當目標函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。一個綁定函數也能使用new操做符建立對象:這種行爲就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。ui

  1. 建立綁定函數 bind() 最簡單的用法是建立一個函數,使這個函數不論怎麼調用都有一樣的 this 值。JavaScript新手常常犯的一個錯誤是將一個方法從對象中拿出來,而後再調用,但願方法中的 this 是原來的對象。(好比在回調中傳入這個方法。)若是不作特殊處理的話,通常會丟失原來的對象。從原來的函數和原來的對象建立一個綁定函數,則能很漂亮地解決這個問題:
his.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX(); // 9, because in this case, "this" refers to the global object
// Create a new function with 'this' bound to module
//New programmers (like myself) might confuse the global var getX with module's property getX
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
複製代碼
  1. 偏函數(Partial Functions) bind()的另外一個最簡單的用法是使一個函數擁有預設的初始參數。這些參數(若是有的話)做爲bind()的第二個參數跟在this(或其餘對象)後面,以後它們會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們的後面。
function list() {
  return Array.prototype.slice.call(arguments);
}
var list1 = list(1, 2, 3); // [1, 2, 3]
// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);
var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]
複製代碼
  1. 配合 setTimeout 在默認狀況下,使用 window.setTimeout() 時,this 關鍵字會指向 window (或全局)對象。當使用類的方法時,須要 this 引用類的實例,你可能須要顯式地把 this 綁定到回調函數以便繼續使用實例。
function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom();  // 一秒鐘後, 調用'declare'方法
複製代碼
  1. 快捷調用 在你想要爲一個須要特定的 this 值得函數建立一個捷徑(shortcut)的時候,bind() 方法也很好用.你能夠用 Array.prototype.slice 來將一個相似於數組的對象(array-like object)轉換成一個真正的數組,就拿它來舉例子吧。你能夠建立這樣一個捷徑:
var slice = Array.prototype.slice;
// ...
slice.apply(arguments);
複製代碼

用 bind() 可使這個過程變得簡單。在下面這段代碼裏面,slice 是 Function.prototype 的 call() 方法的綁定函數,而且將 Array.prototype 的 slice() 方法做爲 this 的值。這意味着咱們壓根兒用不着上面那個 apply() 調用了。this

// same as "slice" in the previous example
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(unboundSlice);
// ...
slice(arguments);
複製代碼

ECMAScript 5 引入了 Function.prototype.bind。調用f.bind(someObject)會建立一個與f具備相同函數體和做用域的函數,可是在這個新函數中,this將永久地被綁定到了bind的第一個參數,不管這個函數是如何被調用的。

function f(){
  return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty
複製代碼
相關文章
相關標籤/搜索