js模拟实现bind

bind

原文详见: JavaScript深入之bind的模拟实现
一句话介绍 bind (来自于 MDN ):

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

由此我们可以首先得出 bind 函数的两个特点:
1.返回一个新函数
2.可以传入参数

返回函数的模拟实现

从第一个特点开始,我们举个例子:

var value = 2;
var foo = {
  value: 1
}
function bar() {
  console.log(this.value);
}
//返回了一个新函数
var bindFoo = bar.bind(foo);
bindFoo(); 
//1

关于指定 this 的指向,我们可以使用 call 或者 apply 实现。我们来写第一版的代码:

//第一版
Function.prototype.bind2 = function(context) {
    var self = this;
    return function() {
    //之所以 return self.apply(context),
    //是考虑到绑定函数可能是有返回值的  
      return self.apply(context);
    }
}
var value = 2;
var foo = {
  value: 1
}
function bar() {
  console.log(this.value);
}
var bindFoo = bar.bind2(foo);
bindFoo();
//1

传参的模拟实现

接下来看第二点,可以传入参数。这个就有点让人费解了,我在 bind 的时候,是否可以传参呢?我在执行 bind 返回的函数的时候,可不可以传参呢?让我们看个例子:

var foo = {
    value: 1
};

function bar(name, age) {
    console.log(this.value);
    console.log(name);
    console.log(age);

}

var bindFoo = bar.bind(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18

函数需要传 name 和 age 两个参数,竟然还可以在 bind 的时候,只传一个 name,在执行返回的函数的时候,再传另一个参数 age!

这可咋办?不急,我们用 arguments 进行处理:

//第二版
Function.prototype.bind2 = function(context) {
  var self = this;
  var args = Array.prototype.slice.call(arguments,1);

  //执行bind返回的函数的时候才会走到执行function
  return function() {
    //这时候的arguments是指向bind返回函数传入的参数
    var bindArgs = Array.prototype.slice.call(arguments);
    return self.apply(context, args.concat(bindArgs));
  }
}

注意:Array.prototype.slice.call 改变this的指向指向arguments 并且slice能转化arguments类数组为数组实现slice截取的作用,并返回一个新数组。还可以写成[].slice.call(); 因为[]是Array的一个实例。

构造函数效果的模拟实现

bind 还有一个特点,就是

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

也就是说当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。举个例子:

var value = 2;

var foo = {
    value: 1
};

function bar(name, age) {
    this.habit = 'shopping';
    console.log(this.value);
    console.log(name);
    console.log(age);
}
bar.prototype.friend = 'kevin';
var bindFoo = bar.bind(foo, 'daisy');
var obj = new bindFoo('18');
// undefined
// daisy
// 18
console.log(obj.habit);
console.log(obj.friend);
// shopping
// kevin

注意:尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的 this 失效了,如果大家了解 new 的模拟实现,就会知道这个时候的 this 已经指向了 obj。

所以我们可以通过修改返回的函数的原型来实现,让我们写一下:

//第三版
Function.prototype.bind2 = function(context) {
  var self= this;
  var args = [].slice.call(arguments,1);
  var fBood = function() {
     var newArgs = [].slice.call(arguments);
 // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,
//可以让实例获得来自绑定函数的值
// 以上面的是 demo 为例,如果改成 `this instanceof fBound ? null : context`,
//实例只是一个空对象,将 null 改成 this ,实例会具有 habit 属性
 // 当作为普通函数时,this 指向 window
//此时结果为 false,将绑定函数的 this 指向 context
     return self.apply(this instanceof fBood? this: context,args.concat(newArgs));
  }
  // 修改返回函数的 prototype 为绑定函数的 prototype,
//实例就可以继承绑定函数的原型中的值
   fBood.prototype = this.prototype;
  return fBood;
}

注意1:this instanceof fBound 用来检测 构造函数的prototype属性是否出现在某个实例对象的原型链上。
如果你用new 来调用一个bind过的函数,这时候this instanceof fBood === true ,这时候就无视bind的效果(this失效),因此this该是什么就是什么。

注意2:fBood.prototype = this.prototype 为了让fBood构造函数实例能够继承绑定函数的原型中的值

构造函数效果的优化实现

但是在这个写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。
举个例子:

Function.prototype.bind2 = function(context) {
  var self = this;
  var args = Array.prototype.slice.call(arguments,1);
  var fBood = function() {
    var newArgs = [].slice.call(arguments);
    self.apply(this instanceof fBood? this: context, args.concat(newArgs));
  }
  fBood.prototype = this.prototype;
  return fBood;
}
function bar() {}
var bindFoo = bar.bind(null);
bindFoo.prototype.value = 1;
console.log(bar.prototype.value); //1

(你会发现我们明明修改的是 bindFoo.prototype ,但是 bar.prototype 的值也被修改了,这就是因为 fBound.prototype = this.prototype导致的。)

//第四版
Function.prototype.bind2 = function(context) {
  var self = this;
  var args = [].slice.call(arguments, 1);
  var fBood = function() {
    var newArgs = [].slice.call(arguments);
    self.apply(this instanceof fBood? this: context,args.concat(newArgs));
  }
  fNOP.peototype = this.prototype;
  fBood.prototype = new fNOP();
  return fBood;
}

到此为止,大的问题都已经解决,给自己一个赞!o( ̄▽ ̄)d

小问题

调用 bind 的不是函数咋办?

不行,我们要报错!

if(typeof this !== "function") {
  throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

我要在线上用
那别忘了做个兼容:

Function.prototype.bind = Function.prototype.bind || function () {
    ……
};

最终代码

Function.prototype.bind2 = Function.prototype.bind|| function() {
  if(typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  }
  var self = this;
  var args = [].slice.call(arguments, 1);
  var fNOP = function () {};
  var fBood = function() {
    var newArgs = [].slice.call(arguments);
    self.apply(this instanceof fBood? this: context, args.concat(newArgs));
  }
  fNOP.prototype = this.prototype;
  fBood.prototype= new fNOP();
  return fBood;
}

加油~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容