摘要:方法創建一個新的函數當被調用時,它的關鍵字被設置為提供的值。語法簡單地看一下這些參數的含義當綁定函數被調用時,該參數會作為原函數運行時的指向當使用操作符調用綁定函數時,該參數無效。結尾文章很簡短,知道怎么實現一個原生的就行。
前言
這是underscore.js源碼分析的第五篇,如果你對這個系列感興趣,歡迎點擊
underscore-analysis/ watch一下,隨時可以看到動態更新。
bind簡單回顧事情要從js中的this開始說起,你是不是也經常有種無法掌控和知曉它的感覺,對于初學者來說,this簡直如同回調地獄般,神乎其神,讓人無法捉摸透。但是通過原生js中的bind方法,我們可以顯示綁定函數的this作用域,而無需擔心運行時是否會改變而不符合自己的預期。當然了下劃線中的bind也是模仿它的功能同樣可以達到類似的效果。
我們從mdn上的介紹來回顧一下bind的使用方法。
bind方法創建一個新的函數, 當被調用時,它的this關鍵字被設置為提供的值。
語法
fun.bind(thisArg[, arg1[, arg2[, ...]]])
簡單地看一下這些參數的含義
thisArg
當綁定函數被調用時,該參數會作為原函數運行時的this指向,當使用new 操作符調用綁定函數時,該參數無效。
arg1, arg2, ...
當綁定函數被調用時,這些參數將置于實參之前傳遞給被綁定的方法。
綁定this作用域示例
window.name = "windowName" let obj = { name: "qianlongo", showName () { console.log(this.name) } } obj.showName() // qianlongo let showName = obj.showName showName() // windowName let bindShowName = obj.showName.bind(obj) bindShowName() // qianlongo
通過以上簡單示例,我們知道了第一個參數的作用就是綁定函數運行時候的this指向
第二個參數開始起使用示例
let sum = (num1, num2) => { console.log(num1 + num2) } let bindSum = sum.bind(null, 1) bindSum(2) // 3
bind可以使一個函數擁有預設的初始參數。這些參數(如果有的話)作為bind的第二個參數跟在this(或其他對象)后面,之后它們會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們的后面。
參數的使用基本上明白了,我們再來看看使用new去調用bind之后的函數是怎么回事。
function Person (name, sex) { console.log(this) // Person {} this.name = name this.sex = sex } let obj = { age: 100 } let bindPerson = Person.bind(obj, "qianlongo") let p = new bindPerson("boy") console.log(p) // Person {name: "qianlongo", sex: "boy"}
有沒有發現bindPerson內部的this不再是bind的第一個參數obj,此時obj已經不再起效了。
實際上bind的使用是有一定限制的,在一些低版本瀏覽器下不可用,你想不想看看下劃線中是如何實現一個兼容性好的bind呢!!!come on
下劃線中bind實現源碼
_.bind = function(func, context) { // 如果原生支持bind函數,就用原生的,并將對應的參數傳進去 if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); // 如果傳入的func不是一個函數類型 就拋出異常 if (!_.isFunction(func)) throw new TypeError("Bind must be called on a function"); // 把第三個參數以后的值存起來,接下來請看executeBound var args = slice.call(arguments, 2); var bound = function() { return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; return bound; };
executeBound實現
var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { // 如果調用方式不是new func的形式就直接調用sourceFunc,并且給到對應的參數即可 if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); // 處理new調用的形式 var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; };
上面的源碼都做了相應的注釋,我們著重來看一下executeBound的實現
先看一下這些參數都代表什么含義
sourceFunc:原函數,待綁定函數
boundFunc: 綁定后函數
context:綁定后函數this指向的上下文
callingContext:綁定后函數的執行上下文,通常就是 this
args:綁定后的函數執行所需參數
ok,我們來看一下第一句
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
這句話是為了判斷綁定后的函數是以new關鍵字被調用還是普通的函數調用的方式,舉個例子
function Person () { if (!(this instanceof Person)) { return console.log("非new調用方式") } console.log("new 調用方式") } Person() // 非new調用方式 new Person() // new 調用方式
所以如果你希望自己寫的構造函數無論是new還是沒用new都起效的話可以用下面的代碼
function Person (name, sex) { if (!(this instanceof Person)) { return new Person(name, sex) } this.name = name this.sex = sex } new Person("qianlongo", "boy") // Person {name: "qianlongo", sex: "boy"} Person("qianlongo", "boy") // Person {name: "qianlongo", sex: "boy"}
我們回到executeBound的解析
if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
callingContext是被綁定后的函數的this作用域,boundFunc就是那個被綁定后的函數,那么通過這個if判斷,當為非new調用形式的時候,直接利用apply處理即可。
但是如果是用new調用的呢?我們看下面這段代碼,別看短短的四行代碼里面知識點挺多的呢!
// 這里拿到的是一個空對象,且其繼承于原函數的原型鏈prototype var self = baseCreate(sourceFunc.prototype); // 構造函數執行之后的返回值 // 一般情況下是沒有返回值的,也就是undefined // 但是有時候寫構造函數的時候會顯示地返回一個obj var result = sourceFunc.apply(self, args); // 所以去判斷結果是不是object,如果是那么返回構造函數返回的object if (_.isObject(result)) return result; // 如果沒有顯示返回object,就返回原函數執行結束后的實例 return self;
好,到這里,我有一個疑問,baseCreate是個什么鬼?
var Ctor = function(){}; var baseCreate = function(prototype) { // 如果prototype不是object類型直接返回空對象 if (!_.isObject(prototype)) return {}; // 如果原生支持create則用原生的 if (nativeCreate) return nativeCreate(prototype); // 將prototype賦值為Ctor構造函數的原型 Ctor.prototype = prototype; // 創建一個Ctor實例對象 var result = new Ctor; // 為了下一次使用,將原型清空 Ctor.prototype = null; // 最后將實例返回 return result; };
是不是好簡單,就是實現了原生的Object.create用來做一些繼承的事情。
結尾文章很簡短,知道怎么實現一個原生的bind就行。如果你對apply、call和this感興趣,歡迎查看
js中call、apply、bind那些事
[this-想說愛你不容易](https://qianlongo.github.io/2...
)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/50787.html
摘要:方法創建一個新的函數當被調用時,它的關鍵字被設置為提供的值。語法簡單地看一下這些參數的含義當綁定函數被調用時,該參數會作為原函數運行時的指向當使用操作符調用綁定函數時,該參數無效。結尾文章很簡短,知道怎么實現一個原生的就行。 前言 這是underscore.js源碼分析的第五篇,如果你對這個系列感興趣,歡迎點擊 underscore-analysis/ watch一下,隨時可以看到動態...
摘要:代碼方法返回一個新的數組對象原數組的淺拷貝,常用于動態解析參數。看過的童鞋,對這行代碼不會陌生。所以筆者覺得,從理解角度來看,新創建的函數命名為更便于理解。總結乍一看,函數有點繞,一經推敲還是很好理解的。 代碼 var slice = Function.prototype.call.bind(Array.prototype.slice); slice() 方法返回一個新的數組對象(原數...
摘要:音樂團隊分享數據綁定運行機制分析一個項目搞定所有主流架構單元測試一個項目搞定所有主流架構系列的第二個項目。代碼開源,展示了的用法,以及如何使用進行測試,還有用框架對的進行單元測試。 Android 常用三方框架的學習 Android 常用三方框架的學習 likfe/eventbus3-intellij-plugin AS 最新可用 eventbus3 插件,歡迎品嘗 簡單的 MVP 模...
摘要:好,師傅我們要學習帝吧人民,進能打,退能刷淘寶。恩,大致過程就是這樣,我們使用中介者模式想一想。首先,數據需要放在中介者模式內,用戶的一切操作,都會傳遞給中介者模式,由他來選擇是哪一個部分發生改變。 俗話說,一個模式三個坑。 中介者模式應該算最坑的一個模式,坑不在于他的原理。而在于他的名字和其他模式的使用,真尼瑪像。首先,中介者 好像是一切模式里面都有的一個東西,比如,享元模式中-元對...
閱讀 3247·2021-09-22 15:58
閱讀 1717·2019-08-30 14:17
閱讀 1716·2019-08-28 18:05
閱讀 1505·2019-08-26 13:33
閱讀 683·2019-08-26 12:20
閱讀 606·2019-08-26 12:18
閱讀 3192·2019-08-26 11:59
閱讀 1401·2019-08-26 10:36