call、apply和bind方法的用法以及區(qū)別

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)用

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市捡多,隨后出現(xiàn)的幾起案子蓖康,更是在濱河造成了極大的恐慌,老刑警劉巖垒手,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒜焊,死亡現(xiàn)場離奇詭異,居然都是意外死亡淫奔,警方通過查閱死者的電腦和手機(jī)山涡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來唆迁,“玉大人鸭丛,你說我怎么就攤上這事√圃穑” “怎么了鳞溉?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鼠哥。 經(jīng)常有香客問我熟菲,道長看政,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任抄罕,我火速辦了婚禮允蚣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呆贿。我一直安慰自己嚷兔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布做入。 她就那樣靜靜地躺著冒晰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪竟块。 梳的紋絲不亂的頭發(fā)上壶运,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音浪秘,去河邊找鬼蒋情。 笑死,一個(gè)胖子當(dāng)著我的面吹牛耸携,可吹牛的內(nèi)容都是我干的恕出。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼违帆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了金蜀?” 一聲冷哼從身側(cè)響起刷后,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎渊抄,沒想到半個(gè)月后尝胆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡护桦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年含衔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片二庵。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贪染,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出催享,到底是詐尸還是另有隱情杭隙,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布因妙,位于F島的核電站痰憎,受9級(jí)特大地震影響票髓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铣耘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一洽沟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜗细,春花似錦裆操、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至橱野,卻和暖如春朽缴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背水援。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工密强, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蜗元。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓或渤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奕扣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子薪鹦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容