call是什么
在 JavaScript 里,call
是函数原型 Function.prototype
上的一个方法,所有函数都能继承并使用它。以下是 call
方法的定义和作用:
定义
call
方法的语法如下:
function.call(thisArg, arg1, arg2, ...)
thisArg
:调用函数时this
要指向的对象。若为null
或undefined
,在非严格模式下this
会指向全局对象(浏览器中是window
)。arg1, arg2, ...
:传递给函数的参数,多个参数需逐个列出。
作用
- 改变
this
指向:call
最主要的作用是改变函数执行时this
的指向。这在需要复用函数,但又想指定不同的this
上下文时非常有用。 - 方法借用:可以让一个对象调用另一个对象的方法,而无需在该对象上定义这个方法。
- 立即执行函数:调用
call
方法会立即执行函数。
示例代码
const obj = { name: 'John' };
function greet(message) {
console.log(`${message}, ${this.name}`);
}
greet.call(obj, 'Hello'); // 输出: Hello, John
在这个示例中,call
方法将 greet
函数的 this
指向 obj
对象,并传递了 'Hello'
作为参数。
两种模式下call的this指向
var name = "Trump";
function greeting() {
return `hello, I am ${this.name}.`;
}
const lj = {
name: "雷军",
};
console.log(greeting.call(lj)); // hello, I am 雷军
console.log(greeting.call()); // hello, I am Trump
console.log(greeting.call(null)); // hello, I am Trump
console.log(greeting.call(undefined)); // hello, I am Trump
</script>
可以看到在非严格模式下,greeting.call(lj) :call
方法将 this
绑定到lj
对象,因此this.name
为 "雷军"
。greeting.call()
、greeting.call(null)
和greeting.call(undefined)
:在非严格模式下, this 会被绑定到全局对象(浏览器中为 window ),全局的 name 变量值为 "Trump"
;在严格模式下,this
为 null
或 undefined
,访问 this.name
会报错。
"use strict";
var name = "Trump";
function greeting() {
return `hello, I am ${this.name}.`;
}
const lj = {
name: "雷军",
};
console.log(greeting.call(lj)); // hello, I am 雷军
console.log(greeting.call());
console.log(greeting.call(null));
console.log(greeting.call(undefined));
在严格模式下会产生报错,因为在严格模式下greeting.call()
不传参数时this
会保持为undefined
,访问undefined.name
会抛出TypeError。greeting.call(null)
和greeting.call(undefined)
在严格模式下会直接是传入的null
或undefined
,访问null.name
或undefined.name
同样会抛出TypeError
。
手写call
1.准备好测试用例
开始可以看到需要自己定义一个myCall
的函数,就可以发现当使用普通的call的时候发现打印出来的即为hello, I am xww
。因此根据上述内容可以展开写判断call
函数是否正确。
function greeting(...args) {
// console.log(args, arguments[0], arguments[1]);
return `hello, I am ${this.name}.`;
}
var obj = {
name: 'xww',
fn:function(){
}
}
console.log(greeting.myCall(obj, 1, 2, 3));
2.判断myCall方法的对象是否为函数
为 Function 原型添加自定义的 myCall 方法,用于模拟原生的 call 方法
传递参数从一个数组变成逐个传参,不用 ... 扩展运算符的也可以用 arguments 代替
处理 context 为 null 或 undefined 的情况,将其默认设置为 window 对象
检查调用 myCall 方法的对象是否为函数,如果不是则抛出类型错误
function greeting(...args) {
// console.log(args, arguments[0], arguments[1]);
return `hello, I am ${this.name}.`;
}
Function.prototype.myCall = function(context,...args){
if(context === null || context === undefined){
context = window;
}
if(typeof this !== "function"){
throw new TypeError(
"Function.prototype.myCall called on non-function"
);
}
}
var obj = {
name: 'xww',
fn:function(){
}
}
console.log(greeting.myCall(obj, 1, 2, 3));
3.避免覆盖和污染
function greeting(...args) {
// console.log(args, arguments[0], arguments[1]);
return `hello, I am ${this.name}.`;
}
Function.prototype.myCall = function(context,...args){
if(context === null || context === undefined){
context = window;
}
if(typeof this !== "function"){
throw new TypeError(
"Function.prototype.myCall called on non-function"
);
}
// 创建一个唯一的 Symbol 值作为属性名,避免覆盖 context 上已有的属性
const fnKey = Symbol("fn");
// 将调用 myCall 方法的函数赋值给 context 的 fnKey 属性
context[fnKey] = this;
// 在 context 上下文中调用函数,并传入参数 ...args,将结果存储在 result 中
const result = context[fnKey](...args);
// 删除 context 上临时添加的属性,避免污染 context 对象
defalut context[fnkey];
// 返回函数执行的结果
return result;
}
var obj = {
name: 'xww',
fn:function(){
}
}
console.log(greeting.myCall(obj, 1, 2, 3));
这样子完整的手写call代码就出来了,现在让我们来看下运行结果,可以看到拿到了我们想要的结果。