手写代码 : 如何用手 , 写js中的apply方法 ? 🤔

210 阅读5分钟

前言

大家好 , 我是一名在校大二学生 , 今日学习apply的实现机制 , 和大家一起分享 ~

发自内心的快乐.gif

题目分析

JavaScript 中的 apply方法

  • 用于调用一个函数

  • 并显式地设置 this 的值

  • 同时传入一个数组或类数组对象作为参数

apply方法与call方法类似,不同之处在于

  • apply接受的是参数数组,
  • 而call接受的是参数列表。

我的代码

Function.prototype.myApply = function (thisArg , argsArray){
    //1. 确保 this 是一个函数
    if(typeof this !== 'function'){
        throw new TypeError(this + 'is not a function');
    }

    // 2.处理thisArg , 如果为null 或者 undefined , 则指向全局对象
    thisArg = thisArg ? Object(thisArg) : globalThis;

    // 3.创建一个唯一的属性名 , 避免覆盖原有属性

    const fnSymbol = Symbol();
  
    // 4. 将函数作为thisArg 的属性
    thisArg[fnSymbol] = this ; // 将函数挂载到thisArg上

    // 5.调用函数并传入参数数组
    const result = thisArg[fnSymbol](...argsArray);


    // 6. 删除临时属性

    delete thisArg[fnSymbol] ;

 
    return result ;

 }


 const obj = {
    name : 'ganzhibin'
 }

 function sayHello(age,hobby){
    console.log(`hello ${this.name} ,you are ${age} ,your hobby is ${hobby}`);
 }


 sayHello.myApply(obj,[18,'coding'])

image.png

解释代码

逐行解释

  1. 确保 this 是一个函数

    if (typeof this !== 'function') {
        throw new TypeError(this + ' is not a function');
    }
    
    • 为什么这样写myApplyFunction.prototype 的方法,因此 this 应该是一个函数。如果不是函数,抛出一个 TypeError
    • 作用:确保 myApply 只能被函数调用,防止错误的调用导致未定义行为。
  2. 处理 thisArg, 如果为 null 或者 undefined, 则指向全局对象

    thisArg = thisArg ? Object(thisArg) : globalThis;
    
    • 为什么这样写:根据规范,如果 thisArgnullundefinedthis 应该指向全局对象(在浏览器中是 window,在 Node.js 中是 global)。
    • 作用:确保 thisArgnullundefined 时指向正确的全局对象。
    • 细节Object(thisArg) 用于将原始值(如字符串、数字、布尔值)转换为相应的对象包装器。
  3. 创建一个唯一的属性名, 避免覆盖原有属性

    const fnSymbol = Symbol();
    
    • 为什么这样写:使用 Symbol 创建一个唯一的属性名,确保不会覆盖 thisArg 上已有的属性。
    • 作用:避免与 thisArg 上的现有属性发生冲突。
  4. 将函数作为 thisArg 的属性

    thisArg[fnSymbol] = this; // 将函数挂载到 thisArg 上
    
    • 为什么这样写:将当前函数(即 this)挂载到 thisArg 对象上,以便可以通过 thisArg[fnSymbol]() 调用该函数。
    • 作用:实现将函数绑定到指定的 this 上下文。
  5. 调用函数并传入参数数组

    const result = thisArg[fnSymbol](...argsArray);
    
    • 为什么这样写:使用扩展运算符 ...argsArray 展开为单独的参数,并调用 thisArg[fnSymbol]
    • 作用:确保参数正确传递给函数,并调用该函数。
  6. 删除临时属性

    delete thisArg[fnSymbol];
    
    • 为什么这样写:删除之前添加的临时属性,保持 thisArg 对象的干净状态。
    • 作用:避免在 thisArg 上留下不必要的属性,保持对象的整洁。
  7. 返回结果

    return result;
    
    • 为什么这样写:返回函数调用的结果。
    • 作用:确保 myApply 的返回值与原生 apply 方法一致。

详细解释

  1. 确保 this 是一个函数

    • 原因myApplyFunction.prototype 的方法,只能被函数调用。
    • 替代方案:可以在调用 myApply 之前手动检查,但直接在方法内部检查更为合理。
  2. 处理 thisArg

    • 原因:根据规范,thisArgnullundefined 时,this 应该指向全局对象。
    • 替代方案:可以使用 thisArg || globalThis,但 thisArg ? Object(thisArg) : globalThis 更加严谨,处理了原始值的情况。
  3. 创建唯一属性名

    • 原因:避免与 thisArg 上的现有属性发生冲突。
    • 替代方案:可以使用随机字符串,但 Symbol 更加安全和简洁。
  4. 将函数作为 thisArg 的属性

    • 原因:实现将函数绑定到指定的 this 上下文。
    • 替代方案:可以使用其他方法绑定 this,但这种方法最为直接。
  5. 调用函数并传入参数数组

    • 原因:确保参数正确传递给函数,并调用该函数。
    • 替代方案:可以使用 Function.prototype.call,但 apply 更适合处理可变参数。
  6. 删除临时属性

    • 原因:保持 thisArg 对象的干净状态。
    • 替代方案:如果不删除,可能会留下不必要的属性,但删除可以确保对象的整洁。

通过这些步骤,myApply 函数实现了与 JavaScript 内置 apply 方法类似的功能,确保函数能够在指定的 this 上下文中正确调用,并传递参数。

拓展

apply 和 call方法都可以用来调用一个函数,并显式地设置 this 的值。不同之处在于:

  • apply 方法接受两个参数:一个是 this 的值,另一个是参数数组。
  • call方法接受多个参数:第一个是 this 的值,后续是函数的参数。
function example(a, b) {
    console.log(this.name, a, b);
}

const obj = { name: 'ganzhibin' };

example.apply(obj, [1, 2]); // 输出: ganzhibin 1 2
example.call(obj, 1, 2); // 输出: ganzhibin 1 2

Symbol

Symbol是 ES6 引l入的一种新的原始数据类型,表示独一无二的值。使用 Symbol 可以创建唯一的属性名,避免属性名冲突。

const sym1 = Symbol('foo');
const sym2 = Symbol('foo');
console.log(sym1 === sym2); // 输出: false

globalThis

globalThis 是 ES2020 引I入的一个全局对象,它在不同的环境中指向全局对 象。在浏览器中,globalThis 等同于 window,在 Node.js 中等同于 global。

console.log(globalThis === window);// 在浏览器中输出:true
console.log(globalThis === global); // 在 Node.js 中输出:true

要实现 apply方法,我们需要:

1)确保 this 是一个函数。

2)处理thisArg,即调用函数时的this值。

3)处理参数数组,并调用原函数。