这是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...)