彻底搞定apply以及应用场景

463 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

显示改变this指向是一个非常高频的操作,在JS中可以通过applycallbind三种方式显示的修改this指向,掌握其原理以及常见使用场景是初学者必不可少的,本文主要通过apply来阐述

首先看一下apply的使用

const obj = {
    name: "nordon",
};

var name = "globalName";
function fn() {
    console.log(this.name);
}

直接调用fn(),此时输出globalName

若是期望调用fn时输出的是obj中的name属性,此时常用的一种方式,便是通过apply显示改变其函数内部this指向

fn.apply(obj);

此时输出nordon,符合期望

🤔那么若是将name的声明方式修改一下,直接调用fn输入什么?

--- var name = "globalName";
+++ const name = "globalName";

实现apply

接下来便通过分析通过apply做了什么事情,然后手动实现一版

fn.apply(obj);调用时等价于直接调用obj.fn(),可以将上述代码修改一下

const obj = {
    name: "nordon",
    fn() {
        console.log(this.name);
    },
};

此时直接调用完全符合我们的期望,明白这点之后,那么我们便可以知道其实apply内部完成的事情就这么简单

实现版本如下:

Function.prototype._apply = function (targetObject, argsArray) {
  // 若是没有传递,则置为空数组
  if(typeof argsArray === 'undefined' || argsArray === null) {
    argsArray = []
  }

  // 是否传入执行上下文,若没有指定,则指向 window
  if(typeof targetObject === 'undefined' || targetObject === null){
      targetObject = window
  }

  // 利用Symbol的特性,设置为key
  const targetFnKey = Symbol('key')
  // 将调用_apply的函数赋值
  targetObject[targetFnKey] = this
  // 执行函数,并在删除之后返回
  const result = targetObject[targetFnKey](...argsArray)
  delete targetObject[targetFnKey]
  return result
}

套用上述案例代码并结合手动实现apply代码进行分析

当调用fn._apply(obj)时,Function.prototype._apply内部的this是当前的调用者fn函数,targetObjectobj

在函数内部,主要就是将fn挂载到obj上,然后调用函数obj.fn

应用场景

结合下面的应用场景,帮助我们更好的理解apply

判断数据类型

判断一个数据类型可以使用typeofinstanceof等方式,但是最推荐的方式是使用Object.prototype.toString进行类型判断

Object.prototype.toString({})       // "[object Object]"
Object.prototype.toString.apply([])  //"[object Array]"
Object.prototype.toString.apply('1') // "[object String]"

上述代码可以看到Object可以直接调用toString方法,其他类型就需要通过apply借助Object原型的方法来实现

类数组借助数组方法

类数组并不是真正的数组,没有数组类型上的方法,但是我们可以利用applycall借用数组的方法,比如借用数组的 push 方法

函数的arguments就是一个典型的类数组

function fn() {
    console.log(arguments); // [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]0: 11: 2callee: ƒ fn()length: 2Symbol(Symbol.iterator): ƒ values()[[Prototype]]
}
fn(1, 2);

类数组不具备数组的方法:

arguments.shift();

会直接在控制台报错:Uncaught TypeError: arguments.shift is not a function

此时可以借助aplly

Array.prototype.shift.apply(arguments);

继承

构造函数通常时借助call、apply来完成继承

function Person(name) {
    this.name = name;
    this.permission = ["user", "salary", "vacation"];
}

Person.prototype.say = function () {
    console.log(`${this.name} 说话了`);
};

function Staff(name, age) {
    Person.call(this, name);
    this.age = age;
}

Staff.prototype.eat = function () {
    console.log('吃东西啦~~~');
}

const zs = new Staff("张三", 12);
console.log(zs);