這是underscore.js源碼分析的第五篇,若是你對這個系列感興趣,歡迎點擊javascript
underscore-analysis/ watch一下,隨時能夠看到動態更新。java
事情要從js中的
this
開始提及,你是否是也常常有種沒法掌控和知曉它的感受,對於初學者來講,this
簡直如同回調地獄般,神乎其神,讓人沒法捉摸透。可是經過原生js中的bind方法,咱們能夠顯示綁定函數的this
做用域,而無需擔憂運行時是否會改變而不符合本身的預期。固然了下劃線中的bind也是模仿它的功能一樣能夠達到相似的效果。git
<!--more-->github
咱們從mdn上的介紹來回顧一下bind的使用方法。瀏覽器
bind方法建立一個新的函數, 當被調用時,它的this關鍵字被設置爲提供的值。app
語法函數
fun.bind(thisArg[, arg1[, arg2[, ...]]])
簡單地看一下這些參數的含義源碼分析
thisArgthis
當綁定函數被調用時,該參數會做爲原函數運行時的this指向,當使用new 操做符調用綁定函數時,該參數無效。spa
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 = 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感興趣,歡迎查看
[this-想說愛你不容易](https://qianlongo.github.io/2...)