手动实现bind方法

Javascript天下第一 !

接上一篇文章,我们来一起实现一下bind方法
老规矩,来分析下bind的作用以及参数,返回值等信息
bind方法的全称是Function.prototype.bind(),官方是这么介绍的
bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项
官方的描述直接介绍了bind做了什么
  1. 返回了一个新函数
  2. 将this(传入的参数)关键字绑定到该函数
  3. 参数合并,将bind函数的参数与原来的函数参数合并作为参数传给创建的新的函数
  4. 返回该函数
【手动实现bind方法】但是bind与call和apply又有区别,一个函数被call的时候,会直接去调用,但是bind是会返回一个函数,当这个函数执行的时候,bind()的第一个参数将作为它运行时的this。
好了,bind就是做了这个事情,所以现在要一步步去实现它
  • 首先去创建一个函数,这个函数需要有原函数的prototype的值
Function.prototype._bind = function(){ //首先去缓存参数列表,避免直接更改参数列表 let _arguments = arguments; //类数组转换成数组 _arguments = Array.prototype.slice.call(_arguments); // 创建一个函数, let fn = function(){} //这个函数需要有所有的prototype的值 fn.prototype = this.prototype; }

  • 紧接着将this指向这个函数
Function.prototype._bind = function(){ //首先去缓存参数列表,避免直接更改参数列表 let _arguments = arguments; //类数组转换成数组 _arguments = Array.prototype.slice.call(_arguments); //拿到当前方法 let _this = this; //拿到指定的this值,shift操作会改变原数组 let target = Array.prototype.shift.call(_arguments); // 创建一个函数, let fn = function(){ _this.apply(target); } //这个函数需要有所有的prototype的值 fn.prototype = this.prototype; }

  • 然后合并参数(bind函数的参数与被bind函数的参数)
Function.prototype._bind = function(){ //首先去缓存参数列表,避免直接更改参数列表 let _arguments = arguments; //类数组转换成数组 _arguments = Array.prototype.slice.call(_arguments); //拿到当前方法 let _this = this; //拿到指定的this值,shift操作会改变原数组 let target = Array.prototype.shift.call(_arguments); // 创建一个函数, let fn = function(){ _this.apply(target,_arguments.concat(Array.prototype.slice.call(arguments))); } //这个函数需要有所有的prototype的值 fn.prototype = this.prototype; }

  • 最后将这个函数返回
Function.prototype._bind = function(){ //首先去缓存参数列表,避免直接更改参数列表 let _arguments = arguments; //类数组转换成数组 _arguments = Array.prototype.slice.call(_arguments); //拿到当前方法 let _this = this; //拿到指定的this值,shift操作会改变原数组 let target = Array.prototype.shift.call(_arguments); // 创建一个函数,并执行合并之后的参数 let fn = function(){ _this.apply(target,_arguments.concat(Array.prototype.slice.call(arguments))); } //添加原函数所有的prototype的值 fn.prototype = this.prototype; //最后返回这个方法 return fn }

如此一个简单的bind方法就已经实现了。但是这里有个问题,就是当bind之后的函数,如果被当作构造函数去new的话,new出来的实例的指针指的是原来的bind方法,而不是bind之后的方法,这里有点绕,我先用变量来代表一下:
  • A函数 ----代表原函数,也就是需要被bind的函数 A.bind(ctx);
  • B函数 ----代表bind之后的函数,也就是B = A.bind(ctx);
  • C函数 ----代表new一个B之后得到的实例函数 C = new B();
    也就是说,如果用上面写的自定义bind方法,new出来的C的构造函数是A函数,但是我们需要的并不是A函数,我们要的是B函数
function A(name){ this.name = name } var obj = {} var B = A._bind(obj) var C = new B(); //按照上一章节的原型规则中的第四条 //所有的引用类型(数组,对象,函数),__ proto__属性指向它的构造函数的prototype属性值,这里C的构造函数是B //但是这里却是false, console.log(C.__proto__ === B.prototype)//false //相反 console.log(C.__proto__ === A.prototype)//true

所以此时的C函数的构造函数是A并不是B,所以我们需要重新写一下上面的代码,需要判断返回的函数(B函数)是不是被当作构造函数使用的,怎么判断呢,其实很简单,判断B函数的this instanceof B是否是true,如果是的话,说明此时函数被当作构造函数来使用了,这个时候,apply里面的target不能使用外部传入的指针了,应该直接使用this,所以,整理一下,代码应该是这样:
Function.prototype._bind = function(){ //首先去缓存参数列表,避免直接更改参数列表 let _arguments = arguments; //类数组转换成数组 _arguments = Array.prototype.slice.call(_arguments); //拿到当前方法 let _this = this; //拿到指定的this值,shift操作会改变原数组 let target = Array.prototype.shift.call(_arguments); // 创建一个函数,并执行合并之后的参数 let fn = function(){ let ctx = this instanceof fn ? this : target _this.apply(ctx,_arguments.concat(Array.prototype.slice.call(arguments))); } //添加原函数所有的prototype的值 fn.prototype = this.prototype; //最后返回这个方法 return fn }

好了,这下是可以满足要求了,但是还是有一个小问题,由于对象属于引用类型,直接进行赋值语句操作的话,后面改动一个,另一个也会改变,因为指针指向的是同一个内存地址,所以上面代码中的最后一部分应该做一个clone
Function.prototype._bind = function(){ //首先去缓存参数列表,避免直接更改参数列表 let _arguments = arguments; //类数组转换成数组 _arguments = Array.prototype.slice.call(_arguments); //拿到当前方法 let _this = this; //拿到指定的this值,shift操作会改变原数组 let target = Array.prototype.shift.call(_arguments); // 创建一个函数,并执行合并之后的参数 let fn = function(){ let ctx = this instanceof fn ? this : target _this.apply(ctx,_arguments.concat(Array.prototype.slice.call(arguments))); } //添加原函数所有的prototype的值 fn.prototype = Object.create(this.prototype); //最后返回这个方法 return fn }

好了,大功告成!

    推荐阅读