javascript 原生 bind() 的 ES6 + ES5 實(shí)現(xiàn)

前言: 本來只是想寫一下簡(jiǎn)單的 bind 函數(shù)實(shí)現(xiàn),沒想到寫著寫著還能牽出 js 中繼承的知識(shí)恐锦,其實(shí)研究原生函數(shù)的實(shí)現(xiàn)總是能學(xué)到很多新東西

在實(shí)現(xiàn)之前呢,我們首先要知道bind是做什么的。JS MDN 給出的定義是The bind() methods creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.,簡(jiǎn)單來說就是bind()函數(shù)創(chuàng)建了一個(gè)新函數(shù)(原函數(shù)的拷貝)挠进,這個(gè)函數(shù)接受一個(gè)提供新的this上下文的參數(shù)色乾,以及之后任意可選的其他參數(shù)。當(dāng)這個(gè)新函數(shù)被調(diào)用時(shí)领突,它的this關(guān)鍵字指向第一個(gè)參數(shù)的新上下文暖璧。而第二個(gè)之后的參數(shù)會(huì)與原函數(shù)的參數(shù)組成新參數(shù)(原函數(shù)的參數(shù)在后),傳遞給函數(shù)攘须。

弄清楚這個(gè)之后,我們?cè)賮矸治隹纯匆趺醋觥?/p>

首先殴泰,調(diào)用 bind() 會(huì)返回一個(gè)閉包于宙,這個(gè)閉包中創(chuàng)建了一個(gè)新函數(shù),這個(gè)函數(shù)首先包含原函數(shù)的屬性與方法悍汛,并且這個(gè)函數(shù)的 this 值是傳給 bind() 函數(shù)第一個(gè)參數(shù)捞魁,所以自然而然我們想到用 call 或者 apply 來改變?cè)瘮?shù)的 this ,這里我們選擇 apply离咐, 理由是我們新函數(shù)的第一個(gè)之后的參數(shù)是由傳給 bind() 的第二個(gè)及之后的參數(shù)(代碼中的 formerArgs )再加上原函數(shù)的參數(shù)(代碼中的 laterArgs )構(gòu)成的谱俭,我們把它們拼接成一個(gè)數(shù)組就完事兒了~具體代碼如下:

Function.prototype.bind = function (ctx) {

// 保存原函數(shù)的 this 至 _this

var _this = this

// slice 使用了兩次,保存到變量中

// slice 主要用于類數(shù)組對(duì)象 arguments 的淺復(fù)制

var slice = Array.prototype.slice

// 傳給 bind() 函數(shù)的第二個(gè)至之后的參數(shù)宵蛀,從? arguments 的第二位開始

var formerArgs = slice.call(arguments, 1)

// bind() 本身就是一個(gè)函數(shù)昆著,返回

return function (){

// 傳給原函數(shù)的參數(shù)

let laterArgs = slice.call(arguments, 0)

// 返回一個(gè)函數(shù),這個(gè)函數(shù)調(diào)用了原函數(shù)术陶,并且 this 指向 bind 的第一個(gè)參數(shù)凑懂,

// 第二個(gè)參數(shù)由 formerArgs? 與 laterArgs組成

return _this.apply(ctx, formerArgs.concat(laterArgs))

}

}


ES6 實(shí)現(xiàn)

上面的代碼是基于 ES5 實(shí)現(xiàn)的,當(dāng)時(shí)對(duì)于不確定的參數(shù)的一般處理方法都是利用類數(shù)組對(duì)象arguments(其中包含了傳遞給函數(shù)的所有參數(shù))梧宫,也就免不了使用call或者是apply對(duì)其進(jìn)行數(shù)組操作接谨。代碼也就顯得比較冗長(zhǎng)。但是 ES6 不一樣了呀~我們有了不定參數(shù)這個(gè)神器塘匣。無論有無參數(shù)脓豪,有幾個(gè)參數(shù)都可以簡(jiǎn)單地處理。

不定參數(shù): 傳遞給函數(shù)的最后一個(gè)參數(shù)可以被標(biāo)記為不定參數(shù)忌卤,當(dāng)函數(shù)被調(diào)用時(shí)扫夜,不定參數(shù)之前的參數(shù)都可正常被填充,剩下的參數(shù)會(huì)被放進(jìn)一個(gè)數(shù)組中驰徊,并被賦值給不定參數(shù)历谍。而當(dāng)沒有剩下的參數(shù)時(shí),不定參數(shù)會(huì)是一個(gè)空數(shù)組辣垒,而不會(huì)被填充為undefined望侈。

