面試感悟,手寫bind,apply,call

前言

  前段時間有一場電話面試,記得裏面有個內容,問我http相關的東西,我內心暗爽,我把http圖解看了兩遍了,裏面的知識點啥的,基本都作了提煉和記錄,http?那還不是so easy?javascript

  balabala講了一堆,從http到https到http2,還補充了點http3的東西,巴拉巴拉講了一堆,信心滿滿.誰知道一個問題就問住我了.java

面試官: http2實現了多路複用,http1.x爲何不能多路複用?
我: 我說由於http1.x要按照順序來.
面試官: 沒錯,可是爲何http1.x要按照順序來?
我: 唔...這個不知道..
面試官: HTTP/1.1是基於文本分割解析的協議,也沒有序號,若是多路複用會致使順序錯亂,http2則用幀的方式,等於切成一塊塊,每一塊都有對應的序號,因此能夠實現多路複用.es6

  倒不是說這個問題怎麼樣怎麼樣,而是這個問題讓我意識到學習還不夠深刻,應該作到知其然並知其因此然.多思考多總結多提問,而後昨天恰好看到這樣的一道面試題就稍微總結一下面試

正文

是什麼?

apply,call,bind都是js給函數內置的一些api,調用他們能夠爲函數指定this的執行,同時也能夠傳參.api

怎麼用?

//apply 
func.apply(thisArg, [argsArray])

//call
fun.call(thisArg, arg1, arg2, ...)

//bind
const newFun = fun.bind(thisArg, arg1, arg2, ...)
newFun()
複製代碼

apply和call就是傳參不同,可是兩個都是會在調用的時候同時執行調用的函數,可是bind則會返回一個綁定了this的函數.數組

咱們還須要知道一個事情,就是this的指向.bash

this的指向

this的指向是在函數調用的時候肯定下來的,this的指向大體能夠分爲五種.app

1. 默認綁定

默認綁定通常發生在回調函數,函數直接調用;函數

function test() {
    //嚴格模式下是undefined
    //非嚴格模式下是window
    console.log(this);
}
setTimeout(function () {
    //setTimeout的比較特殊
    //嚴格模式和非嚴格模式下都是window
    console.log(this);
});

arr.forEach(function () {
    //嚴格模式下是undefined
    //非嚴格模式下是window
    console.log(this);
});
複製代碼

2. 隱式綁定

這個通俗點用一句話歸納就是誰調用就是指向誰學習

const obj = {
        name:'joy',
        getName(){
            console.log(this); //obj
            console.log(this.name); //joy
        }
    };
    obj.getName();
複製代碼

3. 顯示綁定call,apply,bind

const obj1 = {
    name: 'joy',
    getName() {
        console.log(this); 
        console.log(this.name); 
    }
};

const obj2 = {
    name: 'sam'
};

obj1.getName.call(obj2); //obj2 sam
obj1.getName.apply(obj2); //obj2 sam
const fn = obj1.getName.bind(obj2);
fn();//obj2 sam
複製代碼

4. new綁定

function Vehicle() {
    this.a = 2
    console.log(this);
}
new Vehicle(); //this指向Vehicle這個new出來的對象
複製代碼

5. es6的箭頭函數

  es6的箭頭函數比較特殊,箭頭函數this爲父做用域的this,不是調用時的this.要知道前四種方式,都是調用時肯定,也就是動態的,而箭頭函數的this指向是靜態的,聲明的時候就肯定了下來.比較符合js的詞法做用域吧

window.name = 'win';
const obj = {
    name: 'joy',
    age: 12,
    getName: () => {
        console.log(this); //其父做用域this是window,因此就是window
        console.log(this.name); //win 
    },
    getAge: function () {
        //經過obj.getAge調用,這裏面this是指向obj
        setTimeout(() => {
            //因此這裏this也是指向obj 因此結果是12
            console.log(this.age); 
        });
    }
};
obj.getName();
obj.getAge();
複製代碼

既然有5種this的綁定方式,那麼確定有優先級的前後

箭頭函數 -> new綁定 -> 顯示綁定call/bind/apply -> 隱式綁定 -> 默認綁定

這裏直接給出告終論,有興趣的小夥伴們能夠本身去驗證一下

實現apply

先來實現apply吧

  1. 先給Function原型上擴展個方法並接收2個參數,
Function.prototype.myApply = function (context, args) {

}
複製代碼
  1. 由於不傳context的話,this會指向window的,args也作一下容錯處理
Function.prototype.myApply = function (context, args) {
    //這裏默認不傳就是給window,也能夠用es6給參數設置默認參數
    context = context || window
    args = args ? args : []
}
複製代碼
  1. 須要回想一下綁定this的五種方式,如今要來給調用的函數綁定this了, 這裏默認綁定和new確定用不了,這裏就使用隱式綁定去實現顯式綁定了
Function.prototype.myApply = function (context, args) {
    //這裏默認不傳就是給window,也能夠用es6給參數設置默認參數
    context = context || window
    args = args ? args : []
    //給context新增一個獨一無二的屬性以避免覆蓋原有屬性
    const key = Symbol()
    context[key] = this
    //經過隱式綁定的方式調用函數
    context[key](...args)
}
複製代碼
  1. 最後一步要返回函數調用的返回值,而且把context上的屬性刪了纔不會形成影響
Function.prototype.myApply = function (context, args) {
    //這裏默認不傳就是給window,也能夠用es6給參數設置默認參數
    context = context || window
    args = args ? args : []
    //給context新增一個獨一無二的屬性以避免覆蓋原有屬性
    const key = Symbol()
    context[key] = this
    //經過隱式綁定的方式調用函數
    const result = context[key](...args)
    //刪除添加的屬性
    delete context[key]
    //返回函數調用的返回值
    return result
}
複製代碼

這樣一個簡單的apply就實現了,可能會有一些邊界問題和錯誤判斷須要完善,這裏就不作繼續優化了

既然apply實現了,那麼call一樣也很是簡單了,主要就是傳參不同

實現call

這裏就直接上代碼吧

//傳遞參數從一個數組變成逐個傳參了,不用...擴展運算符的也能夠用arguments代替
Function.prototype.myCall = function (context, ...args) {
    //這裏默認不傳就是給window,也能夠用es6給參數設置默認參數
    context = context || window
    args = args ? args : []
    //給context新增一個獨一無二的屬性以避免覆蓋原有屬性
    const key = Symbol()
    context[key] = this
    //經過隱式綁定的方式調用函數
    const result = context[key](...args)
    //刪除添加的屬性
    delete context[key]
    //返回函數調用的返回值
    return result
}
複製代碼

實現bind

bind和apply的區別在於,bind是返回一個綁定好的函數,apply是直接調用.其實想想實現也很簡單,就是返回一個函數,裏面執行了apply上述的操做而已.不過有一個須要判斷的點,由於返回新的函數,要考慮到使用new去調用,而且new的優先級比較高,因此須要判斷new的調用,還有一個特色就是bind調用的時候能夠傳參,調用以後生成的新的函數也能夠傳參,效果是同樣的,因此這一塊也要作處理 由於上面已經實現了apply,這裏就借用一下,實際上不借用就是把代碼copy過來

Function.prototype.myBind = function (context, ...args) {
    const fn = this
    args = args ? args : []
    return function newFn(...newFnArgs) {
        if (this instanceof newFn) {
            return new fn(...args, ...newFnArgs)
        }
        return fn.apply(context, [...args,...newFnArgs])
    }
}
複製代碼

以上全部實現能夠再加點判斷啊,例如調用的不是function就返回或者拋出錯誤啊之類的.我這裏就不處理了

以上就是apply,call,bind的實現了

結尾

  一直在求學的道路上迷迷糊糊,磕磕碰碰,但願能變得愈來愈棒,早日晉升大牛.多思考,多總結,多練習.養成終身學習的習慣.加油吧

相關文章
相關標籤/搜索