首頁  >  文章  >  web前端  >  在Javascript中如何實作bind

在Javascript中如何實作bind

亚连
亚连原創
2018-06-13 15:30:361461瀏覽

這篇文章主要介紹了Javascript中從學習bind到實現bind的過程,有興趣的朋友跟著學習下。

bind是什麼

bind()方法建立一個新的函數, 當被呼叫時,將其this關鍵字設定為提供的值,在呼叫新函數時,在任何提供之前提供一個給定的參數序列。

var result = fun.bind(thisArg[, arg1[, arg2[, ...]]]) 
result(newArg1, newArg2...)

沒看懂沒事接著往下看。

bind到底做了什麼

從上面的介紹可以看出三點。首先呼叫bind方法會傳回一個新的函數(這個新的函數的函數體應該和fun是一樣的)。同時bind中傳遞兩個參數,第一個是this指向,也就是傳入了什麼this就等於什麼。如下程式碼所示:

this.value = 2
var foo = {
  value: 1
}
var bar = function() {
 console.log(this.value)
}
var result = bar.bind(foo)
bar() // 2
result() // 1,即this === foo

第二個參數為一個序列,你可以將任意數量的參數傳遞到其中。並且會預置到新函數參數之前。

this.value = 2
var foo = {
  value: 1
};
var bar = function(name, age, school) {
 console.log(name) // 'An'
 console.log(age) // 22
 console.log(school) // '家里蹲大学'
}
var result = bar.bind(foo, 'An') //预置了部分参数'An'
result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中

我們可以看出在最後調用 result(22, '家裡蹲大學') 的時候,其內部已經包含了在調用bind的時候傳入的 'An'。

一句話總結:呼叫bind,就會回傳一個新的函數。這個函數裡面的this就指向bind的第一個參數,同時this後面的參數會提前傳給這個新的函數。呼叫該新的函數時,再傳遞的參數會放到預置的參數後一起傳遞進新函數。

自己實作一個bind

實作一個bind需要實作以下兩個功能

傳回一個函數,綁定this ,傳遞預置參數

bind傳回的函數可以當作建構子使用。故作為建構函數時應使得this失效,但是傳入的參數依然有效

1、返回一個函數,綁定this,傳遞預置參數

this.value = 2
var foo = {
  value: 1
};
var bar = function(name, age, school) {
  console.log(name) // 'An'
  console.log(age) // 22
  console.log(school) // '家里蹲大学'
  console.log(this.value) // 1
}
Function.prototype.bind = function(newThis) {
  var aArgs  = Array.prototype.slice.call(arguments, 1) //拿到除了newThis之外的预置参数序列
  var that = this
  return function() {
    return that.apply(newThis, aArgs.concat(Array.prototype.slice.call(arguments)))
    //绑定this同时将调用时传递的序列和预置序列进行合并
  }
}
var result = bar.bind(foo, 'An')
result(22, '家里蹲大学')

這裡面有一個細節就是Array.prototype.slice.call(arguments, 1) 這句話,我們知道arguments這個變數可以拿到函數呼叫時傳遞的參數,但不是一個數組,但是其具有一個length屬性。為什麼如此呼叫就可以將其變成純數組了呢。那我們就需要回到V8的源碼來進行分析。 #這個版本的原始碼為早期版本,內容相對少一些。

function ArraySlice(start, end) {
 var len = ToUint32(this.length); 
 //需要传递this指向对象,那么call(arguments),
 //便可将this绑定到arguments,拿到其length属性。
 var start_i = TO_INTEGER(start);
 var end_i = len;
 if (end !== void 0) end_i = TO_INTEGER(end);
 if (start_i < 0) {
  start_i += len;
  if (start_i < 0) start_i = 0;
 } else {
  if (start_i > len) start_i = len;
 }
 if (end_i < 0) {
  end_i += len;
  if (end_i < 0) end_i = 0;
 } else {
  if (end_i > len) end_i = len;
 }
 var result = [];
 if (end_i < start_i)
  return result;
 if (IS_ARRAY(this))
  SmartSlice(this, start_i, end_i - start_i, len, result);
 else 
  SimpleSlice(this, start_i, end_i - start_i, len, result);
 result.length = end_i - start_i;
 return result;
};

從原始碼中可以看到透過call將arguments下的length屬性賦給slice後,便可透過start_i & end_i來獲得最後的數組,所以不需要傳遞進slice時就是一個純數組最後也可以得到一個數組變數。

2、bind傳回的函式可以作為建構函式使用

被用來當作建構子時,this應指向new出來的實例,同時有prototype屬性,其指向實例的原型。