同樣的功能,只要幾行代碼就可以實(shí)現(xiàn):

// formerArgs 為傳遞給 bind 函數(shù)的第二個(gè)到之后的參數(shù)

Function.prototype.bind = function (ctx, ...formerArgs) {

let _this = this

// laterArgs 為傳遞給原函數(shù)的參數(shù)

return (...laterArgs) => {

// bind 函數(shù)的不定參數(shù)在原函數(shù)參數(shù)之前勋桶,formerArgs 本身就是數(shù)組脱衙,可以直接調(diào)用數(shù)組的 concat 方法侥猬,無需借助 call 或 apply

return _this.apply(ctx, formerArgs.concat(laterArgs))

}

}


至此,我們就實(shí)現(xiàn)了簡(jiǎn)單的 bind() 函數(shù)的功能捐韩,接下來我們給它做點(diǎn)優(yōu)化退唠。


優(yōu)化 UPUPUP..

·?當(dāng) Function 的原型鏈上沒有 bind 函數(shù)時(shí),才加上此函數(shù)

·if (!Function.prototype.bind) {

// add bind() to Function.prototype

}

·?只有函數(shù)才能調(diào)用 bind 函數(shù)荤胁,其他的對(duì)象不行瞧预。即判斷 this 是否為函數(shù)。

if (typeof this !== 'function') {

// throw NOT_A_FUNCTION error

}

·?壓軸戲: 關(guān)于繼承

我們上面的代碼使用了借用 apply 繼承的方式仅政。用了 apply 來改變 this 的指向垢油,繼承了原函數(shù)的基本屬性和引用屬性,并且保留了可傳參優(yōu)點(diǎn)圆丹,但是新函數(shù)無法實(shí)現(xiàn)函數(shù)復(fù)用滩愁,每個(gè)新函數(shù)都會(huì)復(fù)制出一份新的原函數(shù)的函數(shù),并且也無法繼承到原函數(shù)通過 prototype 方式定義的方法或?qū)傩浴?/p>

為解決以上問題辫封,我們選用組合繼承方式硝枉,在使用 apply 繼承的基礎(chǔ)上,加上了原型鏈繼承倦微。

所以我們可以這么改妻味。

if(!Function.prototype.binds) {

Function.prototype.binds =function(ctx){

if(typeofthis!=='function') {

thrownewTypeError("NOT_A_FUNCTION -- this is not callable")

}

var_this =this

varslice =Array.prototype.slice

varformerArgs = slice.call(arguments,1)

// 定義一個(gè)中間函數(shù),用于作為繼承的中間值

varfun =function(){}

varfBound =function(){

letlaterArgs = slice.call(arguments,0)

return_this.apply(ctx, formerArgs.concat(laterArgs))

}

// 先讓 fun 的原型方法指向 _this 即原函數(shù)的原型方法欣福,繼承 _this 的屬性

fun.prototype = _this.prototype

// 再將 fBound 即要返回的新函數(shù)的原型方法指向 fun 的實(shí)例化對(duì)象

// 這樣弧可,既能讓 fBound 繼承 _this 的屬性,在修改其原型鏈時(shí)劣欢,又不會(huì)影響到 _this 的原型鏈

fBound.prototype =newfun()

returnfBound

}

}

在上面的代碼中棕诵,我們引入了一個(gè)新的函數(shù) fun,用于繼承原函數(shù)的原型凿将,并通過 new 操作符實(shí)例化出它的實(shí)例對(duì)象校套,供 fBound 的原型繼承,至此牧抵,我們既讓新函數(shù)繼承了原函數(shù)的所有屬性與方法笛匙,又保證了不會(huì)因?yàn)槠鋵?duì)原型鏈的操作影響到原函數(shù)。用圖來表示應(yīng)該是下面這樣的:

這樣我們對(duì)新函數(shù)的 prototype 修改只會(huì)應(yīng)用在它自己身上犀变,而不會(huì)影響到原函數(shù)妹孙。

其他

