Loading

JS 手写之 Function.prototype.apply

Function.prototype.apply

apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

注意: call()方法的作用和 apply() 方法类似,区别就是 call()方法接受的是参数列表,而 apply()方法接受的是一个参数数组。

语法

func.apply(thisArg, [argsArray]);

参数

  • thisArg - 必选的。在 func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
  • argsArray - 可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。 浏览器兼容性 请参阅本文底部内容。

返回值

调用有指定 this 值和参数的函数的结果。

描述

在调用一个存在的函数时,你可以为其指定一个 this 对象。 this 指当前对象,也就是正在调用这个函数的对象。 使用 apply, 你可以只写一次这个方法然后在另一个对象中继承它,而不用在新对象中重复写该方法。

apply 与 call() 非常相似,不同之处在于提供参数的方式。apply 使用参数数组而不是一组参数列表。apply 可以使用数组字面量(array literal),如 fun.apply(this, ['eat', 'bananas']),或数组对象, 如 fun.apply(this, new Array('eat', 'bananas'))。

你也可以使用 arguments 对象作为 argsArray 参数。 arguments 是一个函数的局部变量。 它可以被用作被调用对象的所有未指定的参数。 这样,你在使用 apply 函数的时候就不需要知道被调用对象的所有参数。 你可以使用 arguments 来把所有的参数传递给被调用对象。 被调用对象接下来就负责处理这些参数。

从 ECMAScript 第 5 版开始,可以使用任何种类的类数组对象,就是说只要有一个 length 属性和(0..length-1)范围的整数属性。例如现在可以使用 NodeList 或一个自己定义的类似 {'length': 2, '0': 'eat', '1': 'bananas'} 形式的对象。

Function.prototype.myApply

Function.prototype.myApply = function (ctx, args = []) {
  // 避免上下文不传,缺省值为 window 全局对象
  ctx ||= window;
  // 将入参强行修改为数组类型,对于字符串类型,直接抛出异常
  if (typeof args === "string") {
    throw new TypeError("CreateListFromArrayLike called on non-object");
  }
  args = Array.from(args);
  // 创建一个唯一的 Symbol 避免对象属性重复
  const uniqueKey = Symbol("fn");
  // 保存当前的 this,当前的 this 原来指向调用它的方法
  ctx[uniqueKey] = this;

  // 对象调用保存的方法, 此时 this(原方法) 指向被修改为ctx
  const res = args.length > 0 ? ctx[uniqueKey](...args) : ctx[uniqueKey]();
  // 删除对象的属性,减少内存占用
  delete ctx[uniqueKey];
  return res;
};

测试

var target = "world";
var context = {
  target: "universe",
};
function fn() {
  const args = Array.from(arguments).join(" ");
  console.log(`${args} ${this.target}!!!`);
}

// thisArg 为 undefined/null 时,上下文默认为 window
fn.apply(undefined, ["hello", "all", "the"]); // hello all the world!!!
fn.myApply(undefined, ["hello", "all", "the"]); // hello all the world!!!

// thisArg 存在时,可以发现,this 的指向已经被修改到 context
fn.apply(context, ["hello", "all", "the"]); // hello all the universe!!!
fn.myApply(context, ["hello", "all", "the"]); // hello all the universe!!!

// 异常测试
fn.apply(undefined, "hello"); // Uncaught TypeError: CreateListFromArrayLike called on non-object
fn.myApply(undefined, "hello"); // Uncaught TypeError: CreateListFromArrayLike called on non-object
posted @ 2021-05-29 21:47  Frank-Link  阅读(164)  评论(0编辑  收藏  举报