趣談js的call和apply兩大召喚術

前言

《趣談js的bind牌膠水》這篇文章中,我聊到了js的bind牌膠水,這篇文章我來聊聊bind牌膠水的升級版:call和apply方法。前端

Why? ——> 爲何會出現apply和call?

《趣談js的bind牌膠水》中,我經過js的相關歷史,敘述了bind、call、apply三方法誕生的背景,同時也指出這三個方法出現的共同目的就是就是爲js的一等公民Function函數找個門當戶對的人家(指明Function函數的this指向),既然bind方法已經知足了目的,爲何還須要創造出call、apply兩個方法呢?這兩個方法和bind有哪些異同點?帶着些許疑問,且隨小生遨遊前行。git

What? ——> call和apply是啥玩意兒?

一、漢語釋義:

call:召喚、呼叫、訪問github

apply:應用、適用、申請編程

在call和apply的中文釋義中咱們能夠看出call、apply這兩個方法帶有明顯的鏈接特性,好比「召喚call」:who召喚who?「應用apply」:who應用到who上?還有bind的中文釋意義:「綁定」,從這三個中文釋義中不難看出知足鏈接特性的動詞須要三元素:1.主動鏈接方、2.被動鏈接方、3.鏈接兩者的中介。對比這三個中文釋義,能夠看出bind和call、apply的釋義略有不一樣,bind的中文釋義帶有明顯的靜態鏈接特性(只鏈接),call、apply的中文釋義中帶有明顯的動態鏈接特性(鏈接以後還使用),因此在三個方法的使用上,bind只負責鏈接函數與相應的對象,call、apply在鏈接好函數與相應的對象後還主動把「鏈接了指定對象的函數」給當場運行了!數組

二、語法解析:

function.call(thisArg, arg1, arg2, ...); // call語法 function.apply(thisArg, [argsArray]); // apply語法 複製代碼

具體的語法能夠去MDN上看詳情,這裏關於thisArg說如下幾個注意點:架構

  • 不傳,或者傳null,undefined,this指向window對象(若是沒有房子,那就只能露宿天地了,55555)
  • 傳遞另外一個函數的函數名fun2,this指向函數fun2的this指向(fun2隨誰,俺就隨誰,嫁雞隨雞嫁狗隨狗?)
  • 值爲原始值(數字,字符串,布爾值),this會指向該原始值的自動包裝對象,如Number、 String、Boolean
  • 傳遞一個對象,函數中的this指向這個對象

在上面的幾種thisArg參數例子中,咱們發現一個共同的事實就是:thisArg參數永遠會是個對象,原始值就用原始值對應的包裝對象,函數就用該引用該函數的對象,無對象時就是全局對象,那些看上去沒對象的狀況,其實也是有對象的,不難看出,js是一門面向對象編程的語言,到處都是對象,萬物皆有對象,那你呢,你有沒有對象?app

三、詳細敘述:

call和apply方法都是爲了改變函數的this值而生,具體使用以下:dom

var obj = {
    age: 22
  }

  function say(name) {
    console.log('我是:' + name + '|今年:' + this.age);
  }

  say.call(obj, 'jack'); // 我是:jack|今年:22
  say.apply(obj, ['mike']); // 我是:mike|今年:22
複製代碼
  • 經過代碼能夠看出call和apply有以以下相同點:
  1. 第一個參數指明瞭宿主對象
  2. 指明瞭新宿主對象後,當即運行該函數
  • 惟一不一樣點:apply接收的是數組格式的參數,call接受的是若干個參數。關於兩種傳參形式,我是這樣理解的:apply帶有「授予」之意,相似皇帝的封賞(是一種自上而下的交接),皇帝的封賞會給你一個清單,有些啥子東西都在清單裏,call帶有「呼喚」之意(是一種比較親密的交接),你呼喚一個朋友過來,給他講些小祕密,你會一五一十的把這些祕密逐個講出來。

How? ——> 怎樣使用call和apply?

call技能 —— 北風驟起:

技能詳解: 「Master」從天地召喚出一個強力風暴,逐一對多個目標形成60/85/135/160(+0.35)點魔法傷害。函數式編程

技能演示:函數

var Master = {
  name: '召喚師'
};
var target1 = 'enemy1';
var target2 = 'enemy2';
var target3 = 'enemy3';
var target4 = 'enemy4';
var target5 = 'enemy5';

function NorthernStorm(target1, target2, target3, target4, target5) {
  console.log(this.name + ' have slained an enemy ' + target1);
  console.log(this.name + ' have slained an enemy ' + target2);
  console.log(this.name + ' have slained an enemy ' + target3);
  console.log(this.name + ' have slained an enemy ' + target4);
  console.log(this.name + ' have slained an enemy ' + target5);
}

NorthernStorm.call(Master, target1, target2, target3, target4, target5);
複製代碼

apply技能 —— 末日風暴:

