call畦攘、apply霸妹、bind的作用是改變函數(shù)運(yùn)行時(shí)this的指向,所以先說清楚this知押。
以下是函數(shù)的調(diào)用方法:
方法調(diào)用模式:
當(dāng)一個(gè)函數(shù)被保存為對(duì)象的一個(gè)方法時(shí)叹螟,如果調(diào)用表達(dá)式包含一個(gè)提取屬性的動(dòng)作,那么它就是被當(dāng)做一個(gè)方法來調(diào)用台盯,此時(shí)的this被綁定到這個(gè)對(duì)象罢绽。
var a = 1
var obj1 = {
a:2,
fn:function(){
console.log(this.a)
}
}
obj1.fn()//2
此時(shí)的this是指obj1這個(gè)對(duì)象,obj1.fn()
實(shí)際上是obj1.fn.call(obj1)
静盅,事實(shí)上誰調(diào)用這個(gè)函數(shù)良价,this就是誰。補(bǔ)充一下蒿叠,DOM對(duì)象綁定事件也屬于方法調(diào)用模式明垢,因此它綁定的this就是事件源DOM對(duì)象。如:
document.addEventListener('click', function(e){
console.log(this);
setTimeout(function(){
console.log(this);
}, 200);
}, false);
點(diǎn)擊頁面市咽,依次輸出:document和window對(duì)象
解析:點(diǎn)擊頁面監(jiān)聽click事件屬于方法調(diào)用痊银,this指向事件源DOM對(duì)象,即obj.fn.apply(obj)
魂务,setTimeout內(nèi)的函數(shù)屬于回調(diào)函數(shù)曼验,可以這么理解,f1.call(null,f2)
粘姜,所以this指向window鬓照。
函數(shù)調(diào)用模式:
就是普通函數(shù)的調(diào)用,此時(shí)的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是一個(gè)函數(shù)function(){console.log(this.a)}
孤紧,此時(shí)fn1就是不帶任何修飾的函數(shù)調(diào)用豺裆,function(){console.log(this.a)}.call(undefined)
,按理說打印出來的 this 應(yīng)該就是 undefined 了吧号显,但是瀏覽器里有一條規(guī)則:
如果你傳的 context 就 null 或者 undefined臭猜,那么 window 對(duì)象就是默認(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()
仍舊是最普通的函數(shù)調(diào)用揽碘,f1.call(undefined)
次屠,this指向window园匹,打印出的是全局的a。
借此劫灶,我們終于可以解釋為什么setTimeout總是丟失this了裸违,因?yàn)樗簿褪且粋€(gè)回調(diào)函數(shù)而已。
setTimeout(function() {
console.log(this)//window
function fn(){
console.log(this)//window
}
fn()
}, 0);
構(gòu)造器調(diào)用模式:
new一個(gè)函數(shù)時(shí)本昏,背地里會(huì)將創(chuàng)建一個(gè)連接到prototype成員的新對(duì)象供汛,同時(shí)this會(huì)被綁定到那個(gè)新對(duì)象上
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
call
call 方法第一個(gè)參數(shù)是要綁定給this的值,后面?zhèn)魅氲氖且粋€(gè)參數(shù)列表涌穆。當(dāng)?shù)谝粋€(gè)參數(shù)為null怔昨、undefined的時(shí)候,默認(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
可以這么理解:
obj1.fn()
obj1.fn.call(obj1);
fn1()
fn1.call(null)
f1(f2)
f1.call(null,f2)
看一個(gè)例子:
var obj = {
message: 'My name is: '
}
function getName(firstName, lastName) {
console.log(this.message + firstName + ' ' + lastName)
}
getName.call(obj, 'Dot', 'Dolby')
apply
apply接受兩個(gè)參數(shù)朱监,第一個(gè)參數(shù)是要綁定給this的值,第二個(gè)參數(shù)是一個(gè)參數(shù)數(shù)組原叮。當(dāng)?shù)谝粋€(gè)參數(shù)為null、undefined的時(shí)候巡蘸,默認(rèn)指向window奋隶。
var arr = [1,2,3,89,46]
var max = Math.max.apply(null,arr)//89
可以這么理解:
obj1.fn()
obj1.fn.apply(obj1);
fn1()
fn1.apply(null)
f1(f2)
f1.apply(null,f2)
是不是覺得和前面寫的call用法很像,事實(shí)上apply 和 call 的用法幾乎相同, 唯一的差別在于:當(dāng)函數(shù)需要傳遞多個(gè)變量時(shí), apply 可以接受一個(gè)數(shù)組作為參數(shù)輸入, call 則是接受一系列的單獨(dú)變量悦荒。
看一個(gè)例子:
var 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
可以看到唯欣,obj 是作為函數(shù)上下文的對(duì)象,函數(shù) getName 中 this 指向了 obj 這個(gè)對(duì)象搬味。參數(shù) firstName 和 lastName 是放在數(shù)組中傳入 getName 函數(shù)境氢。
call和apply可用來借用別的對(duì)象的方法,這里以call()為例:
var Person1 = function () {
this.name = 'Dot';
}
var Person2 = function () {
this.getname = function () {
console.log(this.name);
}
Person1.call(this);
}
var person = new Person2();
person.getname(); // Dot
從上面我們看到碰纬,Person2 實(shí)例化出來的對(duì)象 person 通過 getname 方法拿到了 Person1 中的 name萍聊。因?yàn)樵?Person2 中,Person1.call(this) 的作用就是使用 Person1 對(duì)象代替 this 對(duì)象悦析,那么 Person2 就有了 Person1 中的所有屬性和方法了寿桨,相當(dāng)于 Person2 繼承了 Person1 的屬性和方法。
對(duì)于什么時(shí)候該用什么方法强戴,其實(shí)不用糾結(jié)亭螟。如果你的參數(shù)本來就存在一個(gè)數(shù)組中,那自然就用 apply骑歹,如果參數(shù)比較散亂相互之間沒什么關(guān)聯(lián)预烙,就用 call。像上面的找一組數(shù)中最大值的例子道媚,當(dāng)然是用apply合理扁掸。
bind
和call很相似娱节,第一個(gè)參數(shù)是this的指向,從第二個(gè)參數(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 方法不會(huì)立即執(zhí)行,而是返回一個(gè)改變了上下文 this 后的函數(shù)狸剃。而原函數(shù) printName 中的 this 并沒有被改變掐隐,依舊指向全局對(duì)象 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 是把第二個(gè)及以后的參數(shù)作為 fn 方法的實(shí)參傳進(jìn)去钞馁,而 fn1 方法的實(shí)參實(shí)則是在 bind 中參數(shù)的基礎(chǔ)上再往后排虑省。
有時(shí)候我們也用bind方法實(shí)現(xiàn)函數(shù)珂里化,以下是一個(gè)簡單的示例:
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)一個(gè)探颈。
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 () { // 返回一個(gè)新函數(shù)
self.apply(context, [].concat.call(args, [].slice.call(arguments)));
}
}
}
應(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返回對(duì)應(yīng)函數(shù), 便于稍后調(diào)用训措; apply, call則是立即調(diào)用伪节。
除此外, 在 ES6 的箭頭函數(shù)下, call 和 apply 將失效, 對(duì)于箭頭函數(shù)來說:
- 箭頭函數(shù)體內(nèi)的 this 對(duì)象, 就是定義時(shí)所在的對(duì)象, 而不是使用時(shí)所在的對(duì)象;所以不需要類似于
var _this = this
這種丑陋的寫法 - 箭頭函數(shù)不可以當(dāng)作構(gòu)造函數(shù),也就是說不可以使用 new 命令, 否則會(huì)拋出一個(gè)錯(cuò)誤
- 箭頭函數(shù)不可以使用 arguments 對(duì)象,绩鸣,該對(duì)象在函數(shù)體內(nèi)不存在. 如果要用, 可以用 Rest 參數(shù)代替
- 不可以使用 yield 命令, 因此箭頭函數(shù)不能用作 Generator 函數(shù)怀大,什么是Generator函數(shù)可自行查閱資料,推薦閱讀阮一峰Generator 函數(shù)的含義與用法呀闻,Generator 函數(shù)的異步應(yīng)用
參考資料: