bind的那些事

最近面头条的时候,要求本身手动实现一个bind函数,而后又问了bind还能干吗。这里就围绕这两个好好写一下。面试

首先bind的文档说明: (连接:传送门数组

bind()方法建立一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供以前提供一个给定的参数序列。
fun.bind(thisArg[, arg1[, arg2[, ...]]])
参数app

  • thisArg
    当绑定函数被调用时,该参数会做为原函数运行时的 this 指向。当使用new 操做符调用绑定函数时,该参数无效。
  • arg1, arg2, ...
    当绑定函数被调用时,这些参数将置于实参以前传递给被绑定的方法。
  • 返回值
    返回由指定的this值和初始化参数改造的原函数拷贝

bind() 函数会建立(返回)一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具备相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当新函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。(注意这句话是重点,也就是说bind支持了这个功能)函数

通常新手分不清于call,apply的关系,call/apply通常会伴随这函数的调用的,而bind只是返回了带新this的函数,将在下次调用。通常用法是调用bind后赋给一个变量,支持调用的时候继续传参。this

咱们通常快速实现一个bind:prototype

/*

函数体内的this,就是须要绑定this的实例函数,或者说是原函数。最后咱们使用apply来进行参数(context)绑定,并返回。
同时,将第一个参数(context)之外的其余参数,做为提供给原函数的预设参数,这也是基本的柯里化基础。(应该是偏函数)

*/
Function.prototype.bind =  Function.prototype.bind || function(context) {
    var self = this;
    arg = Array.prototype.slice.call(arguments,1);
    return function() {
        return self.apply(context,arr);
    }
}

可是这个实现有个问题,咱们将参数限定了arguments.slice(1),咱们返回的绑定函数中,若是想实现预设传参,上个代码就不能知足了。
eg:code

function sum(a, b) {
    console.log(a + b)
}

var sum2 = sum.bind(null,2);   // 固定参数a, 值为2
sum2(4)                        // 传入参数b, 值为4, 结果为6 重在咱们在调用函数的时候能够预设传参

那么咱们将上个bind的实现更完善一下:对象

Function.prototype.bind =  Function.prototype.bind || function(context) {
    var self = this;
    var args = Array.prototype.slice.call(arguments,1);
    return function() {
        var innerArgs = Array.prototype.slice.call(arguments);
        var FinalArgs = args.concat(innerArgs);
        return self.apply(context,FinalArgs);
    }
}

这样就是实现了偏函数功能,在一些资料里是说“柯里化”,我以为仍是有点区别的,共同特色是实现了参数复用。继承

关于柯里化和偏函数举个小例子就知道了:
假设有一个Add(x,y,z)函数,接收x,y,z三个参数,返回x+y+zip

  • 偏函数

AddBySeven =Otherbind(Add, 7);
AddBySeven(5, 10); // returns 22;
这是偏函数,固定了你函数的某一个或几个参数,返回一个新的函数,接收剩下的参数, 参数个数多是1个,也多是2个,甚至更多。

  • 柯里化:把一个有n个参数的函数变成n个只有1个参数的函数

curryAdd = Curry(Add);
AddBySeven = curryAdd(7);
AddBySeven(5)(10); // returns 22
// curryAdd(7)(5)(10)

Add = (x, y, z) => x + y + z
变成了CurryAdd = x => y => z => x + y + z

不少资料有的叫柯里化有的叫偏函数,这点我以为仍是读者本身判断把。

到这里可能你们以为上面的实现已经完美了,可是JS的坑是补不完的,问题又来了!
看过文档的就知道,在文档上介绍bind时还说了这点:

一个绑定函数也能使用new操做符建立对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

那么若是bind返回的函数当作构造函数调用的时候,咱们就须要在内部从新构造原型链了。因此更兼容的写法来了:

Function.prototype.bind =  Function.prototype.bind || function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
    var self = this;
    var args = Array.prototype.slice.call(arguments,1);
    var Fun = {};
    Fun.prototype = this.prototype;//继承原来函数
    var Comb = function(){
        var innerArgs = Array.prototype.slice.call(arguments);
        var FinalArgs = args.concat(innerArgs);
        return self.apply(this instanceof Fun ? this : context || this ,FinalArgs);
    }
    Comb.prototype = new Fun();
    return Comb;
    }

这里的点:

  1. 第一个this是第一次调用bind的函数,也必须是函数,因此在以前就作了一个容错判断。

  2. 若是最后咱们是以new 调用bind返回的函数,即当作构造函数调用,那么这里的this就是Comb的实例,这时候由于Fun继承了以前调用的函数,Comb又new了Fun,Comb便是Fun的派生类,所以 this instanceof fNOP === true,这时候无视 bind 效果,所以 this 该是什么仍是什么。模仿了本来bind的feature,若是这个条件判断失败,则使用context 去替换 this。若是context没传,那么this该是什么就是什么。

但这里再想一想面试官当时的的问题,"bind还能干吗",其实这个bind就是改变上下文还能干吗,其实面试管的意思是利用bind的这个feature能够干吗

把bind自己的做用讲讲,在把上面bind自己的偏函数功能(容许第一次传参不彻底,后面调用能够继续传参),本身实现的偏函数,做为new 构造函数来调用这些讲讲,就够了。

最后再加一个bind的小功能把,日常咱们转换伪数组,一般是使用:

var slice = Array.prototype.slice;

// ...

slice.call(arguments);//arguments是一个伪数组

若是咱们用bind把对象参数绑定到call上返回给slice,每次就不用调用call了,并且还不影响原函数的this:

var combSlice = Array.prototype.slice;
var slice = Function.prototype.call.bind(combSlice);

// ...

slice(arguments);//slice是一个新函数

好了,bind的这些事到此结束,欢迎在下方交流评论。

相关文章
相关标签/搜索