面試官問:能否模擬實現JS的bind方法(高頻考點)

可以點擊上方的話題JS基礎系列,查看往期文章

寫于2018年11月21日,發布在掘金閱讀量1.3w+

前言

這是面試官問系列的第二篇,旨在幫助讀者提升JS基礎知識,包含new、call、apply、this、繼承相關知識。
面試官問系列文章如下:感興趣的讀者可以點擊閱讀。
1.面試官問:能否模擬實現JS的new操作符
2.面試官問:能否模擬實現JS的bind方法(本文)
3.面試官問:能否模擬實現JS的call和apply方法
4.面試官問:JS的this指向
5.面試官問:JS的繼承

用過React的同學都知道,經常會使用bind來綁定this

import?React,?{?Component?}?from?'react';
class?TodoItem?extends?Component{constructor(props){super(props);this.handleClick?=?this.handleClick.bind(this);}handleClick(){console.log('handleClick');}render(){return??(<div?onClick={this.handleClick}>點擊</div>);};
}
export?default?TodoItem;

那么面試官可能會問是否想過bind到底做了什么,怎么模擬實現呢。

附上之前寫文章寫過的一段話:已經有很多模擬實現bind的文章,為什么自己還要寫一遍呢。學習就好比是座大山,人們沿著不同的路登山,分享著自己看到的風景。你不一定能看到別人看到的風景,體會到別人的心情。只有自己去登山,才能看到不一樣的風景,體會才更加深刻。

先看一下bind是什么。從上面的React代碼中,可以看出bind執行后是函數,并且每個函數都可以執行調用它。眼見為實,耳聽為虛。讀者可以在控制臺一步步點開例子1中的obj:

var?obj?=?{};
console.log(obj);
console.log(typeof?Function.prototype.bind);?//?function
console.log(typeof?Function.prototype.bind());??//?function
console.log(Function.prototype.bind.name);??//?bind
console.log(Function.prototype.bind().name);??//?bound
Function.prototype.bind

因此可以得出結論1:

1、bindFunctoin原型鏈中Function.prototype的一個屬性,每個函數都可以調用它。
2、bind本身是一個函數名為bind的函數,返回值也是函數,函數名是bound。(打出來就是bound加上一個空格)。知道了bind是函數,就可以傳參,而且返回值'bound '也是函數,也可以傳參,就很容易寫出例子2
后文統一 bound 指原函數original bind之后返回的函數,便于說明。

var?obj?=?{name:?'若川',
};
function?original(a,?b){console.log(this.name);console.log([a,?b]);return?false;
}
var?bound?=?original.bind(obj,?1);
var?boundResult?=?bound(2);?//?'若川',?[1,?2]
console.log(boundResult);?//?false
console.log(original.bind.name);?//?'bind'
console.log(original.bind.length);?//?1
console.log(original.bind().length);?//?2?返回original函數的形參個數
console.log(bound.name);?//?'bound?original'
console.log((function(){}).bind().name);?//?'bound?'
console.log((function(){}).bind().length);?//?0

由此可以得出結論2:

1、調用bind的函數中的this指向bind()函數的第一個參數。

2、傳給bind()的其他參數接收處理了,bind()之后返回的函數的參數也接收處理了,也就是說合并處理了。

3、并且bind()后的namebound + 空格 + 調用bind的函數名。如果是匿名函數則是bound + 空格

4、bind后的返回值函數,執行后返回值是原函數(original)的返回值。

5、bind函數形參(即函數的length)是1bind后返回的bound函數形參不定,根據綁定的函數原函數(original)形參個數確定。

根據結論2:我們就可以簡單模擬實現一個簡版bindFn

//?第一版?修改this指向,合并參數
Function.prototype.bindFn?=?function?bind(thisArg){if(typeof?this?!==?'function'){throw?new?TypeError(this?+?'must?be?a?function');}//?存儲函數本身var?self?=?this;//?去除thisArg的其他參數?轉成數組var?args?=?[].slice.call(arguments,?1);var?bound?=?function(){//?bind返回的函數?的參數轉成數組var?boundArgs?=?[].slice.call(arguments);//?apply修改this指向,把兩個函數的參數合并傳給self函數,并執行self函數,返回執行結果return?self.apply(thisArg,?args.concat(boundArgs));}return?bound;
}
//?測試
var?obj?=?{name:?'若川',
};
function?original(a,?b){console.log(this.name);console.log([a,?b]);
}
var?bound?=?original.bindFn(obj,?1);
bound(2);?//?'若川',?[1,?2]

如果面試官看到你答到這里,估計對你的印象60、70分應該是會有的。但我們知道函數是可以用new來實例化的。那么bind()返回值函數會是什么表現呢。
接下來看例子3

var?obj?=?{name:?'若川',
};
function?original(a,?b){console.log('this',?this);?//?original?{}console.log('typeof?this',?typeof?this);?//?objectthis.name?=?b;console.log('name',?this.name);?//?2console.log('this',?this);??//?original?{name:?2}console.log([a,?b]);?//?1,?2
}
var?bound?=?original.bind(obj,?1);
var?newBoundResult?=?new?bound(2);
console.log(newBoundResult,?'newBoundResult');?//?original?{name:?2}

例子3種可以看出this指向了new bound()生成的新對象。

可以分析得出結論3:

1、bind原先指向obj的失效了,其他參數有效。

2、new bound的返回值是以original原函數構造器生成的新對象。original原函數的this指向的就是這個新對象。另外前不久寫過一篇文章:面試官問:能否模擬實現JS的new操作符。簡單摘要:new做了什么:

1.創建了一個全新的對象。
2.這個對象會被執行[[Prototype]](也就是__proto__)鏈接。
3.生成的新對象會綁定到函數調用的this。
4.通過new創建的每個對象將最終被[[Prototype]]鏈接到這個函數的prototype對象上。
5.如果函數沒有返回對象類型Object(包含Functoin, Array, Date, RegExg, Error),那么new表達式中的函數調用會自動返回這個新的對象。

所以相當于new調用時,bind的返回值函數bound內部要模擬實現new實現的操作。話不多說,直接上代碼。

//?第三版?實現new調用
Function.prototype.bindFn?=?function?bind(thisArg){if(typeof?this?!==?'function'){throw?new?TypeError(this?+?'?must?be?a?function');}//?存儲調用bind的函數本身var?self?=?this;//?去除thisArg的其他參數?轉成數組var?args?=?[].slice.call(arguments,?1);var?bound?=?function(){//?bind返回的函數?的參數轉成數組var?boundArgs?=?[].slice.call(arguments);var?finalArgs?=?args.concat(boundArgs);// new 調用時,其實this instanceof bound判斷也不是很準確。es6 new.target就是解決這一問題的。if(this?instanceof?bound){//?這里是實現上文描述的?new?的第?1,?2,?4?步//?1.創建一個全新的對象//?2.并且執行[[Prototype]]鏈接// 4.通過`new`創建的每個對象將最終被`[[Prototype]]`鏈接到這個函數的`prototype`對象上。// self可能是ES6的箭頭函數,沒有prototype,所以就沒必要再指向做prototype操作。if(self.prototype){//?ES5?提供的方案?Object.create()//?bound.prototype?=?Object.create(self.prototype);//?但?既然是模擬ES5的bind,那瀏覽器也基本沒有實現Object.create()//?所以采用?MDN?ployfill方案?https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/createfunction?Empty(){}Empty.prototype?=?self.prototype;bound.prototype?=?new?Empty();}//?這里是實現上文描述的?new?的第?3?步// 3.生成的新對象會綁定到函數調用的`this`。var?result?=?self.apply(this,?finalArgs);//?這里是實現上文描述的?new?的第?5?步//?5.如果函數沒有返回對象類型`Object`(包含`Functoin`,?`Array`,?`Date`,?`RegExg`,?`Error`),//?那么`new`表達式中的函數調用會自動返回這個新的對象。var?isObject?=?typeof?result?===?'object'?&&?result?!==?null;var?isFunction?=?typeof?result?===?'function';if(isObject?||?isFunction){return?result;}return?this;}else{//?apply修改this指向,把兩個函數的參數合并傳給self函數,并執行self函數,返回執行結果return?self.apply(thisArg,?finalArgs);}};return?bound;
}

面試官看到這樣的實現代碼,基本就是滿分了,心里獨白:這小伙子/小姑娘不錯啊。不過可能還會問this instanceof bound不準確問題。上文注釋中提到this instanceof bound也不是很準確,ES6 new.target很好的解決這一問題,我們舉個例子4:

instanceof 不準確,ES6 new.target很好的解決這一問題