this.value = 2
var foo = {
 value: 1
};
var bar = function(name, age, school) {
 ...
 console.log(&#39;this.value&#39;, this.value)
}
Function.prototype.bind = function(newThis) {
 var aArgs  = Array.prototype.slice.call(arguments, 1)
 var that = this //that始终指向bar
 var NoFunc = function() {}
 var resultFunc = function() {
  return that.apply(this instanceof that ? this : newThis, aArgs.concat(Array.prototype.slice.call(arguments)))
 } 
 NoFunc.prototype = that.prototype //that指向bar
 resultFunc.prototype = new NoFunc()
 return resultFunc
}
var result = bar.bind(foo, &#39;An&#39;)
result.prototype.name = &#39;Lsc&#39; // 有prototype属性
var person = new result(22, &#39;家里蹲大学&#39;)
console.log(&#39;person&#39;, person.name) //&#39;Lsc&#39;

上面這段模擬程式碼做了兩件重要的事。

1.給回傳的函數模擬一個prototype屬性。 ,因為透過建構函式new出來的實例可以查詢到原型上定義的屬性和方法

var NoFunc = function() {}
...
NoFunc.prototype = that.prototype //that指向bar
resultFunc.prototype = new NoFunc()
return resultFunc

透過上面程式碼可以看出,that總是指向bar。同時傳回的函數已經繼承了that.prototype即bar.prototype。為什麼不直接讓傳回的函數的prototype屬性resultFunc.prototype 等於為bar(that).prototype呢,這是因為任何new出來的實例都可以存取原型鏈。如果直接賦值那麼new出來的物件可以直接修改bar函數的原型鏈,這也就是原型鏈污染。所以我們採用繼承的方式(將建構函式的原型鏈賦值為父級建構子的實例),讓new出來的物件的原型鏈與bar脫離關係。

2.判斷目前被呼叫時,this是用於普通的bind還是用於建構函數從而更改this指向。

如何判斷當前this指向了哪裡呢,透過第一點我們已經知道,透過bind方法返回的新函數已經有了原型鏈,剩下需要我們做的就是改變this的指向就可以模擬完成了。透過什麼來判斷當前被召喚是以何種姿勢呢。答案是instanceof 。

instanceof 運算子用來測試一個物件在其原型鏈中是否存在一個建構函式的 prototype 屬性。

// 定义构造函数
function C(){} 
function D(){} 
var o = new C();
// true,因为 Object.getPrototypeOf(o) === C.prototype
o instanceof C; 
// false,因为 D.prototype不在o的原型链上
o instanceof D;

從上面可以看出,instanceof可以判斷出一個物件是否是由這個函數new出來的,如果是new出來的,那麼這個物件的原型鏈應為該函數的prototype.

所以我們來看這段關鍵的返回的函數結構:

var resultFunc = function() {
  return that.apply(this instanceof that ? 
    this : 
    newThis, 
    aArgs.concat(Array.prototype.slice.call(arguments)))
 }

在這其中我們要先認清this instanceof that 中的this是bind函數被調用後,返回的新函數中的this。所以這個this可能執行在普通的作用域環境,同時也可能被new一下從而改變自己的指向。再看that,that總是指向了bar,同時其原型鏈that.prototype是一直存在的。所以如果現在這個新函數要做new操作,那麼this指向了新函數,那麼 this instanceof that === true, 所以在apply中傳入this為指向,即指向新函數。如果是普通調用,那麼this不是被new出來的,即新函數不是作為構造函數,this instanceof that === false就很顯而易見了。這個時候是正常的bind呼叫。將呼叫的第一個參數作為this的指向即可。

完整代码(MDN下的实现)

if (!Function.prototype.bind) {
 Function.prototype.bind = function(oThis) {
  if (typeof this !== &#39;function&#39;) {
   // closest thing possible to the ECMAScript 5
   // internal IsCallable function
   throw new TypeError(&#39;Function.prototype.bind - what is trying to be bound is not callable&#39;);
  }

  var aArgs  = Array.prototype.slice.call(arguments, 1),
    fToBind = this,
    fNOP  = function() {},
    fBound = function() {
     return fToBind.apply(this instanceof fNOP
         ? this
         : oThis,
         aArgs.concat(Array.prototype.slice.call(arguments)));
    };

  if (this.prototype) {
   // Function.prototype doesn&#39;t have a prototype property
   fNOP.prototype = this.prototype; 
  }
  fBound.prototype = new fNOP();
  return fBound;
 };
}

可以看到,其首先做了当前是否支持bind的判定,不支持再实行兼容。同时判断调用这个方法的对象是否是个函数,如果不是则报错。

同时这个模拟的方法也有一些缺陷,可关注MDN上的Polyfill部分

小结

模拟bind实现最大的一个缺陷是,模拟出来的函数中会一直存在prototype属性,但是原生的bind作为构造函数是没有prototype的,这点打印一下即可知。不过这样子new出来的实例没有原型链,那么它的意义是什么呢。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

MVVM框架如何解析双向绑定

JS运动特效

JS中链式运动(详细教程)

以上是在Javascript中如何實作bind的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn