前端進擊的巨人(四):略知函數式編程

系列更文前三篇文章,圍繞了一個重要的知識點:"函數"
函數調用棧、函數執行上下文、函數作用域到閉包。可見不理解函數式編程,代碼都擼不好。

前端進擊的巨人(四):略知函數式編程

 函數是一等公民

函數與其它數據類型一樣,可以作爲值賦給變量,作爲參數傳遞或返回值返回,也可以像對象一樣給函數創建屬性(不推薦給函數加屬性,雖然可用)。

函數在實際開發中應用:

  1. 函數聲明
  2. 函數表達式
  3. 匿名函數
  4. 自執行函數
// 函數聲明
function getName() {
    //...    
}

// 函數表達式
var getName = function() {
    //...
}

// 匿名函數
setTimeout(function(){
    //...
}, 1000);

// 自執行函數
(function(){
    //...
})();

何爲一等:優先級

函數聲明在"執行上下文創建階段"就會進行聲明並賦值,而var聲明變量會初始化爲undefined,實際賦值會等到"執行上下文執行階段"。函數表達式使用var來聲明,因此它遵循的是變量聲明的規則。( 如果函數名與變量重名,函數優先賦值)

"函數聲明優先級高於變量聲明,函數表達式,自稱一等公民。"

// 代碼書寫:
console.log(getName);
getName();
var getName;
getName = '我的名字';
function getName(){
    //...
}
console.log(getName);

// 實際執行
var getName;            // 變量名與函數名重名,函數優先賦值
function getName() {
    //...
}
console.log(getName);
getName();
getName = '我的名字';
console.log(getName);

函數式編程

函數式編程是一種編程思維方式,它建議我們在程序編寫時,對複用性高的功能代碼進行函數封裝,實現代碼的高複用性。

新手朋友往往是一塊代碼多次出現在不同的地方,常見的例子就是ajax請求方法運用,在需要請求後端數據時多次出現一串ajax請求代碼。

如果想要對ajax請求統一做異常處理,或管理後端返回狀態碼,是不是每處代碼都要修改???但是如果把ajax請求代碼封裝成一個函數,接口url和數據data通過參數傳遞到函數內部處理,後期擴展維護都方便修改,複用性擴展性都更加優秀。

所以實際敲代碼過程中,要經常提醒自己運用函數式編程的思維方式,只要有可能出現多次的業務邏輯代碼,那麼就要考慮是否封裝成函數,以便後續統一調用。

function sumScore(list) {
    var totalScore = 0
    for (var i = 0; i < list.length; i++) {
        totalScore += list[i];        
    }
    return totalScore;    
}

var list = [10, 8, 9, 7];
var totalScore = sumScore(list);    // 計算總分

TIPS: 函數名建議使用動詞,如addUser(),sumScore(),getUser()...

純函數

純函數:相同的輸入對應相同的輸出,穩定沒有副作用(不改變外部變量的值)

相同的輸入,相同的輸出

相同的參數傳入調用,要有相同的結果輸出,概念有點繞,上代碼栗子:

function getDate() {
    return new Date();
}
var dateOne = getDate();
var dateTwo = getDate();
var dateThr = getDate();

上述代碼中調用了三次getDate(),三次返回的值都不一樣。相同的輸入並沒有相同的輸出,所以getDate()並不是一個純函數。

TIPS:函數中使用new Date(),Math.random(), 異步等都可能造成函數不穩定。

沒有副作用(不改變外部環境的值)

部分小夥伴的代碼,在函數裏面直接修改參數的值,這是一種非常不推薦的做法,這樣做會造成代碼環境不可控制,污染外部變量環境,一旦出現錯誤排查起來:心累,三個字心好累。

函數有自己的局部作用域,因此函數中,對需要使用到的變量,管控在自身的作用域下。如果需要修改外部參數的值,通過函數返回值返回給函數調用者。修改外部參數值的操作不在函數內進行,確保對外部環境沒有副作用。

