【JavaScript】幾個必需要會的高級編程技巧

寫在前面

做爲一名有追求的前端攻城獅,掌握幾個高級編程技巧是必須的~學會這些技巧,會讓咱們的開發工做事半功倍,讓咱們在工做中更加遊刃有餘,本篇文章將介紹三個小技巧:前端

  • 惰性載入函數的應用web

  • 函數柯里化的應用面試

  • compose調用函數扁平化編程

惰性載入函數

在咱們的代碼中,必定包含了大量的if語句,這些if語句的執行要花費一些時間。有一種狀況是這樣的,咱們第一次進入到if分支中,執行了這部分代碼,而後第二次一樣執行進入了同一分支,因此會再次執行此部分代碼。這樣的狀況下,代碼執行確定會慢一些,那麼若是咱們只讓代碼有一個if分支,代碼就會執行的快一些。若是讓代碼執行的更快一些,這就是惰性載入函數的應用,接下來,看一個DOM事件綁定的例子:redux

function emit(element, type, func) {
    if(element.addEventListener) {
        element.addEventListener(type, func, false)
    } else if(element.attachEvent) {  // IE六、七、8
        element.attachEvent("on" + type, func)
    } else {
        element["on" + type] = func;
    }
}
複製代碼

上面的例子中,判斷瀏覽器是否支持每一個方法,進入到不一樣分支,從而用不一樣的方式綁定事件。若是屢次綁定就會屢次進入同一if分支執行代碼,其實在同一瀏覽器環境下,屢次綁定,只須要判斷一次就能夠完成目的。因此,這時須要應用惰性載入函數數組

function emit(element, type, func) {
    if(element.addEventListener) {
        emit = function(element, type, func) {
            element.addEventListener(type, func, false)
        }
    } else if(element.attachEvent) {
        emit = function(element, type, func) {
            element.attachEvent("on" + type, func)
        }
    } else {
        emit = function(element, type, func) {
            element["on" + type] = func;
        }
    }
    emit(element, type, func);
}
複製代碼

優化後的代碼中,第一次執行進入到一個分支執行此部分代碼後,函數emit會被從新賦值,這樣就保證了屢次調用時只有一次if執行,代碼執行的就變得快了一些。瀏覽器

函數柯里化的應用

對於函數柯里化,最通俗的理解是:一個大函數返回一個小函數在《JavaScript高級程序設計》中,這樣解釋函數柯里化:與函數綁定緊密相關的主題時函數柯里化,它用於建立已經設置好了一個或多個參數的函數。函數柯里化的基本方法和函數綁定是同樣的:使用一個閉包返回一個函數。 咱們先來看一個例子:bash

function add(num1, num2) {
    return num1 + num2;
}

function curried(num2) {
    return add(5, num2);
}

console.log(curried(2));    // 7
複製代碼

這段代碼有兩個函數,add函數返回了兩個參數的和,curried函數返回了調用add函數後5和接收的參數的和。這個curried函數就是我們所說的一個大函數返回了一個小函數,可是它並非一個柯里化函數。閉包

柯里化函數一般動態建立:調用另外一個函數併爲它傳入要柯里化的函數和必要參數。 下面,寫一個柯里化函數的通用方式app

function curry(fn) {
    var args = Array.prototype.slice.call(arguments, 1);  // 獲取第一個參數以後的參數
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);                // 執行傳入函數fn
    }
}

function add(num1, num2) {
    return num1 + num2;
}

console.log(curry(add, 5, 12)());         // 17
console.log(curry(add, 6, 7)());          // 13
複製代碼

柯里化函數也可應用於構造出bind()函數中

/**
 * @params
 *      fn: 要執行的函數
 *      context: 須要改變的this指向
 */
function bind(fn, context) {
    context = context || window;                          // 若是沒傳context參數,就讓其爲window
    var args = Array.prototype.slice.call(arguments, 2);  // 獲取第二個參數以後的參數
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(context, finalArgs);
    }
}
複製代碼

來看一下效果:

var obj = {
    x: 1
}
function fn() {
    return this.x + 2;
}

btn.onclick = function() {
    console.log(bind(fn, obj)())      // 3
}
複製代碼

bind函數成功將this指向修改成了obj。可是咱們爲了調用時和Function原型上的bind同樣,咱們將本身寫的bind寫在原型裏

(function(proto) {
    function bind(context) {
        context = context || window;                          // 若是沒傳context參數,就讓其爲window
        var args = Array.prototype.slice.call(arguments, 1);  // 獲取第二個參數以後的參數
        return function() {
            var innerArgs = Array.prototype.slice.call(arguments);
            var finalArgs = args.concat(innerArgs);
            return fn.apply(context, finalArgs);
        }
    }
    proto.bind = bind;
})(Function.prototype)
複製代碼

