自己動手用原生實現 bind/call/apply


大家好!!!注冊一年多的第一篇博客。

自我介紹: 本人非計算機專業出身,轉行進入前端半年時間,寫的東西可能觀賞性不強,一起進步吧道友們。。。

接下來的一段時間, 我都會不定期整理自己理解的js知識點, 歡迎各路道友吐槽。

 

進入正題......  (針對新手,老司機不要嘲笑我)

 

首先, bind/call/apply 這改變this指向的三兄弟我們都很熟悉了, 還有其他改變this指向的方法這里就不多說了哈,要不就跑題了。。。。

你需要知道的前提 : 這三貨都是函數原型(Function.prototype)上的方法, 所以只有函數可以調用。

              小白 : “原型是啥?”   每一個構造函數(其實就是普通函數,函數名首字母大寫而已)都有一個prototype原型對象,當你引用某個實例對象上的屬性時, 會先查找該對象本身有沒有該屬性,有就用,沒有就會去構造這個實例的構造函數的原型上去找,還沒有的話就會沿着構造函數的原型對象的__proto__屬性往上找......haha, 好像又跑題了, 關於原型鏈和繼承可以自行百度

 

真的進入正題。。。。。。

 

它們不是函數上的方法嗎, 我們先定義個函數, 后面來改變這個函數的this指向

function out(age, sex, type) {
    console.log(age, sex, this.age, this.sex, type); }

分別輸出傳入的參數age, sex, type  和   this 上的age , sex, 

然后定義個對象, 后面讓this指向該對象,  沒錯,此對象描述的就是我

let  my = {
    age : 16, sex : 'handsome man' };

先試試bind, 先會用然后再來實現, 由於bind可以傳兩次參數,你可以這樣傳

const bindFunction = out.bind(my,50); //this 指向my 不會直接執行 返回一個明確this指向的函數
bindFunction('women','bind'); // 傳參執行 

還可以這樣傳,想怎么傳就怎么傳,只要確保.bind后的第一個參數是你要改變this指向的對象, (如果什么都不傳,this指向window)

const bindFunction = out.bind(my); //this 指向my 不會直接執行 返回一個明確this指向的函數
bindFunction('50','women','bind'); // 傳參執行 

執行后輸出 :

50 "women" 16 "handsome man" "bind"

總結一下我們要模擬的bind

1.  函數A調用bind返回一個可執行的函數B, 調用bind時可傳參 可不穿參, 不穿參時this指向window

2.  調用B可繼續傳參, 兩次傳參合並

3.   new B() 的構造函數依然是A, 而不是B,並且因為new操作符改變this指向的優先級最高,所以如果使用了new操作符,this指向不應該變,就是該是啥是啥(划重點)

上代碼 :

 

Function.prototype.myBind = function (target) { // bind 是 function 上的 方法  this 指向 target
    if (typeof  this !== 'function') {
        throw new TypeError('not a function') }; const _this = this, // 保存一下this args = Array.prototype.slice.call(arguments, 1),// arguments是類數組,沒有 slice 方法 保存第一次傳入的參數 temp = function () {}; // 定義一個中間函數 const f = function () { return _this.apply(this instanceof temp ? this : (target || window), args.concat([].slice.call(arguments))); // 合並兩次傳的參數  }; temp.prototype = _this.prototype; // 讓temp 的原型 等於 this的原型 f.prototype = new temp(); //f 繼承 temp 聖杯繼承可以了解下 return f; };

 

先正常執行一下myBind:

  const bind1 = this.out.myBind(my, 50);
    // const bind2 = new bind1('women','myBind');
  const bind3 = bind1('women','myBind');

結果是:

50 "women" 16 "handsome man" "myBind"

OK , 和bind的輸出結果一樣

再使用new操作符:

  const bind1 = this.out.myBind(my, 50);
    const bind2 = new bind1('women','myBind'); // const bind3 = bind1('women','myBind');

結果是這樣:此時this不再指向my, 而是指向new 出來的一個空對象,所以this上沒有age和sex兩個屬性

50 "women" undefined undefined "myBind"

 

到此bind 我們已經完美模擬出來了  -------- 是不是有點小激動 ----------細心的道友會發現myBind 里 還借用了call 和apply!!! 這還不行啊

 

🆗接下來我們繼續實現myCall 和 myApply

 

思考:

模擬bind是用call和apply來改變this指向的,

那么我們模擬call和apply要用什么來改變this指向呢?        給大家1s    思考......................................................

 

沒錯, 就是優先級最低的點(.)操作符, 還記得誰調用this就指向誰嗎, 就是A.eat(),eat中的this就指向A,

 

基於此我們來實現myCall:

Function.prototype.myCall = function (target) {
target.fn = this; // 給target加一個屬性fn = this
const args = [];
for (let i = 1; i < arguments.length; i ++ ) {
args.push(arguments[i]); // 遍歷把參數push進數組
}
const result = target.fn(...args); // ...是把數組的中括號去掉, 這樣傳進去的就是參數列表了
delete target.fn; // 最后把fn屬性刪掉
return result;
};

這樣就把this指向target了

同理myBind;

Function.prototype.myApply = function (target) {
target.fn = this;// 給target加一個屬性fn = this
var result;
if(arguments[1]) {
result = target.fn(...arguments[1]);// ...是把數組的中括號去掉, 這樣傳進去的就是參數列表了
} else {
result = target.fn();
}
delete target.fn;// 最后把fn屬性刪掉
return result;
};

執行結果:

 

 

 這樣理論上來說Call和Apply就已經模擬出來了, 但是我還留下了一個坑....................................................

 

有老司機可以看出來嗎?       一秒鍾時間搶答 -----------------------------------------------------------

 

沒錯, 我在myCall和myApply 里用了ES6的語法 : ...操作符扒括號(let 和 const 請忽略,用var也可以), 那我們可以完全用es5 來解決 如何把數組里的參數變成參數列表傳進去嗎?

 

 

道友們 有思路嗎 ?  給大家個提示: 可以用ES3.0 的 eval()  配合 join() 方法實現 ,       eval是魔鬼大家請慎用

 

好記性不如爛筆頭,大家回去自己動手試試吧 !

再演示一遍完整demo

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM