手動(dòng)實(shí)現(xiàn)Javascript中 apply,call,bind,new,節(jié)流防抖函數(shù),讓你一次性全搞懂。

一、寫在前面

趁著做畢設(shè)的空閑時(shí)間,打算梳理一下 Javascript 中幾個(gè)比較重要的函數(shù),并手動(dòng)來實(shí)現(xiàn)一下。可以加深自己對(duì)這些函數(shù)的理解,從而更方便在平時(shí)項(xiàng)目中使用,所謂知其然,亦知其所以然也。梳理了一下主要有以下幾個(gè)函數(shù),往后會(huì)不斷更新。

  • 節(jié)流,防抖函數(shù)
  • call,apply 和 bind 方法
  • new方法

二、節(jié)流防抖(throttle & debounce)

1)、什么是節(jié)流、防抖。

函數(shù)的節(jié)流和防抖是前端對(duì)于前端代碼性能優(yōu)化的一個(gè)重要的組成部分,節(jié)流,顧名思義,就是當(dāng)我們執(zhí)行一個(gè)函數(shù)多次時(shí),我們只讓它在一定時(shí)間段執(zhí)行一次,達(dá)到節(jié)制的目的。防抖就是一段時(shí)間內(nèi)一個(gè)函數(shù)多次執(zhí)行時(shí),我們讓它只執(zhí)行一次。這里引出別人的概念。

函數(shù)防抖(debounce):
在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí);典型的案例就是輸入搜索:輸入結(jié)束后n秒才進(jìn)行搜索請(qǐng)求,n秒內(nèi)又輸入的內(nèi)容,就重新計(jì)時(shí)。

函數(shù)節(jié)流(throttle):
規(guī)定在一個(gè)單位時(shí)間內(nèi),只能觸發(fā)一次函數(shù),如果這個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù),只有一次生效; 典型的案例就是鼠標(biāo)不斷點(diǎn)擊觸發(fā),規(guī)定在n秒內(nèi)多次點(diǎn)擊只有一次生效。

上面的概念其實(shí)也給了我們實(shí)現(xiàn)函數(shù)的思路,下面就來實(shí)現(xiàn)一下。

// 節(jié)流函數(shù)
function throttle(fn) {
    // 先設(shè)置標(biāo)識(shí)
    let flag = true
    return function () {
        // 觸發(fā)時(shí)間段還沒過去
        if (!flag) return
        // 改變狀態(tài)
        flag = false
        // 這里我沒有寫在setTimeout里,因?yàn)槲蚁朦c(diǎn)擊立即觸發(fā)。
        fn.apply(this, arguments)
        setTimeout(() => {
            // 一秒后變?yōu)閠rue標(biāo)識(shí)該函數(shù)可以再次觸發(fā)了
            flag = true
        }, 1000)
    }
}

// 防抖函數(shù)
function debounce(fn) {
    // 開始定時(shí)器為null
    let time = null
    return function () {
        // 清除之前的 time,永遠(yuǎn)保留這段時(shí)間內(nèi)的最后一個(gè)執(zhí)行
        clearTimeout(time)
        time = setTimeout(() => {
            fn.apply(this, arguments)
        }, 500)
    }
}

三、call, apply, bind 方法

首先,這三者都是用來調(diào)用函數(shù)的,我們 Javascript 中的函數(shù)除了可以直接調(diào)用外,還可以用這三個(gè)函數(shù)來手動(dòng)調(diào)用。那么問題就來了,為什么要手動(dòng)去調(diào)用呢?

其實(shí)當(dāng)我們調(diào)用一個(gè)函數(shù)時(shí)候,是在全局對(duì)象window 上調(diào)用的,這是時(shí)候 this 自然就指向 window ,所以調(diào)用結(jié)果是一樣的。關(guān)于 this指向的問題 后面會(huì)出一篇文章詳細(xì)講講。

// 瀏覽器中(因?yàn)樵趎ode中this默認(rèn)指向一個(gè)空對(duì)象)
let name = 'rinvay'
function getName() {
    console.log(this.name);
}
window.getName() // rinvay
this.getName() // rinvay

那想想當(dāng)我們需要在另一個(gè)對(duì)象而不是window對(duì)象上調(diào)用這個(gè)函數(shù),是不是就沒辦法了,這個(gè)時(shí)候就需要我們的這三個(gè)函數(shù)出場(chǎng)了。下面來介紹這三個(gè)函數(shù)的基本用法。首先介紹 apllycall 的區(qū)別,這兩個(gè)函數(shù)都是立即調(diào)用函數(shù) 第一個(gè)參數(shù)就是函數(shù)this的指向,其余參數(shù)在函數(shù)執(zhí)行的時(shí)候傳給函數(shù)。兩個(gè)函數(shù)唯一的不同就是其余參數(shù)的傳遞形式,call個(gè)是一個(gè)個(gè)傳,apply是放在一個(gè)數(shù)組中

let person = {
    name: '樹街貓'
}
let name = 'rinvay'

function getInfo(age, height) {
    console.log(this.name, age, height);
}
getInfo(21, 178) // rinvay 21 178

// call(this,arg1,arg2,...) 函數(shù)
getInfo.call(person, 21, 178) // 樹街貓 21 178

// apply(this,[arg1,arg2,...]) 函數(shù)
getInfo.apply(person, [21, 178]) // 樹街貓 21 178

bind這個(gè)函數(shù)比較特殊,它不同于上面兩個(gè)。她改變了this指向之后不會(huì)立即執(zhí)行該函數(shù)而是返回一個(gè)新的綁定函數(shù)

