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