摘要:綁定函數被調用時,也接受預設的參數提供給原函數。原型鏈官方文檔上有一句話說明綁定過后的函數被實例化之后,需要繼承原函數的原型鏈方法,且綁定過程中提供的被忽略繼承原函數的對象,但是參數還是會使用。
bind 官方描述
bind() 函數會創建一個新函數(稱為綁定函數),新函數與被調函數(綁定函數的目標函數)具有相同的函數體(在 ECMAScript 5 規范中內置的call屬性)。當目標函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。一個綁定函數也能使用new操作符創建對象:這種行為就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。
使用介紹由于javascript中作用域是由其運行時候所處的環境決定的,所以往往函數定義和實際運行的時候所處環境不一樣,那么作用域也會發生相應的變化。
例如下面這個情況:
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后面的參數需要一個個列出來
實現有了上述的思路,大致的雛形已經明了了,代碼應該也很容易實現
綁定作用域,綁定傳參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傳遞的參數。
我們對其進行改造
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作用域了。
以上是個人見解,不對的地方望指導,謝謝!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/80744.html
摘要:之父在中的設計錯誤演講中表示不允許將任意本地函數綁定至當中。所有系統調用都將通過消息傳遞完成序列化。兩項原生函數與。這既簡化了設計流程,又使得系統更易于審計。 Node之父ry:在Node中的設計錯誤演講中表示: 不允許將任意本地函數綁定至 V8 當中。 所有系統調用都將通過消息傳遞完成(protobuf 序列化)。 兩項原生函數:send 與 recv。 這既簡化了設計流程,又使得...
摘要:這里借鑒了一下的處理方式,我們把單獨模塊的包裝成一個函數,提供一個全局的回調方法,加載完成時候再調用回調函數。 前端路由實現之 #hash 先上github項目地址: spa-routers運行效果圖showImg(https://segmentfault.com/img/bVFi7l?w=581&h=312); 背景介紹 用了許多前端框架來做spa應用,比如說backbone,ang...
摘要:官方描述方法在指定值和參數參數以數組或類數組對象的形式存在的情況下調用某個函數。兩者基本一致,只有一個區別,就是方法接受的是若干個參數的列表,而方法接受的是一個包含多個參數的數組。 Function.prototype.apply() & Function.prototype.call() 官方描述 apply() 方法在指定 this 值和參數(參數以數組或類數組對象的形式存在)的...
摘要:模塊化是隨著前端技術的發展,前端代碼爆炸式增長后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調也不等同于異步。將會討論安全的類型檢測惰性載入函數凍結對象定時器等話題。 Vue.js 前后端同構方案之準備篇——代碼優化 目前 Vue.js 的火爆不亞于當初的 React,本人對寫代碼有潔癖,代碼也是藝術。此篇是準備篇,工欲善其事,必先利其器。我們先在代...
閱讀 2727·2021-11-22 13:52
閱讀 1192·2021-10-14 09:43
閱讀 3647·2019-08-30 15:56
閱讀 2956·2019-08-30 13:22
閱讀 3282·2019-08-30 13:10
閱讀 1571·2019-08-26 13:45
閱讀 1106·2019-08-26 11:47
閱讀 2800·2019-08-23 18:13