bind() 函數會建立一個新函數(稱爲綁定函數),新函數與被調函數(綁定函數的目標函數)具備相同的函數體(在 ECMAScript 5 規範中內置的call屬性)。當目標函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。一個綁定函數也能使用new操做符建立對象:這種行爲就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。
javascript
因爲javascript
中做用域是由其運行時候所處的環境決定的,因此每每函數定義和實際運行的時候所處環境不同,那麼做用域也會發生相應的變化。
例以下面這個狀況:java
var id = 'window'; //定義一個函數,可是不當即執行 var test = function(){ console.log(this.id) } test() // window //把test做爲參數傳遞 var obj = { id:'obj', hehe:test } //此時test函數運行環境發生了改變 obj.hehe() // 'obj' //爲了不這種狀況,javascript裏面有一個bind方法能夠在函數運行以前就綁定其做用域,修改以下 var id = 'window'; var test = function(){ console.log(this.id) }.bind(window) var obj = { id:'obj', hehe:test } test() // window obj.hehe() // window
上面介紹了bind
方法的一個重要做用就是爲一個函數綁定做用域,可是bind
方法在低版本瀏覽器不兼容,這裏咱們能夠手動實現一下。數組
由於bind方法不會當即執行函數,須要返回一個待執行的函數(這裏用到閉包,能夠返回一個函數)return function(){}
瀏覽器
做用域綁定,這裏可使用apply或者call方法來實現 xx.call(yy)/xx.apply(yy)
閉包
參數傳遞,因爲參數的不肯定性,須要用apply傳遞數組(實例更明瞭
)xx.apply(yy,[...Array...]),若是用call就不太方便了,由於call後面的參數須要一個個列出來
app
有了上述的思路,大體的雛形已經明瞭了,代碼應該也很容易實現ide
Function.prototype.testBind = function(that){ var _this = this, /* *因爲參數的不肯定性,統一用arguments來處理,這裏的arguments只是一個類數組對象,有length屬性 *能夠用數組的slice方法轉化成標準格式數組,除了做用域對象that之外, *後面的全部參數都須要做爲數組參數傳遞 *Array.prototype.slice.apply(arguments,[1])/Array.prototype.slice.call(arguments,1) */ slice = Array.prototype.slice, args = slice.apply(arguments,[1]); //返回函數 return function(){ //apply綁定做用域,進行參數傳遞 return _this.apply(that,args) } }
測試函數
var test = function(a,b){ console.log('做用域綁定 '+ this.value) console.log('testBind參數傳遞 '+ a.value2) console.log('調用參數傳遞 ' + b) } var obj = { value:'ok' } var fun_new = test.testBind(obj,{value2:'also ok'}) fun_new ('hello bind') // 做用域綁定 ok // testBind參數傳遞 also ok // 調用參數傳遞 undefined
上面已經實現了bind
方法的做用域綁定,可是美中不足的是,既然咱們返回的是一個函數,調用的時候應該支持傳遞參數,很顯然,上面的 fun_new
調用的時候並不支持傳參,只能在 testBind
綁定的時候傳遞參數,由於咱們最終調用的是這個返回函數測試
function(){ return _this.apply(that,args) } 這裏面的args在綁定的時候就已經肯定了,調用的時候值已經固定, 咱們並無處理這個function傳遞的參數。
咱們對其進行改造this
return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) }
這裏的 Array.prototype.slice.apply(arguments,[0])
指的是這個返回函數執行的時候傳遞的一系列參數,因此是從第一個參數開始 [0]
,以前的args = slice.apply(arguments,[1])
指的是 testBind
方法執行時候傳遞的參數,因此從第二個開始 [1]
,兩則有本質區別,不能搞混,只有二者合併了以後纔是返回函數的完整參數
因此有以下實現
Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]); return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } }
測試
var test = function(a,b){ console.log('做用域綁定 '+ this.value) console.log('testBind參數傳遞 '+ a.value2) console.log('調用參數傳遞 ' + b) } var obj = { value:'ok' } var fun_new = test.testBind(obj,{value2:'also ok'}) fun_new ('hello bind') // 做用域綁定 ok // testBind參數傳遞 also ok // 調用參數傳遞 hello bind
在以上2種傳參方式中,bind
的優先級高,從 args.concat(Array.prototype.slice.apply(arguments,[0]))
也能夠看出來,bind
的參數在數組前面。
官方文檔上有一句話:
A bound function may also be constructed using the new operator: doing so acts as though the target function had instead been constructed. The provided this value is ignored, while prepended arguments are provided to the emulated function.
說明綁定事後的函數被new
實例化以後,須要繼承原函數的原型鏈方法,且綁定過程當中提供的this被忽略(繼承原函數的this對象),可是參數仍是會使用。
這裏就須要一箇中轉函數把原型鏈傳遞下去
fNOP = function () {} //建立一箇中轉函數 fNOP.prototype = this.prototype; xx.prototype = new fNOP() 修改以下 Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, //因此調用官方bind方法以後 有一個name屬性值爲 'bound ' bound = function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
並且bind
方法的第一個參數this
是能夠不傳的,須要分2種狀況
直接調用bind以後的方法
var f = function () { console.log('不傳默認爲'+this) };f.bind()() // 不傳默認爲 Window
因此直接調用綁定方法時候
apply(that,
建議改成 apply(that||window,
,其實不改也能夠,由於不傳默認指向window
使用new
實例化被綁定的方法
容易糊塗,重點在於弄清楚標準的bind方法在new的時候作的事情,而後就能夠清晰的實現
這裏咱們須要看看 new
這個方法作了哪些操做 好比說 var a = new b()
建立一個空對象 a = {}
,而且this
變量引用指向到這個空對象a
繼承被實例化函數的原型 :a.__proto__ = b.prototype
被實例化方法b
的this
對象的屬性和方法將被加入到這個新的 this
引用的對象中: b
的屬性和方法被加入的 a
裏面
新建立的對象由 this
所引用 :b.call(a)
經過以上能夠得知,若是是var after_new = new bindFun();
因爲這種行爲是把原函數當成構造器,那麼那麼最終實例化以後的對象 this
須要繼承自原函數, 而這裏的 bindFun
目前是
function(){ return _this.apply(that || window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) }
這裏apply
的做用域是綁定的that || window
,在執行 testBind()
的時候就已經固定,並無把原函數的this對象繼承過來,不符合咱們的要求,咱們須要根據apply的特性解決這個問題:
在一個子構造函數中,你能夠經過調用父構造函數的 `apply/call` 方法來實現繼承 例如
function Product(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' + this.name + ' with a negative price'); } } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } //等同於(其實就是把Product放在Food內部執行了一次) function Food(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' + this.name + ' with a negative price'); } this.category = 'food'; }
因此在new
新的實例的時候實時將這個新的this
對象 進行 apply
繼承原函數的 this
對象,就能夠達到 new
方法裏面的第 3 步的結果
apply(that||window, //修改成 若是是new的狀況,須要綁定new以後的做用域,this指向新的實例對象 apply(isNew ? this : that||window, ==> Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, //因此調用官方bind方法以後 有一個name屬性值爲 'bound ' bound = function(){ return _this.apply(isNew ? this : that||window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
這裏的 isNew
是區分 bindFun
是直接調用仍是被 new
以後再調用,經過原型鏈的繼承關係能夠知道,bindFun
屬於 after_new
的父類,因此 after_new instanceof bindFun 爲 true,
同時bindFun.prototype = new fNOP()
原型繼承; 因此 fNOP
也是 after_new
的父類, after_new instanceof fNOP 爲 true
Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, bound = function(){ //這裏的this指的是調用時候的環境 return _this.apply(this instanceof fNOP ? this : that||window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
我看到有些地方寫的是
this instanceof fNOP && that ? this : that || window,
我我的以爲這裏有點不正確,若是綁定時候不傳參數,那麼that
就爲空,那不管怎樣就只能綁定 window做用域了。
以上是我的看法,不對的地方望指導,謝謝!