原文首發於個人博客,說實話,這半年來在各大社區看別人分享的面試題中 bind 函數已經出現 n 屢次了,此次準備詳細探究下git
首先讓咱們看看 mdn 對於 bind 函數的描述是什麼es6
fun.bind(thisArg[, arg1[, arg2[, ...]]])github
返回由指定的 this 值和初始化參數改造的原函數拷貝面試
當代碼 new Foo(...) 執行時,會發生如下事情: 一、一個繼承自 Foo.prototype 的新對象被建立。 二、使用指定的參數調用構造函數 Foo ,並將 this 綁定到新建立的對象。new Foo 等同於 new Foo(),也就是沒有指定參數列表,Foo 不帶任何參數調用的狀況。 三、由構造函數返回的對象就是 new 表達式的結果。若是構造函數沒有顯式返回一個對象,則使用步驟 1 建立的對象。(通常狀況下,構造函數不返回值,可是用戶能夠選擇主動返回對象,來覆蓋正常的對象建立步驟)若是你看不懂這段話,不要緊,看完下面這段代碼你就清楚了數組
function Foo(){
}
下面代碼就是執行new Foo()時的簡單實現
let obj = {};
obj.__proto__ = Foo.prototype
return Foo.call(obj)
複製代碼
對於new的完整實現能夠參考這位大神的博客babel
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 了對吧,因此咱們繼續完善app
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,而是考察你其餘知識點,好比下面的類數組如何轉換爲真正的數組koa
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的效果及實現