function?Student(name){if(this?instanceof?Student){this.name?=?name;console.log('name',?name);}else{throw?new?Error('必須通過new關鍵字來調用Student。');}
}
var?student?=?new?Student('若');
var?notAStudent?=?Student.call(student,?'川');?//?不拋出錯誤,且執行了。
console.log(student,?'student',?notAStudent,?'notAStudent');function?Student2(name){if(typeof?new.target?!==?'undefined'){this.name?=?name;console.log('name',?name);}else{throw?new?Error('必須通過new關鍵字來調用Student2。');}
}
var?student2?=?new?Student2('若');
var?notAStudent2?=?Student2.call(student2,?'川');
console.log(student2,?'student2',?notAStudent2,?'notAStudent2');?//?拋出錯誤

細心的同學可能會發現了這版本的代碼沒有實現bind后的bound函數的nameMDN Function.name和lengthMDN Function.length。面試官可能也發現了這一點繼續追問,如何實現,或者問是否看過es5-shim的源碼實現L201-L335。如果不限ES版本。其實可以用ES5Object.defineProperties來實現。

Object.defineProperties(bound,?{'length':?{value:?self.length,},'name':?{value:?'bound?'?+?self.name,}
});

es5-shim的源碼實現bind

直接附上源碼(有刪減注釋和部分修改等)

var?$Array?=?Array;
var?ArrayPrototype?=?$Array.prototype;
var?$Object?=?Object;
var?array_push?=?ArrayPrototype.push;
var?array_slice?=?ArrayPrototype.slice;
var?array_join?=?ArrayPrototype.join;
var?array_concat?=?ArrayPrototype.concat;
var?$Function?=?Function;
var?FunctionPrototype?=?$Function.prototype;
var?apply?=?FunctionPrototype.apply;
var?max?=?Math.max;
//?簡版?源碼更復雜些。
var?isCallable?=?function?isCallable(value){if(typeof?value?!==?'function'){return?false;}return?true;
};
var?Empty?=?function?Empty()?{};
//?源碼是?defineProperties
//?源碼是bind筆者改成bindFn便于測試
FunctionPrototype.bindFn?=?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?=?apply.call(target,this,array_concat.call(args,?array_slice.call(arguments)));if?($Object(result)?===?result)?{return?result;}return?this;}?else?{return?apply.call(target,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);}//?這里是Function構造方式生成形參length?$1,?$2,?$3...bound?=?$Function('binder',?'return?function?('?+?array_join.call(boundArgs,?',')?+?'){?return?binder.apply(this,?arguments);?}')(binder);if?(target.prototype)?{Empty.prototype?=?target.prototype;bound.prototype?=?new?Empty();Empty.prototype?=?null;}return?bound;
};

你說出es5-shim源碼bind實現,感慨這代碼真是高效、嚴謹。面試官心里獨白可能是:你就是我要找的人,薪酬福利你可以和HR去談下。

最后總結一下

1、bindFunction原型鏈中的Function.prototype的一個屬性,它是一個函數,修改this指向,合并參數傳遞給原函數,返回值是一個新的函數。
2、bind返回的函數可以通過new調用,這時提供的this的參數被忽略,指向了new生成的全新對象。內部模擬實現了new操作符。
3、es5-shim源碼模擬實現bind時用Function實現了length
事實上,平時其實很少需要使用自己實現的投入到生成環境中。但面試官通過這個面試題能考察很多知識。比如this指向,原型鏈,閉包,函數等知識,可以擴展很多。
讀者發現有不妥或可改善之處,歡迎指出。另外覺得寫得不錯,可以點個贊,也是對筆者的一種支持。

文章中的例子和測試代碼放在github中bind模擬實現 github。bind模擬實現 預覽地址 F12看控制臺輸出,結合source面板查看效果更佳。

//?最終版?刪除注釋?詳細注釋版請看上文
Function.prototype.bind?=?Function.prototype.bind?||?function?bind(thisArg){if(typeof?this?!==?'function'){throw?new?TypeError(this?+?'?must?be?a?function');}var?self?=?this;var?args?=?[].slice.call(arguments,?1);var?bound?=?function(){var?boundArgs?=?[].slice.call(arguments);var?finalArgs?=?args.concat(boundArgs);if(this?instanceof?bound){if(self.prototype){function?Empty(){}Empty.prototype?=?self.prototype;bound.prototype?=?new?Empty();}var?result?=?self.apply(this,?finalArgs);var?isObject?=?typeof?result?===?'object'?&&?result?!==?null;var?isFunction?=?typeof?result?===?'function';if(isObject?||?isFunction){return?result;}return?this;}else{return?self.apply(thisArg,?finalArgs);}};return?bound;
}

推薦閱讀

