從一道面試題,到“我可能看了假源碼”

今天想談?wù)勔坏狼岸嗣嬖囶}钧大,我做面試官的時候經(jīng)常喜歡用它來考察面試者的基礎(chǔ)是否扎實翰撑,以及邏輯、思維能力和臨場表現(xiàn)拓型,題目是:“模擬實現(xiàn)ES5中原生bind函數(shù)”额嘿。
也許這道題目已經(jīng)不再新鮮瘸恼,部分讀者也會有思路來解答。社區(qū)上關(guān)于原生bind的研究也很多册养,比如用它來實現(xiàn)函數(shù)“顆炼В化(currying)”,
或者“反顆燎蚶梗化(uncurrying)”靠闭。
但是,我確信有很多細節(jié)是您注意不到的坎炼,也是社區(qū)上關(guān)于這個話題普遍缺失的愧膀。
這篇文章面向有較牢固JS基礎(chǔ)的讀者,會從最基本的理解入手谣光,一直到分析ES5-shim實現(xiàn)bind源碼檩淋,相信不同程度的讀者都能有所收獲。
也歡迎大家與我討論萄金。

bind函數(shù)究竟是什么?

在開啟我們的探索之前蟀悦,有必要先明確一下bind到底實現(xiàn)了什么:
1)簡單粗暴地來說,bind是用于綁定this指向的氧敢。(如果你還不了解JS中this的指向問題日戈,以及執(zhí)行環(huán)境上下文的奧秘,這篇文章暫時就不太適合閱讀)孙乖。

2)bind使用語法:

fun.bind(thisArg[, arg1[, arg2[, ...]]])

bind方法會創(chuàng)建一個新函數(shù)浙炼。當(dāng)這個新函數(shù)被調(diào)用時,bind的第一個參數(shù)將作為它運行時的this唯袄,之后的一序列參數(shù)將會在傳遞的實參前傳入作為它的參數(shù)弯屈。本文不打算科普基礎(chǔ),如果您還不清楚越妈,請參考MDN內(nèi)容季俩。

3)bind返回的綁定函數(shù)也能使用new操作符創(chuàng)建對象:這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。提供的this值被忽略梅掠,同時調(diào)用時的參數(shù)被提供給模擬函數(shù)。

初級實現(xiàn)

了解了以上內(nèi)容店归,我們來實現(xiàn)一個初級的bind函數(shù)Polyfill:

Function.prototype.bind = function (context) {
    var me = this;
    var argsArray = Array.prototype.slice.call(arguments);
    return function () {
        return me.apply(context, argsArray.slice(1))
    }
}

這是一般“表現(xiàn)良好”的面試者所能給我提供的答案阎抒,如果面試者能寫到這里,我會給他60分消痛。
我們先簡要解讀一下:
基本原理是使用apply進行模擬且叁。函數(shù)體內(nèi)的this,就是需要綁定this的實例函數(shù)秩伞,或者說是原函數(shù)逞带。最后我們使用apply來進行參數(shù)(context)綁定欺矫,并返回。
同時展氓,將第一個參數(shù)(context)以外的其他參數(shù)穆趴,作為提供給原函數(shù)的預(yù)設(shè)參數(shù),這也是基本的“顆劣龉化(curring)”基礎(chǔ)未妹。

初級實現(xiàn)的加分項

上面的實現(xiàn)(包括后面的實現(xiàn)),其實是一個典型的“Monkey patching(猴子補丁)”空入,即“給內(nèi)置對象擴展方法”络它。所以,如果面試者能進行一下“嗅探”歪赢,進行兼容處理化戳,就是錦上添花了,我會給10分的附加分埋凯。

Function.prototype.bind = Function.prototype.bind || function (context) {
    ...
}

顆恋懵ィ化(curring)實現(xiàn)

上述的實現(xiàn)方式中,我們返回的參數(shù)列表里包含:atgsArray.slice(1)递鹉,他的問題在于存在預(yù)置參數(shù)功能丟失的現(xiàn)象盟步。
想象我們返回的綁定函數(shù)中,如果想實現(xiàn)預(yù)設(shè)傳參(就像bind所實現(xiàn)的那樣)躏结,就面臨尷尬的局面却盘。真正實現(xiàn)顆粒化的“完美方式”是:

Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function () {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.apply(context, finalArgs);
    }
}

如果面試者能夠給出這樣的答案媳拴,我內(nèi)心獨白會是“不錯啊黄橘,貌似你就是我要找的那個TA~”。但是屈溉,我們注意在上邊bind方法介紹的第三條提到:bind返回的函數(shù)如果作為構(gòu)造函數(shù)塞关,搭配new關(guān)鍵字出現(xiàn)的話,我們的綁定this就需要“被忽略”子巾。

構(gòu)造函數(shù)場景下的兼容

有了上邊的講解帆赢,不難理解需要兼容構(gòu)造函數(shù)場景的實現(xiàn):

Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var F = function () {};
    F.prototype = this.prototype;
    var bound = function () {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.apply(this instanceof F ? this : context || this, finalArgs);
    }
    bound.prototype = new F();
    return bound;
}

