面试常见问题之实现bind函数
前言:
原文首發于我的博客,說實話,這半年來在各大社區看別人分享的面試題中 bind 函數已經出現 n 多次了,這次準備詳細探究下
首先讓我們看看 mdn 對于 bind 函數的描述是什么
語法
fun.bind(thisArg[, arg1[, arg2[, ...]]])
參數
- thisArg ??當綁定函數被調用時,該參數會作為原函數運行時的 this 指向。當使用 new 操作符調用綁定函數時,該參數無效。
- arg1, arg2, ... ??當綁定函數被調用時,這些參數將置于實參之前傳遞給被綁定的方法。
返回值
??返回由指定的 this 值和初始化參數改造的原函數拷貝
當代碼 new Foo(...) 執行時,會發生以下事情: 1、一個繼承自 Foo.prototype 的新對象被創建。 2、使用指定的參數調用構造函數 Foo ,并將 this 綁定到新創建的對象。new Foo 等同于 new Foo(),也就是沒有指定參數列表,Foo 不帶任何參數調用的情況。 3、由構造函數返回的對象就是 new 表達式的結果。如果構造函數沒有顯式返回一個對象,則使用步驟 1 創建的對象。(一般情況下,構造函數不返回值,但是用戶可以選擇主動返回對象,來覆蓋正常的對象創建步驟)如果你看不懂這段話,沒關系,看完下面這段代碼你就清楚了
function Foo(){} 下面代碼就是執行new Foo()時的簡單實現 let obj = {}; obj.__proto__ = Foo.prototype return Foo.call(obj)對于new的完整實現可以參考這位大神的博客
實現
乞丐版, 無法預先填入參數,僅實現執行時改變 this 指向
let obj = {ll: 'seve' };Function.prototype.bind = function(that) {var self = this;return function() {return self.apply(that, arguments);}; }; let func0 = function(a, b, c) {console.log(this.ll);console.log([a, b, c]); }.bind(obj, 1, 2);func0(3); // seve // [ 3, undefined, undefined ] 發現1,2并沒有填入乞丐版也太 low 了對吧,所以我們繼續完善
es6 進階版
es6 提供了結構運算符,可以很方便的利用其功能實現 bind
Function.prototype.bind = function(that, ...argv) {if (typeof this !== 'function') {throw new TypeError(`${this} is not callable`);}// 保存原函數let self = this;// 獲取bind后函數傳入的參數return function(...argu) {return self.apply(that, [...argv, ...argu]);}; }; let func1 = function(a, b, c) {console.log(this.ll);console.log([a, b, c]); }.bind(obj, 1, 2);func1(3); // seve // [ 1, 2, 3 ]es6 版實現很簡單對吧,但是面試官說我們的運行環境是 es5,這時你心中竊喜,bable 大法好,但是你可千萬不要說有 babel,因為面試官的意圖不太可能是問你 es6 如何轉換成 es5,而是考察你其他知識點,比如下面的類數組如何轉換為真正的數組
es5 進階版
Function.prototype.bind = function() {if (typeof this !== 'function') {throw new TypeError(`${this} is not callable`);}var self = this;var slice = [].slice;// 模擬es6的解構效果var that = arguments[0];var argv = slice.call(arguments, 1);return function() {// slice.call(arguments, 0)將類數組轉換為數組return self.apply(that, argv.concat(slice.call(arguments, 0)));}; }; let func2 = function(a, b, c) {console.log(this.ll);console.log([a, b, c]); }.bind(obj, 1, 2);func2(3); // seve // [ 1, 2, 3 ]當然,寫到這里,對于絕大部分面試,這份代碼都是一份不錯的答案,但是為了給面試官留下更好的印象,我們需要上終極版 實現完整的bind函數,這樣還可以跟面試官吹一波
終極版
為了當使用new操作符時,bind后的函數不丟失this。我們需要把bind前的函數的原型掛載到bind后函數的原型上
但是為了修改bind后函數的原型而對bind前的原型不產生影響,都是對象惹的禍。。。直接賦值只是賦值對象在堆中的地址 所以需要把原型繼承給bind后的函數,而不是直接賦值,我有在一些地方看到說Object.crate可以實現同樣的效果,有興趣的可以了解一下,但是我自己試了下,發現效果并不好,new 操作時this指向錯了(可能是我使用姿勢錯了)
通過直接賦值的效果
Function.prototype.bind = function(that, ...argv) {if (typeof this !== 'function') {throw new TypeError(`${this} is not callable`);}// 保存原函數let self = this;let func = function() {};// 獲取bind后函數傳入的參數let bindfunc = function(...arguments) {return self.apply(this instanceof func ? this : that, [...argv, ...arguments]);};// 把this原型上的東西掛載到func原型上面// func.prototype = self.prototype;// 為了避免func影響到this,通過new 操作符進行復制原型上面的東西bindfunc.prototype = self.prototype;return bindfunc; };function bar() {console.log(this.ll);console.log([...arguments]); } let func3 = bar.bind(null);func3.prototype.value = 1;console.log(bar.prototype.value) // 1 可以看到bind后的原型對bind前的原型產生的同樣的影響通過繼承賦值的效果
Function.prototype.bind = function(that, ...argv) {if (typeof this !== 'function') {throw new TypeError(`${this} is not callable`);}// 保存原函數let self = this;let func = function() {};// 獲取bind后函數傳入的參數let bindfunc = function(...argu) {return self.apply(this instanceof func ? this : that, [...argv, ...argu]);};// 把this原型上的東西掛載到func原型上面func.prototype = self.prototype;// 為了避免func影響到this,通過new 操作符進行復制原型上面的東西bindfunc.prototype = new func();return bindfunc; };function bar() {console.log(this.ll);console.log([...arguments]); } let func3 = bar.bind(null);func3.prototype.value = 1;console.log(bar.prototype.value) // undefined 可以看到bind后的原型對bind前的原型不產生影響func3(5); // seve// [ 5 ] new func3(5); // undefined// [ 5 ]以上代碼或者表述如有錯誤或者不嚴謹的地方,歡迎指出,或者在評論區討論,覺得我的文章有用的話,可以訂閱或者star支持我的博客
下系列文章我打算寫關于koa框架的實現,第一篇我會帶大家探究Object.create的效果及實現
更多專業前端知識,請上 【猿2048】www.mk2048.com 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的面试常见问题之实现bind函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bind函数polyfill源码解析
- 下一篇: Redux源码简析