在ECMAScript中,每一個函數都包含兩個繼承而來的方法:apply() 和 call(),這兩個方法的用途都是在特定的做用域中調用函數,主要做用跟bind同樣,用來改變函數體內this的指向,或者說是在函數調用時改變上下文。
文章儘可能使用大量實例進行講解,它們的使用場景。同時,也會由淺入深的引導出一些理論,畢竟這幾個經常使用方法,在MDN上都能找到合理的解釋javascript
var fruit = { fruitName:"apple" } function getFruit() { console.log("I like "+this.fruitName) } getFruit(); // log I like undefined getFruit.call(fruit) // log I like apple getFruit.apply(fruit) // log I like apple var newBind = getFruit.bind(fruit) newBind(); // log I like apple
當 getFruit 並不是做爲一個對象的屬性,而是直接當作一個函數來調用,裏面的this
就會被綁定到全局對象上,即window上, 因此直接調用 getFruit
,裏面的this
指向了全局對象上,返回 undefined
。前端
在嚴格模式下,函數被調用後,裏面的this默認是 undefined
後面,經過調用函數上的call
和apply
方法,該變this
指向,函數裏面的this
指向fruit
。java
區別:bind
一樣實現了改變this
指向的功能,可是它不會當即執行,而是會從新建立一個綁定函數,新函數被調用時,使用bind()
方法裏面的第一個參數做爲this
git
這三個方法,從接受的第二參數開始,都直接傳遞給函數,可是接受參數的方法卻很大的不一樣。github
call,從第二個參數開始,以參數列表的形式展現,數組
apply,則把傳遞的函數參數,放在一個數組裏面做爲第二個參數。閉包
fn.call(obj,arg1,arg2); fn.apply(obj,[arg1,arg2])
bind,從第二個參數開始,一樣以參數列表的形式,可是會提早放在新綁定函數的參數以前app
var foo = function(name,age){ console.log("name: "+name+"- age: "+age) } var p1 = foo.bind(this,"popo"); // "popo" 做爲新函數的第一個參數。 p1(13); // logs name: popo- age: 13 p1("bobo",14) // logs name: popo- age: bobo
$('.div-class').on('click',function(event) { /*TODO*/ }.bind(this)); } }
一般,咱們在改變函數上下文以前,都會使用相似that = this
,或者self,_this
,來把this賦值給一個變量。利用.bind()
,能夠傳入外層的上下文。函數
循環中利用閉包來處理回調ui
for(var i = 0;i < 10;i++){ (function(j){ setTimeout(function(){ console.log(j); },600); })(i) }
每次循環,都會產生一個當即執行的函數,函數內部的局部變量j保存不一樣時期i的值,循環過程當中,setTimeout回調按順序放入消息隊列中,等for循環結束後,堆棧中沒有同步的代碼,就去消息隊列中,執行對應的回調,打印出j的值。
同理,能夠利用bind
,每次都建立新的函數,而且已經預先設置了參數,傳入不一樣的指針
function func(i) { console.log(i) } for(var i =0 ;i< 10;i++) { setTimeout(func.bind(null,i),600) }
var Person = function(name,age) { this.name = name; this.age = age; } var P1 = function(name,age) { // 借用構造函數的方式實現繼承 // 利用call 繼承了Person Person.call(this,name,age) } P1.prototype.getName = function() { console.log("name: "+this.name+", age: "+this.age); } var newPerson = new P1("popo",20); // logs name: popo, age: 20 newPerson.getName();
實質上,能夠當作經過call()
或者apply()
方法,在即將新建的對象,即這裏的newPerson
上,執行超類型的構造函數,分別在當前上下文this
上添加name
和age
屬性。
function isArray(value) { return Object.prototype.toString.call(value) == "[object Array]" }
借用了Object原生的toString()方法,打印出對應變量的構造函數名,
// 實現一個簡單的數組 'unshift'方法 Array.prototype.unshift = function(){ this.splice.apply(this, [0,0].concat(Array.prototype.slice.apply(arguments))); return this.length; }
首先,利用this.splice.apply()
,其中splice
,能夠直接從數組中移除或者插入變量。apply()
則以數組的形式傳遞參數,須要利用concat
拼接數組。
當函數被調用時,在函數內部會獲得類數組arguments
,它擁有一個length屬性,可是沒有任何數組的方法。因此,將slice
方法中的this
指向arguments
,獲取到arguments
的長度,從而肯定方法的start
和end
下標,獲得一個數組變量。
一樣適用的還有,DOM裏面的NodeList對象,它也是一種類數組對象。
bind
方法在ECMAScript5裏面被引入,前面提到過,調用該方法時,返回一個新的函數,能夠簡單使用下面方法實現其改變this
指向的功能。
Function.prototype.bind = function(scope) { var fn = this; return function() { return fn.apply(scope) } }
接着,就能夠利用concat
把bind傳遞的預置參數拼接到新函數的參數列表中。
Function.prototype.bind = function(scope) { var args = Array.prototype.slice.call(arguments,1) var fn = this return function() { return fn.apply(scope,args.concat(Array.prototype.slice.call(arguments))) } }
參考連接