我在阿里招前端,我該怎么幫你?(現在還可以加模擬面試群)
如何拿下阿里巴巴 P6 的前端 Offer
如何準備阿里P6/P7前端面試--項目經歷準備篇
大廠面試官常問的亮點,該如何做出?
如何從初級到專家(P4-P7)打破成長瓶頸和有效突破
若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?
若川知乎高贊:有哪些必看的 JS庫?

末尾

你好,我是若川,江湖人稱菜如若川,歷時一年只寫了一個學習源碼整體架構系列~(點擊藍字了解我)

  1. 關注若川視野,回復"pdf" 領取優質前端書籍pdf,回復"1",可加群長期交流學習

  2. 我的博客地址:https://lxchuan12.gitee.io?歡迎收藏

  3. 覺得文章不錯,可以點個在看呀^_^另外歡迎留言交流~

精選前端好文,伴你不斷成長

若川原創文章精選!可點擊

小提醒:若川視野公眾號面試、源碼等文章合集在菜單欄中間【源碼精選】按鈕,歡迎點擊閱讀,也可以星標我的公眾號,便于查找

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276299.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276299.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276299.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

推薦一個ASP.NET的資源網站

推薦一個ASP.NET的資源網站&#xff1a;ASP.NET屋-專業的ASP.NET學習,ASP.NET源碼,ASP.NET演示,MVC,Silverlight,JQuery,EXTJS,軟件設計網站 http://www.aspx58.com.cn/ 轉載于:https://www.cnblogs.com/yuyichen/archive/2009/11/26/1611682.html

連續投影算法_中航國畫榮獲“2020 IAV國際視聽嘉年華” 大屏幕投影顯示行業三大獎項!...

近日&#xff0c;“2020IAV國際視聽嘉年華”在深圳灣創新生態園隆重舉行&#xff0c;航空工業上電下屬子公司中航國畫作為2020年迪拜世博會中國館官方合作伙伴應邀參加此次高峰論壇&#xff0c;并在展會期間憑借4K系列雙色激光工程投影機、LP160UL系列超高亮度雙色激光工程投影…

Linux 系統備份恢復工具 SYSTEM-RESCUE-CD 的使用

測試系統&#xff1a;linux-debian 備份系統 1、插入dd有systemrescuecd-x86-4.0.1.iso的系統備份恢復U盤&#xff1b; 2、開機按DEL進入BIOS&#xff0c;設置BOOT為USB-HDD啟動。Save&Exit&#xff1b; 3、進入備份恢復系統界面&#xff1a;----------------------------…

CMDB功能分析與實現方案

CMDB功能分析與實現方案 CMDB系統的主要功能 1.用戶管理,記錄測試,開發,運維人員的用戶表 2.業務線管理,記錄業務的詳情 3.項目管理,指定此項目屬于那一條業務線 4.應用管理,指定此應用的開發人員 5.主機管理,記錄主機管理員,連接的網絡設備,存儲,云主機,物理主機等信息 6.主機…

感冒

看好這個網頁&#xff0c;你的感冒就好了轉載于:https://www.cnblogs.com/baoguo/archive/2009/11/27/1612308.html

python numpy矩陣索引_Numpy中的矩陣索引

索引后查看形狀&#xff1a;In [295]: Anp.matrix([1,2,3])In [296]: A.shapeOut[296]: (1, 3)In [297]: A[0]Out[297]: matrix([[1, 2, 3]])In [298]: A[0].shapeOut[298]: (1, 3)這種行為的關鍵是np.matrix始終是2d&#xff0c;所以即使選擇一行(A[0,:])&#xff0c;結果仍然…

他的前端焦慮:30歲以后的前端路怎么走? 你想過嗎~

馬云曾說&#xff0c;員工的離職原因很多&#xff0c;唯兩點最真實&#xff1a;一是錢&#xff0c;沒給到位&#xff1b;二是心&#xff0c;委屈了。當時間來到了年末&#xff0c;離職、跳槽、年終獎又成了年終熱詞&#xff0c;而此時辭職好似成為了一場勇敢者的游戲&#xff0…

不可忽略的apache 的 Keep Alive

轉載鏈接&#xff1a;http://hi.baidu.com/jx_iben/item/d5fe91feed74495ec9f337f1 在網頁開發過程中&#xff0c;Keep-Alive是HTTP協議中非常重要的一個屬性。大家知道HTTP構建在TCP之上。在HTTP早期實現中&#xff0c;每個HTTP請求都要打開一個socket連接。這種做效率很低&am…

碼云 Gitee 新增倉庫訪問之 IP 白名單功能

