美麗的閉包,在js中實現函式過載

鮑康霖發表於2018-06-16

引言

最近在js的學習中,看到了函式過載的問題,一開始,只看到了實現程式碼,看著程式碼冥思苦想了半個小時,總算是理清了其實現的原理,也為其實現的巧妙感到讚歎,也是在自己搞懂原理之後,去網路上搜尋了下,才知道,這個實現方法是jQuery作者John Resig在《JavaScript忍者祕籍》中對函式過載的實現,設計十分的巧妙,寫下此文,給大家做一個分享

什麼是函式的過載

過載,簡單說,就是函式或者方法有相同的名稱,但是引數列表不相同的情形,這樣的同名不同引數的函式或者方法之間,互相稱之為過載函式或者方法。

先來看看效果

 function addMethod (obj, name, fn) {
        var old = obj[name];
        obj[name] = function () {
            if (fn.length === arguments.length) {
                return fn.apply(this, arguments)
            } else if (typeof old === 'function') {
                return old.apply(this, arguments)
            }
        }
    }

    var person = {userName: 'bear鮑的小小熊'}

    addMethod(person, 'show', function () {
        console.log(this.userName + '---->' + 'show1')
    })
    addMethod(person, 'show', function (str) {
        console.log(this.userName + '---->' + str)
    })
    addMethod(person, 'show', function (a, b) {
        console.log(this.userName + '---->' + (a + b))
    })
    person.show()  
    person.show('bkl')
    person.show(10, 20)
複製程式碼

輸出的結果

    //bear鮑的小小熊---->show1
    //bear鮑的小小熊---->bkl
    //bear鮑的小小熊---->30
複製程式碼

我們給一個物件新增了一個show方法,這個show方法,每次傳入的引數不一樣,它進行的處理也是不一樣的

為了後文更好理解,這裡先對fn.length這個大家可能陌生的屬性做個解釋,先看下面的程式碼

function fn(a,b,c) {}
fn.length // 3
function fn(a,b,c,d) {}
fn.length // 4
複製程式碼

fn.legnth,是函式fn在定義時,形參的個數.好了,讓我們繼續往下講吧

這個addMethod函式,簡單的來說,就是給一個物件新增一個指定name的方法fn(後文中為了方便大家理解,我們就以這個例子中的show來指代這個name吧),他利用了閉包,通過變數old,將每次傳進來的fn給儲存起來,我們每次呼叫這個show方法,根據傳入的引數的不同,我們的程式碼可能多次通過old來找到之前傳入的fn函式

下面我們來對這個方法進行解析為了看的更加直觀,我們對之前的addMethod的函式做一點小小的改造,其實就是加入了一個console.log(),可以方便我們理解,函式的執行過程

    function addMethod (obj, name, fn) {
        var old = obj[name];
        obj[name] = function () {
            console.log(1) //列印1
            if(fn.length === arguments.length){
                console.log(2) // 列印2
                return fn.apply(this,arguments);
            }else if(typeof old === 'function'){
                console.log(3) // 列印3
                return old.apply(this,arguments);
            }
        }
    }
    addMethod(person, 'show', function () {
        console.log(this.userName + '---->' + 'show1')
    })
    addMethod(person, 'show', function (str) {
        console.log(this.userName + '---->' + str)
    })
    addMethod(person, 'show', function (a, b) {
        console.log(this.userName + '---->' + (a + b))
    })

複製程式碼

下面,我們看一下person.show方法,在不傳參,傳了一個引數,與傳了兩個引數時,函式執行的具體過程,

  • person.show(10, 20)
1
2
bear鮑的小小熊---->30
複製程式碼

可見,傳入兩個引數的時候,只列印了一個1,一個2,就將對應的執行函式執行了.其實這個時候person.show函式的作用域內 fn為下面這個函式

    function (a, b) {
        console.log(this.userName + '---->' + (a + b))
    }
複製程式碼
  • person.show('bkl')
1
3
1
2
bear鮑的小小熊---->bkl
複製程式碼

傳入一個引數的時候執行結果為 1 --> 3 --> 1 --> 2 --> 處理後的結果 在這個過程中由於執行person.show方法時,fn.length === 2,而我們傳入的引數為1個,那麼函式會執行到

return old.apply(this,arguments);
複製程式碼

這個時候的old是什麼呢?這個時候的old其實是在下面這個函式執行之前的person.show方法

    addMethod(person, 'show', function (a, b) {
        console.log(this.userName + '---->' + (a + b))
    })
複製程式碼

執行之後person.show函式作用域內的fn函式也就是下面這個方法

    function (str) {
        console.log(this.userName + '---->' + str)
    }
複製程式碼
  • person.show()
1
3
1
3
1
2
bear鮑的小小熊---->show1
複製程式碼

上面的也是同理,根據這個輸出結果,不難看出,當沒有傳遞引數時,通過閉包的old變數,我們可以一路向上找到這個方法.

    function () {
        console.log(this.userName + '---->' + 'show1')
    }
複製程式碼

結語

就這樣,我們通過閉包中的old變數,將對不傳引數,傳了一個引數和傳了兩個引數進行區別處理的方法給串聯了起來.實現了js的過載.再次感嘆一下,這個方法真的很巧妙.真是漂亮又充滿魅力的程式碼

如果覺得還可以,請點贊鼓勵一下,謝謝!

相關文章