上节课我们学了手写 call,今天学 apply—— 它们的核心功能完全一样(改变函数 this 指向 + 执行函数 + 返回结果),唯一区别就是给原函数传参的方式不同!
fn.call(context, 参1, 参2, 参3),fn.apply(context, [参1, 参2, 参3])
//语法fn.call(context, 参1, 参2, 参3),fn.apply(context, [参1, 参2, 参3])
function sayHi(name, age) {
console.log(`我是${this.name},你好${name},我${age}岁`);
return `结果:${this.name}-${name}`;
}
const person = { name: "张三" };
// call:逐个传参
sayHi.call(person, "小明", 20);
// apply:数组传参(第二个参数是数组)
sayHi.apply(person, ["小明", 20]);
// 两者输出完全一样!只是传参格式不同
// 挂载到 Function.prototype,和 call 一样
Function.prototype.myApply = function(context) {
// 步骤1:判断调用者是不是函数(和 call 完全一样)
if (typeof this !== "function") {
throw new TypeError("Error"); // 规范报错类型
}
let result = null; // 存函数执行结果(和 call 一样)
// 步骤2:处理 context(没传就用 window,和 call 一样)
context = context || window;
// 步骤3:临时挂载函数到 context(和 call 完全一样)
context.fn = this; // this 还是原函数(比如 sayHi)
// 步骤4+5:处理参数+执行函数(唯一和 call 不同的地方!)
// arguments[1] 是 apply 的第二个参数(数组类型,给原函数的参数)
if (arguments[1]) {
// 有参数:展开数组传参(...把数组转成逐个参数,适配原函数)
result = context.fn(...arguments[1]);
} else {
// 没传参数:直接执行
result = context.fn();
}
// 步骤6:删除临时属性(和 call 一样,避免污染 context)
delete context.fn;
// 步骤7:返回结果(和 call 一样)
return result;
};
function sayHi(name, age) {
console.log(`我是${this.name},你好${name},我${age}岁`);
return `结果:${this.name}-${name}`;
}
const person = { name: "张三" };
// 测试有参数(数组传参)
myApply.apply(person, ["小明", 20]); // 输出:我是张三,你好小明,我20岁(正确)
const res = myApply.apply(person, ["小红", 22]);
console.log(res); // 结果:张三-小红(返回值正确)
// 测试无参数
function sayHello() {
console.log(`Hello,我是${this.name}`);
}
sayHello.myApply(person); // 输出:Hello,我是张三(正确)