手写之前
我们有一个函数foo。
已知:
- foo的隐式原型是绑定在Function的显式原型上的(Function是一个构造函数)
function foo(){}
console.log(foo.__proto__===Function.prototype);//true
- apply、call、bind方法是来自Function的原型对象
console.log(Function.prototype.apply);//ƒ apply() { [native code] }
- 也就是说:在Function的原型对象上添加的属性或方法, 可以被所有的函数获取
apply
获取thisArg, 我们要确保是一个对象类型,要进行边界判断
Function.prototype.MyApply = function(thisArg){
thisArg=(thisArg===undefined||thisArg===null)?window:Object(thisArg);
};
将this绑定到传入的参数thisArg上
我们想实现apply显式绑定,就只能用:默认绑定、隐式绑定、new绑定来实现。(总不能自己实现自己)。
默认绑定肯定不是我们要的,因为显式绑定并不希望帮到window
上。
隐式绑定可以。
new绑定要用到构造函数,略显复杂。
所以我们用隐式绑定。
Function.prototype.MyApply = function(thisArg){
thisArg=(thisArg===undefined||thisArg===null)?window:Object(thisArg);
//这步
//当读取thisArg.fn时返回this,相当于thisArg.fn=this
//这里创建一个属性fn,它的值为this
Object.defineProperty(thisArg,"fn",{
configurable:true,//true,否则没法delete
value:this,
});
//隐式绑定:让this指向thisArg
thisArg.fn();
//完成后删除thisArg中的fn属性
delete thisArg.fn;
};
将剩余的其他参数传入thisArg
apply传入的其他参数是一个数组。
function foo(name, age) {
console.log(this, name, age); // {name: 'kaisa'} 'kaisa' 18
}
Function.prototype.myapply = function (thisArg, arrArgs) {
thisArg =thisArg === undefined || thisArg === null ? window : Object(thisArg);
Object.defineProperty(thisArg, "fn", {
configurable: true,
value: this,
});
// 通过展开运算符, 将其他参数传入thisArg
thisArg.fn(...arrArgs);
delete thisArg.fn;
};
foo.myapply({ name: "kaisa" }, ["kaisa", 18]);
call
call传入的其他参数是参数列表。
function foo(name, age) {
console.log(this, name, age);
}
Function.prototype.MyCall = function (thisArg,...arrArgs) {
thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
Object.defineProperty(thisArg, "fn", {
configurable: true,
value: this,
});
thisArg.fn(...arrArgs);
delete thisArg.fn;
};
foo.MyCall({name:"abc"},"abc", 18);
封装函数实现apply和call
显然,apply和call的函数有重复的代码。因此,我们可以把重复代码封装成一个函数。
function repFn(thisArg,otherArgs,fn){
thisArg= (thisArg===null||thisArg===undefined)?window:Object(thisArg);
Object.defineProperty(thisArg,"fn",{
configurable: true,
value: fn,
})
thisArg.fn(...otherArgs);
delete thisArg.fn;
}
//apply
Function.prototype.MyApply=function(thisArg,otherArgs){
repFn(thisArg,otherArgs,this);
}
//call
Function.prototype.MyCall=function(thisArg,...otherArgs){
repFn(thisArg,otherArgs,this);
}
测试:
//测试
function foo1(name,age){
console.log(this,name,age);
}
function foo2(name,age){
console.log(this,name,age);
}
foo1.MyApply({name:"apply"},["apply",19]) //{name: 'apply', fn: ƒ} 'apply' 19
foo2.MyCall({name:"call"},"call",20)//{name: 'call', fn: ƒ} 'call' 20
bind
bind函数会返回一个新的函数,返回的新函数中可以传参数。
function foo(name,age,height){
console.log(this,name,age,height);
}
Function.prototype.MyBind=function(thisArg,...otherArgs){
thisArg= thisArg===null||thisArg===undefined?window:Object(thisArg);
Object.defineProperty(thisArg,"fn",{
configurable:true,
value:this
})
return (...newArgs) =>{
var allArgs=[...otherArgs,...newArgs];
thisArg.fn(...allArgs);
};
};
var newFoo=foo.MyBind({name:"bind"},"bind");
newFoo(18,1.88);//{name: 'bind', fn: ƒ} 'bind' 18 1.88
参考
coderwhy的课
手写apply-call-bind
手写 实现call、apply和bind方法 超详细!!!
使用JS简单实现一下apply、call和bind方法
Function