再來看一下效果

var obj = {
    x: 1
}
function fn(y) {
    return this.x + y;
}

btn.onclick = function() {
    console.log(fn.bind(obj, 3)())     // 4
}
複製代碼

以上,就是應用函數柯里化手寫實現的bind。另外,有一些源碼也運用了函數柯里化,好比redux等。

下面,咱們來看一道面試題:實現add函數

add(1);       //1
add(1)(2);    //3
add(1)(2)(3); //6
add(1)(2,3);  //6
add(1,2)(3);  //6
add(1,2,3);   //6
複製代碼
function currying(anonymous, length) {
    return function add(...args) {         // 返回add,接收參數...args
        // 若是接收的參數長度大於函數參數的個數,則直接執行接收函數
        if (args.length >= length) {      
            return anonymous(...args);
        }
        // 不然,遞歸調用currying,返回新的anonymous函數和新的length
        return currying(anonymous.bind(null, ...args), length - args.length);
    }
}

// count參數爲傳進參數的總個數,每次調用此函數時都須要修改
let add = currying(function anonymous(...args) {
	return args.reduce((x, y) => x + y);
}, count);
複製代碼

咱們來看一下效果:

console.log(add(1));           // 修改count參數爲1, 輸出結果爲1
console.log(add(1)(2));        // 修改count參數爲2, 輸出結果爲3
console.log(add(1)(2)(3));     // 修改count參數爲3, 輸出結果爲6
console.log(add(1)(2,3));      // 修改count參數爲3, 輸出結果爲6
console.log(add(1,2)(3));      // 修改count參數爲3, 輸出結果爲6
console.log(add(1,2,3));       // 修改count參數爲3, 輸出結果爲6
console.log(add(5, 6, 7, 8))   // 修改count參數爲4, 輸出結果爲26
複製代碼

綜上,咱們完成了add函數的編寫,一道題的解法不必定只有一種,這道題也是同樣,還有不少解法,歡迎你們在評論區討論~

compose調用函數扁平化

在以前,咱們據說過數組扁平化,數組扁平化就是將多層次的數組變爲一層,那麼一樣的,調用函數扁平化就是將深層次的調用函數變爲一層,咱們來看一個例子:

var fn1 = function(x) {
    return x + 5;
}

var fn2 = function(x) {
    return x + 6;
}

var fn3 = function(x) {
    return x + 7;
}

console.log(fn3(fn2(fn1(5))));     // 23    
複製代碼

上面的例子中,將函數fn1的返回結果傳給fn2,再將fn2的返回結果傳給fn3,最終輸出fn3的結果。這樣層層嵌套的調用,看起來不是那麼舒服,用起來也沒有那麼方便,因此咱們想要實現這樣的compose函數:

compose(fn1, fn2, fn3)(5)   // 等價於fn3(fn2(fn1(5)))
複製代碼

下面開始實現:

function compose() {
    var funcs = Array.prototype.slice.call(arguments);
    return function() {
        var args = Array.prototype.slice.call(arguments);
        var len = funcs.length;
        if(len === 0){
            return args[0];
        } 
        if(len === 1) {
            return funcs[0](...args)
        }
        return funcs.reduce(function(x, y) {
            return typeof x === "function" ? y(x(...args)) : y(x)
        })
    }
    
}
複製代碼

來看一下效果:

console.log(compose(fn1, fn2, fn3)(5));   // 23
console.log(compose(fn1)(5));             // 10
console.log(compose()(5));                // 5
複製代碼

輸出的結果與咱們預期的結果是一致的,說明咱們封裝的compose函數是正確的。可是compose函數封裝的方式並非只有這一種,咱們來看一下redux中的compose函數

export default function compose(...funcs) {
    if (funcs.length === 0) {     // 當傳入的函數個數爲0時,直接返回參數
        return arg => arg
    }
    
    if (funcs.length === 1) {     // 當傳入函數個數爲1時,直接執行函數
        return funcs[0]
    }
    // 當傳入函數個數不爲0和1時,按函數傳入從後向前的順序依次執行函數
    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
複製代碼

這兩種方式均可以實現compose函數,第一種方式看起來很好理解~第二種方式看起來更簡單一些,這也須要咱們認真的思考一下它的執行邏輯,加強本身的思惟能力,讓本身也能夠寫出更好的代碼~

最後

高級編程技巧是開發中的必備,熟練掌握更是好處多多~以爲文章對你有幫助,能夠給本篇文章點個贊呀~若是文章有不正確的地方,還但願你們指出~咱們共同窗習,共同進步~

最後,分享一下個人公衆號「web前端日記」~你們能夠關注一波~

相關文章
相關標籤/搜索