JS手搓代码---(call)

0 阅读2分钟

call是什么

在 JavaScript 里,call 是函数原型 Function.prototype 上的一个方法,所有函数都能继承并使用它。以下是 call 方法的定义和作用:

定义

call 方法的语法如下:

function.call(thisArg, arg1, arg2, ...)
  • thisArg:调用函数时 this 要指向的对象。若为 nullundefined,在非严格模式下 this 会指向全局对象(浏览器中是 window)。
  • arg1, arg2, ...:传递给函数的参数,多个参数需逐个列出。

作用

  1. 改变 this 指向call 最主要的作用是改变函数执行时 this 的指向。这在需要复用函数,但又想指定不同的 this 上下文时非常有用。
  2. 方法借用:可以让一个对象调用另一个对象的方法,而无需在该对象上定义这个方法。
  3. 立即执行函数:调用 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" ;在严格模式下,thisnullundefined ,访问 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)在严格模式下会直接是传入的nullundefined,访问null.nameundefined.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代码就出来了,现在让我们来看下运行结果,可以看到拿到了我们想要的结果。

image.png