call和apply共同点
call和apply都是为了改变某个函数运行时内部this的指向,会立即调用该函数
call和apply不同点
call方法接收的是若干个参数的列表
apply方法接收的是一个包含多个参数的数组
call方法使用说明
let bar = {
sex: "男"
};
function foo(age) {
console.log(this,this.sex, age);
}
// foo(10) //Window undefined 10
foo.call(bar, 10) //{sex: "男"} "男" 10
通过call方法调用foo函数,会立即执行foo函数,同时this会指向传递的第一个参数bar,第二个或后边的以参数的形式传递给foo函数
模拟call方法
let bar = {
sex: "男"
};
function foo(age) {
console.log(this.sex, age); //男 10
console.log(this); //{sex: "男", fn: ƒ}
}
Function.prototype.myCall = function (context) {
//第一种获取形参的方法
// let arr = [];
// for(let i=0; i<arguments.length;i++){
// arr.push(arguments[i])
// }
//从角标1开始删除,并返回被删除的项目
// arr = arr.splice(1);
//第二种获取形参的方法,获取参数列表(去除第一个参数)
// let arr = [...arguments].splice(1);
// console.log(...arr); //10
//第一个参数content为调用call方法的函数内this的指向
console.log(context);//{sex: "男"}
console.log(this);//this是调用myCall的foo()函数
//如果不传入参数,默认指向window
context = context || window;
//动态给content添加fn属性并将this赋值给它
context.fn = this;
//调用content的fn函数,并把除了第一个参数的参数传递过去
// let result = context.fn(...arr);
let result;
//调用content的fn函数,并判断是否有第二个参数,如果有就传递过去
if (arguments[1]) {
let arr = [...arguments].splice(1); //10
result = context.fn(...arr)
} else {
result = context.fn()
}
//删除fn属性,但this已经指向了传入的那个对象
delete context.fn;
//测试this对象中是否还有fn属性
// context.test = this;
// context.test();
return result
};
foo.myCall(bar, 10);
当调用foo函数时,如果属性或方法没有找到,会向原型链上查找,foo构造函数的__proto__指向的是Function.prototype对象,所以我们在模拟call方法时,可以给Function的原型添加方法。
foo调用myCall方法 等同于给bar添加一个fn属性,而fn属性又对应一个foo方法,然后就是调用fn方法。如:
function bar(){
this.sex = "男";
this.fn = function foo(age) {
console.log(this.sex, age);
}
}
具体步骤是:
-
首先通过arguments拿到所所有的实际参数,再通过splice截取除了第一个参数的列表
-
把this对象(this是foo函数)赋值到bar对象的fn属性上,并执行fn函数
-
把先前处理好的参数传递过去,并获取返回结果(如果无就返回undefined)
-
删除bar的fn属性,并返回结果
注意
在foo函数中打印的this为什么还是有fn函数呢,我们已经通过delete context.fn; 把fn函数删除了啊?我个人理解:这里的显示是因为delete context.fn在打印之后执行的,而我们看到的是删除之前的数据,如果我们点开,里边显示的才是foo真正具有的属性(如下图),或者我们在delete context.fn之后再给context赋值一个属性,再次打印就看不到fn属性了
apply方法使用说明
let bar = {
sex: "男"
};
function foo(age) {
console.log(this.sex, age); //男 10
}
foo.apply(bar, [10])
模拟apply方法
let bar = {
sex: "男"
};
function foo(age) {
console.log(this.sex, age); //男 10
}
Function.prototype.myApply = function(context){
//第一个参数content为调用call方法的函数内this的指向
console.log(context);//{sex: "男"}
console.log(this);//this是调用myCall的foo()函数
//如果不传入参数,默认指向window
context = context || window;
//动态的将this赋值给content的fn属性
context.fn = this;
let result;
//调用content的fn函数,并判断是否有第二个参数,如果有就传递过去
if(arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
//删除fn属性
delete context.fn;
return result
};
foo.myApply(bar, [10]); //男 10
apply和call方法实现类似,只不过参数是以数组的形式传递
模拟bind方法
Function.prototype.myBind = function (context) {
let self = this;
let arg = Array.prototype.slice.call(arguments, 1);
let fNOP = function () {};
let fbound = function () {
let innerArg = Array.prototype.slice.call(arguments);
let totalArg = arg.concat(innerArg);
return self.apply(context, totalArg);
}
fNOP.prototype = this.prototype;
fbound.prototype = new fNOP();
return fbound;
}