let person = {
    name: '樹街貓'
}
let getInfoBind = getInfo.bind(person, 21, 178)
// 得到后執(zhí)行
getInfoBind() // 樹街貓 21 178

這樣三個(gè)函數(shù)的基本使用搞清楚了,還有就是也可以不傳參,不改變this指向,就和直接調(diào)用函數(shù)的效果一樣。

搞清楚了用法,我們趁熱打鐵來實(shí)現(xiàn)一下。先寫步驟,再寫代碼。

1)call 和 apply

  1. 將函數(shù)的 this指向 指向 obj
  2. 傳入?yún)?shù)并執(zhí)行函數(shù)

實(shí)現(xiàn)代碼

// call 函數(shù)實(shí)現(xiàn)
Function.prototype._call = function (obj,...rest) {
    if (typeof this !== 'function') return
    // 這里this指向調(diào)用 _call 的函數(shù),也就是函數(shù)本身。所以將函數(shù)賦值給對(duì)象的fn屬性。對(duì)象調(diào)用函數(shù)時(shí)候函數(shù)的this自然指向?qū)ο蟆?    // 這里用了Symbol.for API 大概就是在沒有fn的時(shí)候運(yùn)行Symbol.for('fn')會(huì)創(chuàng)建一個(gè)fn放入注冊(cè)表,有了之后會(huì)返回這個(gè)值。
    obj[Symbol.for('fn')] = this;
    // 取到fn這個(gè)值,對(duì)象調(diào)用函數(shù)。
    obj[Symbol.for('fn')](...rest);
    // 這里需要?jiǎng)h除這個(gè)屬性,否則越來越多。
    delete obj[Symbol.for('fn')]
}

// apply 函數(shù) 其實(shí)只是參數(shù)的處理變了而已,
Function.prototype._apply = function (obj) {
    if (typeof this !== 'function') return
    obj[Symbol.for('fn')] = this;
    obj[Symbol.for('fn')](...arguments[1]);
    delete obj[Symbol.for('fn')]
}

2)bind函數(shù)

下面來說說bind函數(shù),這個(gè)和上面的區(qū)別就是它在綁定后不執(zhí)行,返回一個(gè)綁定函數(shù)

  1. 將函數(shù)的 this指向 指向 obj
  2. 傳入?yún)?shù)并返回函數(shù)

但是這里bind有點(diǎn)不一樣,它支持函數(shù)柯里化,就是如果有兩個(gè)參數(shù),可以先傳一個(gè)參數(shù),執(zhí)行返回函數(shù)的時(shí)候再傳入這里需要除了剩余參數(shù) 。

Function.prototype._bind = function (obj) {
    if (typeof this !== 'function') {
        return;
    }
    var _this = this;
    //從第二個(gè)參數(shù)截取
    var args = Array.prototype.slice.call(arguments, 1)
    return function () {
        // 剩余參數(shù)的處理合并再傳入
        return _this.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
    }
};

這里三個(gè)函數(shù)都實(shí)現(xiàn)完畢了,當(dāng)然可能有點(diǎn)小瑕疵,希望發(fā)現(xiàn)問題的評(píng)論區(qū)留言。

四、new方法

平時(shí)我們都是怎么用的 new 呢,無非就是new 一個(gè)類嘛,我們 Javascript 叫做構(gòu)造函數(shù),其實(shí)就是普通函數(shù)。我們ES中的calss關(guān)鍵字其實(shí)只是語法糖,實(shí)質(zhì)還是一個(gè)函數(shù),可以打印類型看一看。下面演示一下

function Person(name, age) {
    this.name = name
    this.age = age
}

let p1 = new Person('rinvay', 21)
console.log(p1.name); // rinvay

感覺它做的事情就是創(chuàng)建了一個(gè)新的對(duì)象把Person里面的屬性給了這個(gè)對(duì)象,再返回。哈哈哈,這只是感覺,其實(shí)過程是這樣的。

  1. 創(chuàng)建一個(gè)空對(duì)象,將它的引用賦給 this,繼承函數(shù)的原型。
  2. 通過 this 將屬性和方法添加至這個(gè)對(duì)象
  3. 最后返回 this 指向的新對(duì)象,也就是實(shí)例(如果沒有手動(dòng)返回其他的對(duì)象)

這里只描述了如何將構(gòu)造器中的屬性方法給實(shí)例,那么原型上的實(shí)例呢?這里我們就用以構(gòu)造器原型去完成創(chuàng)建第一步那個(gè)空對(duì)象,下面代碼實(shí)現(xiàn)一下。這里給出實(shí)現(xiàn)步驟

  1. 以構(gòu)造器的prototype屬性為原型,創(chuàng)建新對(duì)象;
  2. 將this(也就是上一句中的新對(duì)象)和調(diào)用參數(shù)傳給構(gòu)造器,執(zhí)行;
  3. 如果構(gòu)造器沒有手動(dòng)返回對(duì)象,則返回第一步創(chuàng)建的新對(duì)象,如果有,則舍棄掉第一步創(chuàng)建的新對(duì)象,返回手動(dòng)return的對(duì)象。
function _new(constructor, ...rest) {
    let obj = Object.create(constructor.prototype)
    let res = constructor.apply(obj, rest)
    return typeof res === 'object' ? res : obj
}

好了這里常用的api函數(shù)就差不多實(shí)現(xiàn)完成了,凌晨了也該睡覺了。

喜歡的話點(diǎn)個(gè)關(guān)注吧,你的點(diǎn)贊關(guān)注是我最大的動(dòng)力。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容