日積跬步战得,apply/call/bind 自我實現(xiàn)

call/apply/bind 日常編碼中被開發(fā)者用來實現(xiàn) “對象冒充”喷斋,也即 “顯示綁定 this“。

面試題:“call/apply/bind源碼實現(xiàn)”茧彤,事實上是對 JavaScript 基礎(chǔ)知識的一個綜合考核骡显。

相關(guān)知識點:

  1. 作用域;
  2. this 指向曾掂;
  3. 函數(shù)柯里化惫谤;
  4. 原型與原型鏈;

call/apply/bind 的區(qū)別

  1. 三者都可用于顯示綁定 this;
  2. call/apply 的區(qū)別方式在于參數(shù)傳遞方式的不同珠洗;
    • fn.call(obj, arg1, arg2, ...)溜歪, 傳參數(shù)列表,以逗號隔開许蓖;
    • fn.apply(obj, [arg1, arg2, ...])蝴猪, 傳參數(shù)數(shù)組调衰;
  3. bind 返回的是一個待執(zhí)行函數(shù),是函數(shù)柯里化的應(yīng)用自阱,而 call/apply 則是立即執(zhí)行函數(shù)

思路初探

Function.prototype.myCall = function(context) {
    // 原型中 this 指向的是實例對象嚎莉,所以這里指向 [Function: bar]
    console.log(this);  // [Function: bar]
    // 在傳入的上下文對象中,創(chuàng)建一個屬性沛豌,值指向方法 bar
    context.fn = this;  // foo.fn = [Function: bar]
    // 調(diào)用這個方法趋箩,此時調(diào)用者是 foo,this 指向 foo
    context.fn();
    // 執(zhí)行后刪除它加派,僅使用一次叫确,避免該屬性被其它地方使用(遍歷)
    delete context.fn;
};

let foo = {
    value: 2
};

function bar() {
    console.log(this.value);
}
// bar 函數(shù)的聲明等同于:var bar = new Function("console.log(this.value)");

bar.call(foo);   // 2;

call 的源碼實現(xiàn)

初步思路有個大概,剩下的就是完善代碼芍锦。

// ES6 版本
Function.prototype.myCall = function(context, ...params) {
  // ES6 函數(shù) Rest 參數(shù)竹勉,使其可指定一個對象,接收函數(shù)的剩余參數(shù)娄琉,合成數(shù)組
  if (typeof context === 'object') {
    context = context || window;
  } else {
    context = Object.create(null);
  }

  // 用 Symbol 來作屬性 key 值饶米,保持唯一性,避免沖突
  let fn = Symbol();
  context[fn] = this;
  // 將參數(shù)數(shù)組展開车胡,作為多個參數(shù)傳入
  const result = context[fn](...params);
  // 刪除避免永久存在
  delete(context[fn]);
  // 函數(shù)可以有返回值
  return result;            
}

// 測試
var mine = {
    name: '以樂之名'
}

var person = {
  name: '無名氏',
  sayHi: function(msg) {
    console.log('我的名字:' + this.name + '檬输,', msg);
  }
}

person.sayHi.myCall(mine, '很高興認(rèn)識你!');  
// 我的名字:以樂之名匈棘,很高興認(rèn)識你丧慈!

知識點補(bǔ)充:

  1. ES6 新的原始數(shù)據(jù)類型 Symbol,表示獨一無二的值;
  2. Object.create(null) 創(chuàng)建一個空對象
// 創(chuàng)建一個空對象的方式

// eg.A 
let emptyObj = {};

// eg.B
let emptyObj = new Object();

// eg.C
let emptyObj = Object.create(null);

使用 Object.create(null) 創(chuàng)建的空對象主卫,不會受到原型鏈的干擾逃默。原型鏈終端指向 null,不會有構(gòu)造函數(shù)簇搅,也不會有 toString完域、 hasOwnPropertyvalueOf 等屬性瘩将,這些屬性來自 Object.prototype吟税。有原型鏈基礎(chǔ)的伙伴們,應(yīng)該都知道姿现,所有普通對象的原型鏈都會指向 Object.prototype肠仪。

所以 Object.create(null) 創(chuàng)建的空對象比其它兩種方式,更干凈备典,不會有 Object 原型鏈上的屬性异旧。

ES5 版本:

  1. 自行處理參數(shù);
  2. 自實現(xiàn) Symobo
// ES5 版本

// 模擬Symbol
function getSymbol(obj) {
  var uniqAttr = '00' + Math.random();
  if (obj.hasOwnProperty(uniqAttr)) {
    // 如果已存在提佣,則遞歸自調(diào)用函數(shù)
    arguments.callee(obj);
  } else {
    return uniqAttr;
  }
}

