函数中(在这里说的是普通函数,非剪头函数),this 总是指向调用函数的对象。而 bind,call 和 apply 函数可以用来改变函数的执行上下文(即执行时的 this 指向)。
区别
| 区别 | bind | call | apply |
|---|---|---|---|
| 返回值 | 返回一个新函数待调用 | 立即执行,return 执行结果 | 立即执行,return 执行结果 |
| 传参 | arg1, arg2, ..., argn | arg1, arg2, ..., argn | argsArray 数组或类数组对象 |
手写题
call
Function.prototype.myCall = function (context) {
// 如果在非函数 x 上调用,在进入 myCall 之前就会报错说 x.myCall is not a function
// 所以感觉这个校验并不需要呢?你怎么看
// if (typeof this !== 'function') {
// throw new TypeError('myCall must be called on a function');
// }
// 如果上下文对象 context 为空 undefined / null,非严格模式下会指向全局上下文对象
context = context || globalThis
// 取传入 myCall 的除了 context 之外的其他参数
// 为什么需要 Array.from 处理?因为 arguments 是类数组对象,不能直接用数组的 slice 方法
const args = Array.from(arguments).slice(1);
const key = Symbol('key'); // 生成一个唯一的 key
context[key] = this; // 将调用 myCall 的目标函数作为 context 新属性的 value
const ret = context[key](...args); // 执行函数,此时调用函数的是 context,所以this自然指向了 context
delete context[key]; // 删除 key-value
return ret;
};
🤔 通过 Array.from(arguments).slice(1) 或者 Array.prototype.slice.call(arguments, 1) 又或者 [...arguments].slice(1) 处理 arguments 类数组对象来获得剩余参数有点麻烦呢。直接用剩余参数吧!
Function.prototype.myCall = function (context, ...args) {
const key = Symbol('key');
context[key] = this;
const ret = context[key](...args);
delete context[key];
return ret;
};
apply
除了传参,其他和 call 一样
Function.prototype.myCall = function (context, argsArray) {
context = context || globalThis
const key = Symbol('key');
context[key] = this;
const ret = context[key](...argsArray); // 将数组展开
delete context[key];
return ret;
};
🤔 关于数组展开,想到一个题:怎么通过 Math.max 获得数组 const arr = [1,2,3,4,5] 中的最大值呢?
Math.max.apply(null, arr)Math.max(...arr)
bind
Function.prototype.myBind = function(context, ...args) {
context = context || globalThis;
const fn = this // this 就是调用 myBind 方法的函数
return function (...innerArgs) {
// 兼容 new 关键字:当该 return 的函数被当成构造函数被 new 调用时,
// new.target 会返回该构造函数,普通调用则返回 undefined
// 既然决意要 new,那我们就没法管 this 指向了,
// 因为这时 JavaScript 引擎在内部控制了 this 指向新建的对象 (参阅 new 操作符文档)
if(new.target) {
return new fn(...args, ...innerArgs)
}
return fn.apply(context, args.concat(innerArgs))
}
}
🤔 说到 new.target,又想到一个题:怎么保证一个函数只能作为构造函数被 new 调用?
🤔 args 是个数组,所以可以用数组的 concat 方法,而在 手写 myCall 的时候要将 arguments 转成数组才能用数组的 slice 方法。