Javascript天下第一 !
接上一篇文章,我们来一起实现一下bind方法
老规矩,来分析下bind的作用以及参数,返回值等信息
bind方法的全称是Function.prototype.bind(),官方是这么介绍的
bind()方法创建一个新的函数,在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项
官方的描述直接介绍了bind做了什么
- 返回了一个新函数
- 将this(传入的参数)关键字绑定到该函数
- 参数合并,将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
}
好了,大功告成!