碼云企業版上線 IP 白名單功能&#xff0c;該功能主要用于企業禁止非指定 IP 訪問代碼倉庫。如下圖所示&#xff1a;使用方法&#xff1a;進入企業控制面板 -> 管理 -> 安全設置添加允許訪問 Git 倉庫的 IP 地址啟用安全選項&#xff1a;只允許在信任范圍內推拉代碼其他關…

oracle 實時查詢最耗CPU資源的SQL語句

1. 先通過top命令查看產用資源較多的spid號 2.查詢當前耗時的會話ID&#xff0c;用戶名&#xff0c;sqlID等&#xff1a;select sid,serial#,machine,username,program,sql_hash_value,sql_id, to_char(logon_time,yyyy/mm/dd hh24:mi:ss) as login_time from v$sessionw…

福利 | 抽獎送現金送書《Web前端工程師修煉之道》

很多關注了我的公眾號的粉絲可能都不知道我&#xff0c;趁這次機會簡單介紹下。你好&#xff0c;我是若川。江西人&#xff0c;某不那么知名的陶瓷大學畢業生&#xff0c;目前在杭州從事前端開發工作。常以若川為名混跡于江湖。更詳細的可以點擊關于我我歷時一年才寫了《學習源…

jQuery 1.9.1中live()變更

轉載鏈接&#xff1a;http://www.360doc.com/content/13/1222/22/14022539_339358149.shtml 開始的時候在jQuery.1.7.1中使用了.live()覺得很好用&#xff0c;特別是在綁定事件之后再加入的元素的事件綁定上很方便(第一次live之后以后添加的元素就不需要綁定啦) 后來jQuery更…

蒙特卡洛方法_基本理論-蒙特卡洛方法與定積分

全球圖形學領域教育的領先者、自研引擎的倡導者、底層技術研究領域的技術公開者&#xff0c;東漢書院在致力于使得更多人群具備內核級競爭力的道路上&#xff0c;將帶給小伙伴們更多的公開技術教學和視頻&#xff0c;感謝一路以來有你的支持。我們正在用實際行動來幫助小伙伴們…

WebServices 基礎知識

1. 有關生存期的補充正常情況下&#xff0c;每次調用 WebMethod&#xff0c;服務器都會創建一個新的 WebService 對象&#xff0c;即便客戶端使用同一個代理對象多次調用 WebMethod。而我們一旦調用了有緩存標記的 WebMethod&#xff0c;只要未超出緩存期&#xff0c;WebServic…

sass和compass基礎用法

一、基本命令 sass都是通過gem安裝&#xff0c;以下是一些基礎的命令移除ruby的鏡像地址 gem sources --remove https://rubygems.org/添加淘寶的鏡像 gem source -a http://ruby.taobao.org查看鏡像 gem source -v單文件轉換命令 sass style.scss style.css單文件監聽命令&am…

字節面試官:如何實現Ajax并發請求控制

偷偷告訴你&#xff0c;點此抽獎送紅包還送3本比紅寶書還貴的書實現一個批量請求函數 multiRequest(urls, maxNum)&#xff0c;要求如下&#xff1a; ? 要求最大并發數 maxNum ? 每當有一個請求返回&#xff0c;就留下一個空位&#xff0c;可以增加新的請求 ? 所有請求完成后…

Jquery 中 ajaxSubmit使用講解

轉載鏈接&#xff1a;http://blog.csdn.net/h70614959/article/details/8810270 1 引入依賴腳本 <script type"text/javascript" src"/js/jquery/jquery.form.js"></script> //ajaxForm 依賴腳本<script type"text/javascript"…

接口與抽象類

接口中的屬性 默認是 public static final類型 就算你不加 默認也是存在 方法默認都是public abstract類型 不加默認也存在 比如 interface K { String abc"sdfd"; void add() throws Exception; } 實際上編譯時 是這樣 interface K { Public static final String a…

制造行業電子化簽約及印控一體化解決方案

當生產“智造化”、營銷“數字化”成為趨勢&#xff0c;生產制造型企業如何實現產品生產-銷售全流程提速降本&#xff1f;生產制造型企業為了在生產、營銷環節降低成本、提升效率&#xff0c;經營模式上常有如下特征&#xff1a;? 原料導向&#xff0c;多點協同生產&#xff1…

python 點擊按鈕采集圖片_python多線程采集圖片

cmd中運行>python untitled2.py 圖片的網站import requestsimport threadingfrom bs4 import BeautifulSoupimport sysimport osif len(sys.argv) ! 2:print("Usage : " )print(" python main.py [URL]" )exit(1)# config-starturl sys.argv[1]thre…