面試常見問題之實現bind函數

前言:

原文首發於個人博客,說實話,這半年來在各大社區看別人分享的面試題中 bind 函數已經出現 n 屢次了,此次準備詳細探究下git

首先讓咱們看看 mdn 對於 bind 函數的描述是什麼es6

語法

fun.bind(thisArg[, arg1[, arg2[, ...]]])github

參數

  • thisArg   當綁定函數被調用時,該參數會做爲原函數運行時的 this 指向。當使用 new 操做符調用綁定函數時,該參數無效。
  • arg1, arg2, ...   當綁定函數被調用時,這些參數將置於實參以前傳遞給被綁定的方法。

返回值

  返回由指定的 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


實現

乞丐版, 沒法預先填入參數,僅實現執行時改變 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 了對吧,因此咱們繼續完善app

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,而是考察你其餘知識點,好比下面的類數組如何轉換爲真正的數組koa

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的效果及實現

相關文章
相關標籤/搜索