bind函数源码解析

时间:2022-01-03 17:16:10

bind函数

ES5中原生bind函数是用于绑定this指向的

   var obj = {
x: 1
}
function show(){
console.log(this.x);
}
var newShow = show.bind(obj);
newShow(); // 1

bind方法会创建一个新函数。当这个新函数被调用时,bind的第一个参数将作为它运行时的this,之后的一序列参数将会在传递的实参前传入作为它的参数。
bind返回的绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的this值被忽略,同时调用时的参数被提供给模拟函数

初级实现

我们来实现一个初级的bind函数Polyfill:

   Function.prototype.bind = function(context){
//that为调用bind函数的对象
var that = this;
//bind函数可能入其他参数,先将参数类数组转化为数组
var argsArray = Array.prototype.slice.call(arguments);
return function(){
// slice只传一个参数,返回从参数开始到数组末尾的元素
// slice(1)之后为其他参数
return that.call(context, argsArray.slice(1))
}
}

译者注:如果对这个函数有所不理解,我们可以先看一下不改变this的改造

   function a(){ console.log(1) };
Function.prototype.bind = function(){
return this;
}
var b = a.bind();
b(); // 1

进行嗅探增加程序健壮性

其实是一个典型的“Monkey patching(猴子补丁)”,即“给内置对象扩展方法”。所以,如果能进行一下“嗅探”,进行兼容处理,就是锦上添花了。

   Function.prototype.bind = Function.prototype.bind || function(context){
//...
}

柯里化(curring)实现

我们返回的参数列表里包含:atgsArray.slice(1),他的问题在于存在函数的参数丢失的现象。

   Function.prototype.bind = Function.prototype.bind || function (context) {
var that = this;
//通过bind预先传入的参数
var args = Array.prototype.slice.call(arguments, 1);
return function () {
//函数调用时传入的参数
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return that.apply(context, finalArgs);
}
}

但是,我们注意在上边bind方法介绍的第三条提到:bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们的绑定this就需要“被忽略”

构造函数场景下的兼容

直接上代码

    Function.prototype.bind = Function.prototype.bind || function (context) {
var that = this;
var args = Array.prototype.slice.call(arguments, 1);
var F = function () {};
F.prototype = this.prototype;
var bound = function () {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return that.apply(this instanceof F ? this : context || this, finalArgs);
}
//把新函数的原型清空
bound.prototype = new F();
return bound;
}

更严谨的做法

我们需要调用bind方法的一定要是一个函数,所以可以在函数体内做一个判断:

    if (typeof this !== "function") {
//使用类型错误
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}

一切还没完

es5-shim源码如下所示

   bind: function bind(that) {
var target = this;
if (!isCallable(target)) {
throw new TypeError('Function.prototype.bind called on incompatible ' + target);
}
var args = array_slice.call(arguments, 1);
var bound;
var binder = function () {
if (this instanceof bound) {
var result = target.apply(
this,
array_concat.call(args, array_slice.call(arguments))
);
if ($Object(result) === result) {
return result;
}
return this;
} else {
return target.apply(
that,
array_concat.call(args, array_slice.call(arguments))
);
}
};
var boundLength = max(0, target.length - args.length);
var boundArgs = [];
for (var i = 0; i < boundLength; i++) {
array_push.call(boundArgs, '$' + i);
}
bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);

if (target.prototype) {
Empty.prototype = target.prototype;
bound.prototype = new Empty();
Empty.prototype = null;
}
return bound;
}