call唇辨、apply、bind的作用是改變函數(shù)運(yùn)行時this的指向能耻,所以先說清楚this赏枚。
方法調(diào)用模式
當(dāng)一個函數(shù)被保存為對象的一個方法時,如果調(diào)用表達(dá)式包含一個提取屬性的動作晓猛,那么它就是被當(dāng)做一個方法來調(diào)用饿幅,此時的this被綁定到這個對象。
var a = 1
var obj1 = {
a:2,
fn:function(){
console.log(this.a)
}
}
obj1.fn()//2
DOM對象綁定事件也屬于方法調(diào)用模式戒职,因此它綁定的this就是事件源DOM對象诫睬。
document.addEventListener('click', function(e){
console.log(this);
setTimeout(function(){
console.log(this);
}, 200);
}, false);
點(diǎn)擊頁面,依次輸出:document和window對象帕涌。
函數(shù)調(diào)用模式:
就是普通函數(shù)的調(diào)用摄凡,此時的this被綁定到window
- 最普通的函數(shù)調(diào)用
function fn1(){
console.log(this)//window
}
fn1()
- 函數(shù)嵌套
function fn1(){
function fn2(){
console.log(this)//window
}
fn2()
}
fn1()
- 把函數(shù)賦值之后再調(diào)用
var a = 1
var obj1 = {
a:2,
fn:function(){
console.log(this.a)
}
}
var fn1 = obj1.fn
fn1()//1
obj1.fn是一個函數(shù)function(){console.log(this.a)},此時fn1就是不帶任何修飾的函數(shù)調(diào)用蚓曼,function(){console.log(this.a)}.call(undefined)亲澡,按理說打印出來的 this 應(yīng)該就是 undefined 了吧,但是瀏覽器里有一條規(guī)則:
如果你傳的 context 就 null 或者 undefined纫版,那么 window 對象就是默認(rèn)的 context(嚴(yán)格模式下默認(rèn) context 是 undefined)
因此上面的this綁定的就是window床绪,它也被稱為隱性綁定。
如果你希望打印出2,可以修改fn1()為fn1.call(obj1)癞己,顯示地綁定this為obj1
- 回調(diào)函數(shù)
var a = 1
function f1(fn){
fn()
console.log(a)//1
}
f1(f2)
function f2(){
var a = 2
}
改寫代碼如下:
var a = 1
function f1(){
(function (){var a = 2})()
console.log(a)//1
}
f1()
(f2中的a只有f2內(nèi)部能訪問膀斋,f1只能延作用域鏈找,f2中把var去掉 則打印結(jié)果為2痹雅;)
仍舊是最普通的函數(shù)調(diào)用仰担,f1.call(undefined),this指向window绩社,打印出的是全局的a摔蓝。
借此,我們終于可以解釋為什么setTimeout總是丟失this了愉耙,因?yàn)樗簿褪且粋€回調(diào)函數(shù)而已贮尉。
setTimeout(function() {
console.log(this)//window
function fn(){
console.log(this)//window
}
fn()
}, 0);
構(gòu)造器調(diào)用模式:
new一個函數(shù)時,背地里會將創(chuàng)建一個連接到prototype成員的新對象朴沿,同時this會被綁定到那個新對象上
function Person(name,age){
// 這里的this都指向?qū)嵗? this.name = name
this.age = age
this.sayAge = function(){
console.log(this.age)
}
}
var dot = new Person('Dot',2)
dot.sayAge()//2
this 指向構(gòu)造函數(shù)實(shí)例
call
call 方法第一個參數(shù)是要綁定給this的值(指定的對象猜谚,該對象就是函數(shù)執(zhí)行的上下文),后面?zhèn)魅氲氖且粋€參數(shù)列表赌渣。當(dāng)?shù)谝粋€參數(shù)為null魏铅、undefined的時候,默認(rèn)指向window锡垄。
var arr = [1, 2, 3, 89, 46]
var max = Math.max.call(null, arr[0], arr[1], arr[2], arr[3], arr[4])//89
var obj = {
message: 'My name is: '
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ' ' + lastName)
}
getName.call(obj, 'Dot', 'Dolby')
apply
apply接受兩個參數(shù)沦零,第一個參數(shù)是要綁定給this的值,第二個參數(shù)是一個參數(shù)數(shù)組货岭。當(dāng)?shù)谝粋€參數(shù)為null路操、undefined的時候,默認(rèn)指向window千贯。
var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89
事實(shí)上apply 和 call 的用法幾乎相同, 唯一的差別在于:當(dāng)函數(shù)需要傳遞多個變量時, apply 可以接受一個數(shù)組作為參數(shù)輸入, call 則是接受一系列的單獨(dú)變量屯仗。
ar obj = {
message: 'My name is: '
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ' ' + lastName)
}
getName.apply(obj, ['Dot', 'Dolby'])// My name is: Dot Dolby
bind
和call很相似,第一個參數(shù)是this的指向搔谴,從第二個參數(shù)開始是接收的參數(shù)列表魁袜。區(qū)別在于bind方法返回值是函數(shù)以及bind接收的參數(shù)列表的使用。
- bind返回值是函數(shù)
var obj = {
name: 'Dot'
}
function printName() {
console.log(this.name)
}
var dot = printName.bind(obj)
console.log(dot) // function () { … }
dot() // Dot
bind 方法不會立即執(zhí)行敦第,而是返回一個改變了上下文 this 后的函數(shù)峰弹。而原函數(shù) printName 中的 this 并沒有被改變,依舊指向全局對象 window芜果。
- 參數(shù)的使用
function fn(a, b, c) {
console.log(a, b, c);
}
var fn1 = fn.bind(null, 'Dot');
fn('A', 'B', 'C'); // A B C
fn1('A', 'B', 'C'); // Dot A B
fn1('B', 'C'); // Dot B C
fn.call(null, 'Dot'); // Dot undefined undefined
call 是把第二個及以后的參數(shù)作為 fn 方法的實(shí)參傳進(jìn)去鞠呈,而 fn1 方法的實(shí)參實(shí)則是在 bind 中參數(shù)的基礎(chǔ)上再往后排。
有時候我們也用bind方法實(shí)現(xiàn)函數(shù)珂里化右钾,以下是一個簡單的示例:
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2);
// 3
addTen(2);
// 12
在低版本瀏覽器沒有 bind 方法蚁吝,我們也可以自己實(shí)現(xiàn)一個旱爆。
if (!Function.prototype.bind) {
Function.prototype.bind = function () {
var self = this, // 保存原函數(shù)
context = [].shift.call(arguments), // 保存需要綁定的this上下文
args = [].slice.call(arguments); // 剩余的參數(shù)轉(zhuǎn)為數(shù)組
return function () { // 返回一個新函數(shù)
return self.apply(context, [].concat.call(args, [].slice.call(arguments)));
}
}
}
function () {
var fn=this;
var context=arguments[0];
var args=Array.prototype.slice.call(arguments,1);
return function () {
return fn.apply(context,args)
}
}
應(yīng)用場景
- 求數(shù)組中的最大和最小值
var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89
var min = Math.min.apply(null,arr)//1
- 將類數(shù)組轉(zhuǎn)化為數(shù)組
var trueArr = Array.prototype.slice.call(arrayLike)
- 數(shù)組追加
var arr1 = [1,2,3];
var arr2 = [4,5,6];
var total = [].push.apply(arr1, arr2);//6
// arr1 [1, 2, 3, 4, 5, 6]
// arr2 [4,5,6]
- 判斷變量類型
function isArray(obj){
return Object.prototype.toString.call(obj) == '[object Array]';
}
isArray([]) // true
isArray('dot') // false
- 利用call和apply做繼承
function Person(name,age){
// 這里的this都指向?qū)嵗? this.name = name
this.age = age
this.sayAge = function(){
console.log(this.age)
}
}
function Female(){
Person.apply(this,arguments)//將父元素所有方法在這里執(zhí)行一遍就繼承了
}
var dot = new Female('Dot',2)
- 使用 log 代理 console.log
function log(){
console.log.apply(console, arguments);
}
// 當(dāng)然也有更方便的 var log = console.log()
總結(jié)
call、apply和bind函數(shù)存在的區(qū)別:
bind返回對應(yīng)函數(shù), 便于稍后調(diào)用窘茁; apply, call則是立即調(diào)用怀伦。
除此外, 在 ES6 的箭頭函數(shù)下, call 和 apply 將失效, 對于箭頭函數(shù)來說:
- 箭頭函數(shù)體內(nèi)的 this 對象, 就是定義時所在的對象, 而不是使用時所在的對象;所以不需要類似于
var _this = this
這種丑陋的寫法 - 箭頭函數(shù)不可以當(dāng)作構(gòu)造函數(shù),也就是說不可以使用 new 命令, 否則會拋出一個錯誤
- 箭頭函數(shù)不可以使用 arguments 對象,山林,該對象在函數(shù)體內(nèi)不存在. 如果要用, 可以用 Rest 參數(shù)代替
- 不可以使用 yield 命令, 因此箭頭函數(shù)不能用作 Generator 函數(shù)房待,什么是Generator函數(shù)可自行查閱資料,推薦閱讀阮一峰Generator 函數(shù)的含義與用法捌朴,Generator 函數(shù)的異步應(yīng)用