TIPS:參數爲引用類型時,參數複製的是地址指針,避免修改了引用類型中屬性值污染外部環境,如需使用建議手動深拷貝賦值。

function getGirlGift(list) {
    // 避免污染參數爲引用類型的list,對list深拷貝
    var newList = JSON.parse(JSON.stringify(list));
    newList.map(girl => {
        girl.gift = girl.age > 18 ? 'lipstick' : 'chocolates';
    });
    return newList;    // 返回新值
}

var girlList = [
    {name: 'Kelly', age: 20},
    {name: 'Alic', age: 16},
    {name: 'Moon', age: 23},
    {name: 'Nana', age: 17}
];

var girlGiftList = getGirlGift(girlList);
girlList         // 原用girlList不變
girlGiftList     // 每個girl多了gift屬性

Array對象的函數(純與不純)

// 不純的函數
array.push();       // 數組尾部插入
array.pop();        // 刪除並返回數組最後一個元素
array.unshift();    // 數組頭部插入
array.shift();      // 刪除並返回數組第一元素
array.splice();     // 刪除元素,並向數組添加元素
array.reverse();    // 顛倒數組元素的順序
array.sort();       // 排序數組元素

// 純函數
array.slice();      // 數組中返回選定的元素
array.concat();     // 連接數組,併發揮新數組
array.join();       // 按分隔符連接數組,返回字符串

>>更多Array對象方法,參考W3C

純函數的應用:狀態管理Redux,Vuex

流行框架中狀態管理就是純函數的實踐應用,引用redux的應用,reducer中返回新的狀態數據state,但不能去直接去修改state數據,以下爲redux中reducer的例子代碼:

export default (state = defaultState, action) => {
    let newState = JSON.parse(JSON.stringify(state));

    switch (action.type) {
        case DELETE_TODO_ITEM:
            newState.list.splice(action.value, 1);
        break;
        case ADD_TODO_ITEM:
            if (newState.inputValue.trim().length) {
                newState.list.push(newState.inputValue);
            }
            newState.inputValue = '';
        break;
        case INIT_LIST_ACTION: 
            newState = action.data
        break;
        default: 
        break;
    }

    return newState;
}

"自執行函數 + 閉包" 實現模塊化

模塊化包括:

  1. 私有變量
  2. 私有方法
  3. 公有變量
  4. 公有方法

上篇中《前端進擊的巨人(三):從作用域走進閉包》我們講解了作用域、閉包的原理機制。

"自執行函數可實現塊級作用域,而閉包則可實現外部環境對函數作用域內部數據的訪問。"

// 自執行函數 + 閉包實現模塊化
(function MakeModule(window) {
    var name = '以樂之名';
    var age = 28;
    var job = '程序員';
    
    function changeJob(newJob) {
        job = newJob;
    }
    
    function getName() {
        return name;        
    }
    
   window.modulePublic = {
        changeJob: changeJob,
        getName: getName
    }
})(window);

window.modulePublic.getName();
window.modulePublic.changeJob('產品經理');

對作用域,以及閉包知識還沒掌握的小夥伴,可回閱《前端進擊的巨人(三):從作用域走進閉包》

高階函數

高階函數是一個函數,它接收函數作爲參數或將函數作爲輸出返回

JavaScript中常用的高階函數:

  1. Array.prototype.map (映射遍歷)
  2. Array.prototype.filter (過濾)
  3. Array.prototype.reducer(累計)

除了內置的高階函數,我們實際開放中,高階函數應用的最多就是回調函數了。

function getOrder(url, datas, callBack) {
    return $.post(url, datas, callBack(orderInfo));
}

// getOrder就是一個高階函數,接收callBack函數作爲參數

高階函數的概念很簡單,"本身是函數,參數是函數,或返回值是函數""


參考文檔:

系列更文請關注專欄:《前端進擊的巨人》,不斷更新中。。。

本文首發Github,期待Star!
https://github.com/ZengLingYong/blog

作者:以樂之名
本文原創,有不當的地方歡迎指出。轉載請指明出處。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章