理解bind與call,apply區別及其在實際項目中部分場景的運用

這次總結一下這周遇到的一些坑和總結這周工作的所得。

一、關於bind,call,apply的運用

我本來並不想講這三者的原理的,但是既然要講實際的運用就順帶講一下關鍵性的一點吧。
首先講講三者的共同點
改變this指向
實現繼承關係
…….
下面上一段代碼

    let obj = {
      dividend: 2
    }
    function isOdd (num) {
      console.log(this.dividend) // 1、undefined 2、2
      let result = num%this.dividend === 0 ? '偶數':'奇數';
      return (num + '是' + result);
    }
    isOdd(8); // 8是奇數
    isOdd.call(obj, 8); // 8是偶數

大家可以很明顯的看出函數isOdd原本是指向window的,這裏內部的this.dividend的值爲undefined,所以第一次直接執行isOdd函數將會產生 8 是奇數的情況,因爲num/this.dividend是NaN不爲0,那麼結果即爲奇數;

但是在第二次利用了isOdd.call後,isOdd的指向不再是window了,而是指向了call的第一個參數,此時爲obj,而函數執行內部的this.dividend自然就變成了2,最終完成函數的執行,輸出結果 8 位偶數。

而apply與call的用法是一致的,只是傳參形式上call我們是這樣的xxx.call(obj, arg1, arg2,...),而apply的參數必須要以數組的形式傳遞xxx.apply(obj, ['arg1', 'arg2', ...])

bind是以call的形式傳參的,但是bind與call、apply最大的區別就是bind綁定this指向並傳參後仍然爲一個函數,並沒有去調用,而call與apply是直接調用函數。下面我會以一個小例子的形式說明一下我在實際項目中用到bind的一種小情況;

二、日常項目中bind及call的運用例子

在正常的項目開發中,我們時常會使用各類開源的組件與框架,那麼無法避免的會存在一些個性化的需求是原本的組件與框架事先沒做到的,這時我們就可以去在使用組件提供的方法時利用bind、call、apply傳遞我們需要的參數並且在對應事件中做數據結構的處理;

  • 場景一:某些開源組件提供的函數內會有依賴注入的情況,我們直接將處理好的數據結構拋入依賴注入的函數中,組件便會幫我們完成dom結構的建立,如elementui的autoComplete組件,內部有一個cb的依賴,我們直接將數組傳入cb中便會在input標籤focus時出現對應的建議值

    先上有依賴注入的demo來方便大家理解bind在這個場景中的使用

    function add (arr) {
      let count = 0;
      arr.forEach( (val) => count += val );
      return count;
    }
    
    function even (num) {
      let result = num % 2 === 0 ? true:false;
      return result === true ? '偶數':'奇數';
    }
    
    function countIsLimit (arr, countFn ,limitNum) {
      let count = countFn(arr);
      // 這裏你當然可以不用arguments,而選擇直接在參數上多寫一個參數名,這裏我這樣用只是爲了告訴大家這種場景而已,新增參數名當然更直觀。
      let isEven = arguments[3](count); 
      let result = count <= limitNum ? '小於或等於限定值': '大於或等於限定值';
      return count + result + limitNum + '並且是' + isEven;
    }
    let testArr = [1, 2, 3, 4];
    
    let bindResult = countIsLimit.bind(null, testArr, add, 10, even)
    bindResult(); // 10小於或等於限定值10並且是偶數

    最後的結果顯而易見了,這個demo模仿了上述我所說的場景,首先我沒有改變countIsLimit這個函數的this指向,因爲在此場景下我們不需要改變,那麼我們便設爲null讓他繼續指向window,在countIsLimit的最後一個參數並不是我後面bind的even,因爲原本的函數中就沒這個參數,這個參數是後來我加入的一個自定義依賴,而原本函數中便存在一個countFn的依賴來進行最後的數據處理,但是現在我有一個需求就是我不止需要知道測試的數組和與限定值的關係,還要知道這個和的奇偶性,這時我們需要在函數內拿到數組的和再進行處理,即再數組內執行add函數後進行數據處理,這時這個需求就和我們場景中一致了,我們需要傳入新的參數對函數內部得到的數據進行處理並輸出,而這裏我傳入了一個新的依賴函數even來進行處理,並且將處理結果添加至最後的返回值上,這也是函數式編程的一個概念,所有的需求都有對應函數處理,並且通過依賴注入的形式互相處理最終數據,這種需要依賴注入的場景只有bind能實現,因爲我們需要新傳入依賴函數後,在函數內部進行處理後才執行bind的函數,如果此時用call與apply的話當然也可以,但是顯然沒有這麼直觀了,尤其是在某些我們並不清楚業務邏輯的情況下,我們是不知道業務最終返回的數據形式的,此時我們就應該只傳入依賴函數,讓熟悉的人去編寫業務邏輯處理了,這種情況顯然就不能用call與apply了!

  • 場景2:用別人已經封裝好的事件與函數,只是需要我們傳入新值去判斷,實際上這種情況與場景一類似,只是這時我們扮演的是熟悉業務邏輯的人,而不是新增依賴函數的人,這種場景就簡單太多了;上面講bind、call、apply的運用所用的demo就是例子~

三、利用JSON API解決部分場景下的深度克隆問題

在處理某些全局對象的數據時,我們只是在我們的模塊去對全局變量的數據結構進行處理後輸出,此時我們一般會深度克隆某一全局對象然後再進行處理,以防因爲對象與數組爲地址引用而改變了全局對象,當然在ES6有新的解決方式,但畢竟很多人還是沒用ES6的~

尤其在使用vuex、redux等插件來處理全局數據的情況尤其適用。

關鍵點就是JSON.stringifyJSON.parse的使用,當我們將一個對象利用stringify轉化爲json字符串在利用parse轉回來時,已經等於遍歷字符串 new 了一個新對象了,此時就不存在直接賦值引用的情況了,下面上demo。

let object = {
  name: 'YOLO',
  gender: 'male',
  hobby: ['movie', 'computer game']
}
let newObject = object;
newObject.name = 'jack';
console.log(newObject.name); // 'jack'
console.log(object.name); // 'jack'

顯然這裏是因爲object是一個對象,而對象的賦值是地址引用,而深度克隆毫無疑問就是爲了解決這種問題而存在的,但是這裏我們使用一個新 方法

let jsonObject = JSON.parse(JSON.stringify(object))
jsonObject.name = 'jack';
console.log(object.name); // 'YOLO'
console.log(jsonObject.name); // 'jack'

原理的話我上面已經講過了,大家都明白JSON API的使用前提是可以將鍵值轉化爲字符串,而使用這種方法如果鍵值是一個匿名函數的話這種方法就會失效,所以我最開始也聲明過這屬於奇技淫巧,只適用於某些場景~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章