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