技能詳解:「Master」從天地中召喚出一個強大的末日風暴,能夠瞬間應用到一個目標羣體上,形成200/250/300/444(+1)點AOE魔法傷害。

技能演示:

var Master = {
  name: '召喚師'
};
var target1 = 'enemy1';
var target2 = 'enemy2';
var target3 = 'enemy3';
var target4 = 'enemy4';
var target5 = 'enemy5';

function PowerfulStorm(arr) {
  console.log(this.name + ' Penta Kill!');
}

PowerfulStorm.apply(Master, [target1, target2, target3, target4, target5]);
複製代碼

哈哈,上面我用遊戲技能簡單的演示了一下call和apply方法的使用,但願能幫助你們理解相關概念,爲了加深理解這裏我針對幾個具體的使用場景作了幾個示例:

1. 獲取數組中的最大/小值

var nums = [11, 15, 2, 20, 10];

var max = Math.max.apply(null, nums);
var min = Math.min.apply(null, nums);

console.log(max); // 20
console.log(min); // 2
複製代碼

2. 將函數的arguments轉換爲數組

function func() {
  var args = Array.prototype.slice.call(arguments);
  console.log(args);
}
func('hello', 'world'); // ["hello", "world"]
複製代碼

3. 判斷是否爲數組格式

var arr = [];
var res = Object.prototype.toString.call(arr); // 這裏獲取的是變量的 [[class]]屬性,通常方法沒有,只有借用Object原型上的toString方法才能夠
console.log(res); // [Object Array]
複製代碼

關於apply和call的使用例子不作過多敘述,由於網上一大把,以前一直以爲js的call、apply、bind三方法使用很彆扭,很醜陋(如今也以爲),後來我學會換個角度看世界後就舒服了不少,以這個例子爲例:

var nums = [11, 15, 2, 20, 10];
var max = Math.max.apply(null, nums);
複製代碼

咱們把不相關的剔除掉(一、爲空時this指向的對象就是Window全局對象;二、Window對象取代Math對象使用max方法),代碼以下:

Window.max(nums);
複製代碼

注意:上面的代碼只是輔助理解,在實際運行時,Window對象上只會短暫的存在max方法,一次性的使用了max方法以後,就會從Window上delete掉max方法,因此經過call、apply綁定給指定對象的函數最終並不會存在於指定對象上。

總結

1. bind和apply、call的異同

  • 相同點:都立足於改變函數的this指向
  • 不一樣點:
    1. call和applly會當即執行函數,bind只是綁定了函數,並不會當即執行函數
    2. call、apply由於要當即執行函數,因此第二個參數或以後的參數都是當前的真實參數,bind是「預設參數」(這裏能夠參考文章《趣談js的bind牌膠水》中關於bind預設參數的闡述)

一些想法

我我的一直以爲bind、call、apply使用起來不舒服,感受無關緊要,但後來發現這三個方法仍是有不少用武之地的,好比在dom對象中綁定事件就須要bind方法,好比想複用某些函數就能夠用到call和apply,js出現這三個方法很大程度上是由於js用的是函數式編程的樣子,但其實又是面向對象(DOM對象,數據對象等)的裏子,兩種編程思路參雜在了一塊兒,參雜其實沒問題,但兩者的參雜沒能很好融合,設計bind、apply、call就是爲了討好兩方,融合兩者,但這種帶有臨時性質的妥協方案,效果不咋地,由於一山不容二虎,總得有人作紅花,有人甘當綠葉,不是嗎?直到以Angular、React、Vue等爲表明的MVVM架構和改進的ES6新標準出現,前端開發進入新的模式,MVVM架構能讓前端開發較好的實現「面向對象」的編程模式,同時利用ES6的相關特性兼顧函數式編程的靈活性,以往不少問題都不須要bind、call、apply這三兄弟了,好比ES6的箭頭函數就是解決bind的神器,在React的開發中,若是按照傳統思路給事件的匿名函數綁定對象,須要手動用bind綁定,但利用ES6的「箭頭函數」能夠這樣綁定:

<div
  onClick={(res) => {
    // 這裏的this就是
    this.setState({
      name: 'jack'
    });
  }}
>
  Click Me
</div>

複製代碼

好比在上面如何使用call、apply的例子中能夠用ES6的擴展操做符...替代來處理:

// 將arguments轉換爲數組
function func() {
  var args = ([...arguments]);
  console.log(args);
}
func('hello', 'world'); // ["hello", "world"]

// 求數組最大值
var res = Math.max(...[2,20,22]);
console.log(res); // 22
複製代碼

JS在不斷的升級,這三個方法在當前開發的某些場景中可能還會有用武之地,但在我看來,bind、apply、call做爲一個「妥協方案」終將會慢慢的退出舞臺,但在它們被遺忘以前理解設計者們的智慧和想法,我以爲是頗有意思的。

結語

文章涉及內容不少,不免會有紕漏,望理性指正,一塊兒進步哦。

相關文章
相關標籤/搜索