Function.prototype.myCall = function() {
  var args = arguments;
  if (!args.length) return;

  var context = [].shift.apply(args);
  context = context || window;

  var fn = getSymbol(context);
  context[fn] = this;

  // 無其它參數(shù)傳入
  if (!arguments.length) {
    return context[fn];
  }

  var param = args[i];
  // 類型判斷吮蛹,不然 eval 運行會出錯
  var paramType = typeof param;
  switch(paramType) {
    case 'string':
      param = '"' + param + '"'
    break;
    case 'object':
      param = JSON.stringify(param);
    break;
  } 

  fnStr += i == args.length - 1 ? param : param + ',';

  // 借助 eval 執(zhí)行
  var result = eval(fnStr);
  delete context[fn];
  return result;
}

// 測試
var mine = {
    name: '以樂之名'
}

var person = {
  name: '無名氏',
  sayHi: function(msg) {
    console.log('我的名字:' + this.name + '荤崇,', msg);
  }
}

person.sayHi.myCall(mine, '很高興認(rèn)識你!');  
// 我的名字:以樂之名潮针,很高興認(rèn)識术荤!

apply 的源碼實現(xiàn)

call 的源碼實現(xiàn),那么 apply 就簡單然低,兩者只是傳遞參數(shù)方式不同而已喜每。

Function.prototype.myApply = function(context, params) {
    // apply 與 call 的區(qū)別务唐,第二個參數(shù)是數(shù)組雳攘,且不會有第三個參數(shù)
    if (typeof context === 'object') {
        context = context || window;
    } else {
        context = Object.create(null);
    }

    let fn = Symbol();
    context[fn] = this;
    const result context[fn](...params);
    delete context[fn];
    return result;
}

bind 的源碼實現(xiàn)

  1. bindcall/apply 的區(qū)別就是返回的是一個待執(zhí)行的函數(shù),而不是函數(shù)的執(zhí)行結(jié)果;
  2. bind 返回的函數(shù)作為構(gòu)造函數(shù)與 new 一起使用枫笛,綁定的 this 需要被忽略;

調(diào)用綁定函數(shù)時作為this參數(shù)傳遞給目標(biāo)函數(shù)的值吨灭。 如果使用new運算符構(gòu)造綁定函數(shù),則忽略該值刑巧。 —— MDN

Function.prototype.bind = function(context, ...initArgs) {
    // bind 調(diào)用的方法一定要是一個函數(shù)
    if (typeof this !== 'function') {
      throw new TypeError('not a function');
    }
    let self = this;  
    let F = function() {};
    F.prototype = this.prototype;
    let bound = function(...finnalyArgs) {
      // 將前后參數(shù)合并傳入
      return self.call(this instanceof F ? this : context || this, ...initArgs, ...finnalyArgs);
    }
    bound.prototype = new F();
    return bound;
}

不少伙伴還會遇到這樣的追問喧兄,不使用 call/apply,如何實現(xiàn) bind 啊楚?

騷年先別慌吠冤,不用 call/apply,不就是相當(dāng)于把 call/apply 換成對應(yīng)的自我實現(xiàn)方法恭理,算是偷懶取個巧吧拯辙。

本篇 call/apply/bind 源碼實現(xiàn),算是對之前文章系列知識點的一次加深鞏固颜价。

“心中有碼涯保,前路莫慌≈苈祝”


參考文檔:

更多前端基石搭建夕春,盡在 Github,期待 Star专挪!
https://github.com/ZengLingYong/blog

作者:以樂之名
本文原創(chuàng)及志,有不當(dāng)?shù)牡胤綒g迎指出。轉(zhuǎn)載請指明出處寨腔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末困肩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子脆侮,更是在濱河造成了極大的恐慌锌畸,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件靖避,死亡現(xiàn)場離奇詭異潭枣,居然都是意外死亡比默,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門盆犁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來命咐,“玉大人,你說我怎么就攤上這事谐岁〈椎欤” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵伊佃,是天一觀的道長窜司。 經(jīng)常有香客問我,道長航揉,這世上最難降的妖魔是什么塞祈? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮帅涂,結(jié)果婚禮上议薪,老公的妹妹穿的比我還像新娘。我一直安慰自己媳友,他們只是感情好斯议,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著醇锚,像睡著了一般哼御。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搂抒,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天艇搀,我揣著相機(jī)與錄音,去河邊找鬼求晶。 笑死焰雕,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芳杏。 我是一名探鬼主播矩屁,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼爵赵!你這毒婦竟也來了吝秕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤空幻,失蹤者是張志新(化名)和其女友劉穎烁峭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡约郁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年缩挑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鬓梅。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡供置,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绽快,到底是詐尸還是另有隱情芥丧,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布坊罢,位于F島的核電站续担,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏艘绍。R本人自食惡果不足惜赤拒,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一秫筏、第九天 我趴在偏房一處隱蔽的房頂上張望诱鞠。 院中可真熱鬧,春花似錦这敬、人聲如沸航夺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阳掐。三九已至,卻和暖如春冷蚂,著一層夾襖步出監(jiān)牢的瞬間缭保,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工蝙茶, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留艺骂,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓隆夯,卻偏偏與公主長得像钳恕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蹄衷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

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