MDN 中還提到,若是將 bind 綁定之后的函數(shù)當(dāng)作構(gòu)造函數(shù)获枝,通過 new 操作符使用蠢正,則不綁定傳入的 this姚淆,而是將 this 指向?qū)嵗鰜淼膶?duì)象去枷。所以我們最終的代碼為:

if (!Function.prototype.binds) {

Function.prototype.binds = function (ctx) {

if (typeof this !== 'function') {

throw new TypeError("NOT_A_FUNCTION -- this is not callable")

}

var _this = this

var slice = Array.prototype.slice

var formerArgs = slice.call(arguments, 1)

var fun = function () {}

var fBound = function (){

let laterArgs = slice.call(arguments, 0)

// 若通過 new 調(diào)用 bind() 之后的函數(shù)挑辆,則這時(shí)候 fBound 的 this 指向的是 fBound 實(shí)例宾尚,

// 而下面又定義了 fBound 是 fun 的派生類(其 prototype 指向 fun 的實(shí)例),

// 所以 this instanceof fun === true 雹舀,這時(shí) this 指向了 fBound 實(shí)例芦劣,不另外綁定!

return _this.apply(this instanceof fun ? this : ctx || this, formerArgs.concat(laterArgs))

}

fun.prototype = _this.prototype

fBound.prototype = new fun()

return fBound

}

}

打完收工~~歐耶 ( ?? ω ?? )y

P.S: ES7 中已經(jīng)淘汰了 .bind 的寫法说榆,而是使用兩個(gè)冒號(hào)的方式 :: 來代替虚吟,(要不要這么可愛!不過呢签财,人家還只是個(gè)提案而已串慰,到時(shí)候會(huì)不會(huì)被采用還不一定的呢

用法有兩種,第一種::對(duì)象.方法名

letobj = {

val:'value',

method:function(){

console.log(this.val)

}

};

// ::對(duì)象.方法名

// 等價(jià)于 obj.method.bind(obj)

// 將 method 的 this 綁定為 obj

// 這里的 method 必須是 obj 的方法

::obj.method;

// call

obj.method();// value

第二種荠卷,對(duì)象::方法名()

letobj = {

val:'value'

};

functionmethod(){

console.log(this.val);

}

// 對(duì)象::方法名()

// 等價(jià)于 method.call(obj) 或 method.apply(obj)

// 會(huì)直接調(diào)用并輸出 'value'

// obj::method();

// 對(duì)象::方法? (無括號(hào)

// 等價(jià)于 method.bind(obj)

// 不會(huì)自動(dòng)執(zhí)行模庐,必須賦值給 method 才能實(shí)現(xiàn)綁定

// 手動(dòng)調(diào)用 method 烛愧,輸出 vaue

method = obj::method;

method();

嘛~ 有說的不對(duì)的地方油宜,歡迎指正..

文章來源:https://jothy1023.github.io/2016/10/16/js-bind-es5-es6/

作者:四月(轉(zhuǎn)載已經(jīng)獲得作者授權(quán))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市怜姿,隨后出現(xiàn)的幾起案子慎冤,更是在濱河造成了極大的恐慌,老刑警劉巖沧卢,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚁堤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡但狭,警方通過查閱死者的電腦和手機(jī)披诗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來立磁,“玉大人呈队,你說我怎么就攤上這事〕纾” “怎么了宪摧?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)颅崩。 經(jīng)常有香客問我几于,道長(zhǎng),這世上最難降的妖魔是什么沿后? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任沿彭,我火速辦了婚禮,結(jié)果婚禮上尖滚,老公的妹妹穿的比我還像新娘膝蜈。我一直安慰自己锅移,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布饱搏。 她就那樣靜靜地躺著非剃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪推沸。 梳的紋絲不亂的頭發(fā)上备绽,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音鬓催,去河邊找鬼肺素。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宇驾,可吹牛的內(nèi)容都是我干的倍靡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼课舍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼塌西!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筝尾,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤捡需,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后筹淫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體站辉,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年损姜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饰剥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摧阅,死狀恐怖汰蓉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逸尖,我是刑警寧澤古沥,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站娇跟,受9級(jí)特大地震影響岩齿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜苞俘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一盹沈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦乞封、人聲如沸做裙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锚贱。三九已至,卻和暖如春关串,著一層夾襖步出監(jiān)牢的瞬間拧廊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工晋修, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吧碾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓墓卦,卻偏偏與公主長(zhǎng)得像倦春,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子落剪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

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