JavaScript系列之bind方法原生实现

逐步实现bind方法的特性

一、主要作用:改变执行上下文,并返回bind后的方法。(利用函数柯里化)

Function.prototype.bind = function(context){
  var self = this;
  return function(){
    self.apply(context);
  }
}

//测试代码
//即使用原生bind方法,执行结果也是一样,可以把下面代码直接copy到chrome测试原生。
var obj = {
  name: "hua",
  age: 30
}
function foo(){
  console.log(this.name); //"hua"
  console.log(this.age); //30
}
var bFoo = foo.bind(obj);
bFoo();

call/apply/bind其实都是改变上下方执行还境的方法,但各有不同,简单说一下call/apply不同之处

function add(a, b){
  console.log(this);//{name: "shi"}
  return a + b;
}
var obj = {
  name: "shi"
}
add.call(obj, 1, 2); //3
add.apply(obj, [1, 2]); //3

上面代码可以看出call/apply不同之处,call(context,参数1,参数2),apply(context,[参数1,参数数2])

二、传参

  1. bind时传参
  2. 执行时传参
Function.prototype.bind = function(context){
 var self = this,
     //缓存原型方法,提升性能。
     slice = Array.prototype.slice,
     //获取除第一个以外的参数,第一个参数是上下文,其它才是真正的参数。
     args = slice.call(arguments, 1);
 return function(){
   //用bind时的参数合并执行时的参数,等于所有参数
   self.apply(context, args.concat(slice.call(arguments)));
 }
}

//测试代码
//即使用原生bind方法,执行结果也是一样,可以把下面代码直接copy到chrome测试原生。
var obj = {
 name: "hua",
 age: 30
}
function foo(p1){
 console.log(this.name); //"hua"
 console.log(Array.prototype.slice.call(arguments)); //[1, 2, 2, 3, 2]
 console.log(p1) //1
}
var bFoo = foo.bind(obj, 1, 2);
bFoo(2,3,2);

三. bind后的方法可以作为构造函数,也就是可以使用new操作符。

  1. bind时的context值会失效

  2. bind时的参数变为构造函数参数

Function.prototype.bind = function(context){
  var self = this,
      //缓存原型方法,提升性能。
      slice = Array.prototype.slice,
      //获取除第一个以外的参数,第一个参数是上下文,其它才是真正的参数。
      args = slice.call(arguments, 1),
      //返回函数
      fBound = function(){
        self.apply(
          //如果是new操作符实例出来对象,则为this。
          //如果是直接执行方法,则为context
          this instanceof self ? this : context, 
          //用bind时的参数合并执行时的参数,等于所有参数
          args.concat(slice.call(arguments))
        );
      }
  
  //让返回函数拥有bind函数的原型。
  fBound.prototype = this.prototype;
  return fBound;
}

//测试代码
//即使用原生bind方法,执行结果也是一样,可以把下面代码直接copy到chrome测试原生。
var obj = {
  name: "hua",
  age: 30
}
function foo(p1){
  console.log(this.name); //undefined
  console.log(Array.prototype.slice.call(arguments)); //[1, 2, 2, 3, 2]
  console.log(p1) //1
}
var bFoo = foo.bind(obj, 1, 2);
new bFoo(2,3,2);

一切看似还可以,但还远没有达到要求。

四、bind返回的函数是没有原型的。但bind后方法new出来的对象拥有和原函数一样的方法。

(这其实是个很有用的设计,让一切都发生在原来函数上,这样出现bug也更容易查找,不用关心绑定后的函数。)

Function.prototype.bind = function(context){
  var self = this,
      //缓存原型方法,提升性能。
      slice = Array.prototype.slice,
      //获取除第一个以外的参数,第一个参数是上下文,其它才是真正的参数。
      args = slice.call(arguments, 1),
      //返回函数
      fBound = function(){
        //把原型链指向要bind操作的函数,也就是原函数。
        this.__proto__ = self.prototype;
        self.apply(
          //如果是new操作符实例出来对象,则为this。
          //如果是直接执行方法,则为context
          this instanceof self ? this : context, 
          //用bind时的参数合并执行时的参数,等于所有参数
          args.concat(slice.call(arguments))
        );
      }
  
  //bind后的方法是没有原型的,使其与浏览器原生表现一致
  fBound.prototype = undefined;
  return fBound;
}

//测试代码
//即使用原生bind方法,执行结果也是一样,可以把下面代码直接copy到chrome测试原生。
var obj = {
  name: "hua",
  age: 30
}
function foo(p1){
  console.log(this.name); //undefined
  console.log(Array.prototype.slice.call(arguments)); //[1, 2, 2, 3, 2]
  console.log(p1) //1
}
var bFoo = foo.bind(obj, 1, 2);
console.log(bFoo.prototype); //undefined
var newBindFoo = new bFoo(2,3,2);

foo.prototype.say = function(){
  console.log(this.name); //undefined
}

newBindFoo.say();

es6中的箭头函数(即没有prototype的方法)使得instanceof操作不再安全。

var fn = () => {}
fn.bind(null)() // Uncaught TypeError: Function has non-object prototype 'undefined' in instanceof check

class foo {
  bar(){
    console.log(this)
  }
}
var nFoo = new foo();
nFoo.bar.bind(nFoo)() // Uncaught TypeError: Function has non-object prototype 'undefined' in instanceof check

五、优化最终版

Function.prototype.bind = Function.prototype.bind || function(context){
  //只能对函数执行bind方法
  if (typeof this !== "function") {
    throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
  }

  var self = this,
      //缓存原型方法,提升性能。
      slice = Array.prototype.slice,
      //获取除第一个以外的参数,第一个参数是上下文,其它才是真正的参数。
      args = slice.call(arguments, 1),
      //返回函数
      fBound = function(){
        //把原型链指向要bind操作的函数,也就是原函数。
        //直接执行时this变得未知,所以加上try
        try {
          this.__proto__ = self.prototype;
        } catch(e) {}
        //怎么使用bind后的方法,new或者直接执行
        var isNew = self.prototype ? this instanceof self : false;
        self.apply(
          //如果是new操作符实例出来对象,则为this。
          //如果是直接执行方法,则为context
          isNew ? this : context, 
          //用bind时的参数合并执行时的参数,等于所有参数
          args.concat(slice.call(arguments))
        );
      }
  
  //bind后的方法是没有原型的,使其与浏览器原生表现一致
  fBound.prototype = undefined;
  return fBound;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值