call是改变this指向的方法,传入的参数是要指向的对象和函数需要的参数序列。
改变this指向还可以用apply和bind,区别如下:
| 方法 | 传参方式 | 执行时机 | 返回值 |
|---|---|---|---|
call |
参数序列(逐个传入) | 立即执行 | 函数执行的结果 |
apply |
参数数组(或类数组) | 立即执行 | 函数执行的结果 |
bind |
参数序列(逐个传入) | 返回新函数,稍后执行 | 绑定了 this 的新函数 |
代码
简易版
javascript
Function.prototype.callSimple = function (context, ...args) {
context = context || window // 确认上下文,如果没有上下文就默认window
context.fn = this // 把当前函数赋值给上下文的fn属性(临时属性)
const res = context.fn(...args) // 调用函数,传参
delete context.fn // 删除临时属性
return res
}
健壮版
javascript
Function.prototype.call = function (context, ...args) {
// 如果上下文是null或undefined,就默认window;否则用Object()转换为对象
// Object()包对象返回原对象,包原始类型返回对象包装类型。这是因为简单数据类型不能挂载属性,对象才可以
context = context !== null && context !== undefined ? Object(context) : window
let tag = Symbol('call') // Symbol()创建一个唯一的符号值,避免与其他属性冲突/覆盖
context[tag] = this // 把当前函数赋值给上下文的tag属性(临时属性)
const res = context[tag](...args) // 调用函数,传参。这里使用方括号是因为tag是一个符号值,不能用点号
return res
}
Tips
-
bind传参数可以先传一部分参数,返回新函数,下次再传剩下的。这种特性叫函数柯里化(Currying)
-
如果bind返回的新函数被new构造调用了,this会失效。因为new的优先级高于bind(但也只有new比bind高)
javascript
function Person(name) {
this.name = name;
}
const BoundPerson = Person.bind({ name: '默认' }); // 试图绑定 this
const p = new BoundPerson('李四');
console.log(p.name); // 输出:李四
因为new强行创建了一个新对象作为this,bind绑定的this被覆盖了。