如果面試者能夠?qū)懗蛇@樣,我?guī)缀跻o滿分线梗,會幫忙聯(lián)系HR談薪酬了椰于。當(dāng)然,還可以做的更加嚴(yán)謹(jǐn)仪搔。

更嚴(yán)謹(jǐn)?shù)淖龇?/h2>

我們需要調(diào)用bind方法的一定要是一個函數(shù)瘾婿,所以可以在函數(shù)體內(nèi)做一個判斷:

if (typeof this !== "function") {
  throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}

做到所有這一切,我會很開心的給滿分。其實MDN上有個自己實現(xiàn)的polyfill偏陪,就是如此實現(xiàn)的抢呆。
另外,《JavaScript Web Application》一書中對bind()的實現(xiàn)笛谦,也是如此抱虐。

故事貌似要畫上休止符了——

一切還沒完,高潮即將上演

如果你認(rèn)為這樣就完了揪罕,其實我會告訴你說梯码,高潮才剛要上演。曾經(jīng)的我也認(rèn)為上述方法已經(jīng)比較完美了好啰,直到我看了es5-shim源碼(已適當(dāng)刪減):

bind: function bind(that) {
    var target = this;
    if (!isCallable(target)) {
        throw new TypeError('Function.prototype.bind called on incompatible ' + target);
    }
    var args = array_slice.call(arguments, 1);
    var bound;
    var binder = function () {
        if (this instanceof bound) {
            var result = target.apply(
                this,
                array_concat.call(args, array_slice.call(arguments))
            );
            if ($Object(result) === result) {
                return result;
            }
            return this;
        } else {
            return target.apply(
                that,
                array_concat.call(args, array_slice.call(arguments))
            );
        }
    };
    var boundLength = max(0, target.length - args.length);
    var boundArgs = [];
    for (var i = 0; i < boundLength; i++) {
        array_push.call(boundArgs, '$' + i);
    }
    bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);

    if (target.prototype) {
        Empty.prototype = target.prototype;
        bound.prototype = new Empty();
        Empty.prototype = null;
    }
    return bound;
}

看到了這樣的實現(xiàn)轩娶,心中的困惑太多,不禁覺得我看了“假源碼”框往。但是仔細分析一下鳄抒,剩下就是一個大寫的 。椰弊。许溅。服!
這里先留一個懸念秉版,不進行源碼分析贤重。讀者可以自己先研究一下。如果想看源碼分析清焕,點擊這篇文章的后續(xù)-源碼解讀并蝗。

總結(jié)

通過比對幾版的polyfill實現(xiàn),對于bind應(yīng)該有了比較深刻的認(rèn)識秸妥。作為這道面試題的考察點滚停,肯定不是讓面試者實現(xiàn)低版本瀏覽器的向下兼容,因為我們有了es5-shim,es5-sham處理兼容性問題粥惧,并且無腦兼容我也認(rèn)為是歷史的倒退键畴。
回到這道題考查點上,他有效的考察了很重要的知識點:比如this的指向突雪,JS的閉包起惕,原型原型鏈功力,設(shè)計程序上的兼容考慮等等硬素質(zhì)咏删。
在前端技術(shù)快速發(fā)展迭代的今天疤祭,在“前端市場是否飽和”“前端求職火爆異常”“前端入門簡單饵婆,錢多人傻”的浮躁環(huán)境下,對基礎(chǔ)內(nèi)功的修煉就顯得尤為重要,這也是你在前端路上能走多遠侨核、走多久的關(guān)鍵草穆。

PS:百度知識搜索部大前端繼續(xù)招兵買馬,有意向者火速聯(lián)系搓译。悲柱。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末些己,一起剝皮案震驚了整個濱河市豌鸡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌段标,老刑警劉巖涯冠,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異逼庞,居然都是意外死亡蛇更,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門赛糟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來派任,“玉大人,你說我怎么就攤上這事璧南≌乒洌” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵司倚,是天一觀的道長豆混。 經(jīng)常有香客問我,道長对湃,這世上最難降的妖魔是什么崖叫? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮拍柒,結(jié)果婚禮上心傀,老公的妹妹穿的比我還像新娘。我一直安慰自己拆讯,他們只是感情好脂男,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著种呐,像睡著了一般宰翅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上爽室,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天汁讼,我揣著相機與錄音,去河邊找鬼。 笑死嘿架,一個胖子當(dāng)著我的面吹牛瓶珊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耸彪,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼伞芹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蝉娜?” 一聲冷哼從身側(cè)響起唱较,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎召川,沒想到半個月后南缓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡扮宠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年西乖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坛增。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡获雕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出收捣,到底是詐尸還是另有隱情届案,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布罢艾,位于F島的核電站楣颠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏咐蚯。R本人自食惡果不足惜童漩,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望春锋。 院中可真熱鬧矫膨,春花似錦、人聲如沸期奔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呐萌。三九已至馁痴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肺孤,已是汗流浹背罗晕。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留攀例,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓粤铭,卻偏偏與公主長得像杂靶,于是被迫代替她去往敵國和親梆惯。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容