思路:
call 函数可以接收多个参数,第一个参数必定是一个对象,可以是空对象 null ,也可以是function 或者 数组对象 和 object ,后面可以带无数个参数
call 函数其实是 Function 构造函数上的原型对象 prototype 的一个方法, 当我们使用 a.call(b) 时,实际上相当于调用了 Function.prototype.call ,也可以理解为 在对象 b 中定义了 一个sayhi(),可以通过 b.sayhi() 调用 a.sayhi.() 方法,在 b.sayhi() 执行时,this 指向 对象 b,因为是对象 b 开始引用的
那么当我们使用 a.call(b) 时, this 会指向 a ,因为是 对象a 开始引用的,这是一种隐式绑定,原理是当一个函数执行时,这个函数的 this 会指向当前上下文也就是真正调用这个函数的对象,搞清楚 call 函数实现过程中的 this 的指向,那么我们就可以开始着手实现了
class Person {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat something`)
}
}
class Student extends Person {
constructor(name, number) {
super(name)
this.number = number
}
sayhi() {
console.log(`姓名: ${this.name} 学号: ${this.number}`)
console.log(arguments);
}
}
xialuo = new Student('夏洛', 1)
madongmei = new Student('马冬梅', 2)
xialuo.sayhi() //姓名:夏洛 学号:1 arguments空数组
xialuo.sayhi.call(madongmei) //姓名:马冬梅 学号:2 arguments空数组
//任务:实现一个 mycall 函数,使得 xialuo.sayhi.mycall(madongmei) 输出上面的结果
Function.prototype.mycall = function(obj) {
var obj = obj || window; //我们的 mycall 函数接收形参 obj,如果接收的参数是 null 空对象时,为了防止指向空对象报错,可以让它指向 window
//要让 madongmei 也就是接收到的形参 obj 能使用 xialuo.sayhi,从上面的分析可知,当 xialuo.sayhi 调用 mycall 方法时,这里的 this 指向的就是 xialuo.sayhi,那么用 obj.this 就能使用
//可是这样意义不明,而且太丑了,所以我们把 this 存放在 obj.fn1,通过 obj.fn1 调用 xialuo.sayhi 方法
//最后把 obj.fn1 删除,因为我们不希望改变 obj 对象
obj.fn1 = this; // 当 xialuo.sayhi.mycall(madongmei) 时,这个this 指向 xialuo.sayhi
obj.fn1();
delete obj.fn1;
}
//这样我们就完成了一个只接收一个参数的 mycall 函数
//当我们的 mycall 函数接收多个参数时,我们可以使用 arguments 对象获得所有参数,去除第一个保存在NewArguments,然后 obj.fn1(NewArguments)
//同时我们的 mycall 函数应该返回一个结果,就是运行了 xialuo.sayhi 得到的结果
Function.prototype.mycall = function(obj) {
var obj = obj || window
obj.fn1 = this;
const newArguments = [];
for (let i = 1; i < arguments.length; i++) {
//我们希望以字符串形式获取到剩余参数,所以进行了隐式转换
newArguments.push('arguments[' + i + ']')
}
let result = eval('obj.fn1(' + newArguments + ')');
delete obj.fn1;
return result;
}
//当然,在 es6 的语法中,我们可以用解构赋值获取到 arguments 对象中的所有参数
Function.prototype.mycall = function(obj) {
const obj = obj || window
obj.fn1 = this;
const newArguments = Array.prototype.slice(arguments) //let newArguments = [...arguments].slice(1)
let result = eval('obj.fn1(' + newArguments + ')');
delete obj.fn1;
return result;
}
//最后用 xialuo.sayhi.mycall(madongmei)试试看吧
madongmei.sayhi = xialuo.sayhi.mycall(madongmei, 'aaa', 'bbb', 'ccc', 4)
madongmei.sayhi //姓名: 马冬梅 学号: 2 包含有 aaa bbb ccc 4 这四个字符串的 arguments 数组