apply、call 和 bind 实现
在前面的文章中介绍过了,apply 和bind 的作用就是显示改变函数执行的this的绑定。
apply 和 call 是执行函数并该改变this,二者的参数有所区别
而bind则是 返回一个待执行的新函数, 当新函数执行的时候改变了this的指向。
所以,bind的情况会与apply和call不同。
- 我们知道在js里函数是可以被new的 因此情况会比 apply 和 call 多一种情况。
- bind 是可以预设参数,
相同情况是:
这三个函数对于没有参数 都指向的window 对于原始类型 都会包装成对象的形式
apply的实现
Function.prototype.myapply = function(context){
context = context ? Object(context) : window; // 确定对象
var fn = Symbol();
context[fn] = this; // 绑定到当前对象上
let result;
let arr = [...arguments].slice(1); // 获取参数
// 执行函数
if (!arr.length) {
result = context[fn]();
} else {
result = context[fn](arr);
}
delete context[fn]; // 删除绑定
return result;
}
call的实现
Function.prototype.mycall = function(context){
context = context ? Object(context) : window;
let fn = Symbol();
// 绑定到当前对象上
context[fn] = this;
// 获取参数
let args = [...arguments].slice(1);
// 执行函数
let result = context[fn](...args);
// 删除绑定
delete context[fn]
return result;
}
bind的实现
如果参照 apply 和 call 的想法,实现方式如下:
Function.prototype.mybind = function(context, ...perAgrs){
context = context ? Object(context) : window;
let fn = this; // 记录函数
let fnBound = function(...args){
fn.apply(context, perAgrs.concat(args) )
}
return fnBound;
}
此时我们正常的绑定函数是没有问题的。
但是在 new 绑定后的函数时候就会出现问题
function fn(a){
this.a = a;
}
let t1 = {a:2};
let t2 = {a:2};
let mybindFn = fn.mybind(t1);
let bindFn = fn.bind(t2);
let objmybindFn = new mybindFn(1);
let objbindFn = new bindFn(1);
let objn = new fn(1);
console.log(objmybindFn); // fn {a: 1}
console.log(objbindFn); // fnBound {}
我们会发现这两结果是不一样的,
objmybindFn是 fnBound 实例化后的对象。
objbindFn则是 fn实例化后的对象。
其实对于objmybindFn的结果,我们也很容易理解,构造函数就是fnBound。而在fnBound的函数体内执行语句是fn.apply(context, perAgrs.concat(args) )
所以new值设置在t2上了。
可是我们发现原生bind的实现并不是这样的。他使用的构造函数是fn, 而且他也不会改变t1。你可以认为是直接new fn(2).
到此,我们需要解决两个问题。
- 判断 new 还是 函数执行,从而确定
fn.apply(context, perAgrs.concat(args) )
的context是谁。 - 原型对象的改写。
解决方法: instanceof
可以做原型链的检查, 判断当前对象是都 new 出来的。 用于确定 context是谁。- 重写 fnBound 的原型对象(方法很多)
- 直接让
fnBound.prototype = fn.prototype
, 这样在改写fnBound.prototype
时候会影响fn.prototype
fnBound.prototype = new fn()
,fnBound.prototype = Object.create(fn.prototype)
其实是 2和3 是在 1的中间多加了一个对象, 但是原型链却相连接,这样在改写fnBound.prototype
的时候只会改写创建出来的对象,但是访问的时候却可以通过原型链访问到fn.prototype
。
因此注意构建出来的对象,不能覆盖fn.prototype
上的属性和方法。
- 直接让
实现
Function.prototype.mybind = function(context, ...perAgrs){
context = context ? Object(context) : window;
let fn = this; // 记录函数
let fnBound = function(...args){
fn.apply( this instanceof fnBound ? this : context, perAgrs.concat(args) )
}
fnBound.prototype = Object.create(fn.prototype);
return fnBound;
}
依然存在问题
function fn(a){
this.a = a;
}
let mybindFn = fn.mybind({a:2});
let bindFn = fn.bind({a:2});
let objmybindFn = new mybindFn(1);
let objbindFn = new bindFn(1);
let objn = new fn(1);
console.log(mybindFn.prototype) // {}
console.log(bindFn.prototype) // undefined
这里你就会发现,其实我们实现的结果和bind还是有所差异。