• apply call bind的用法与实现


    概念

    apply call 和bind 允许为不同的对象分配和调用属于一个对象的函数/方法。同时它们可以改变函数内 this 的指向。

    区别

    • apply 和 call 接收的参数形式不同

    • apply 和 call 都是直接调用函数并得到函数执行结果,而 bind 会返回待执行函数,需要再次调用

    用法演示

    我们先创建一个对象 parent

    const parent = {
        name: 'parent',
        sayPerson (age, addr) {
            return {
                name: this.name,
                age,
                addr
            }
        }
    }
    

    显然它具有 name 属性,及方法 sayPerson,我们现在可以通过 parent.sayPerson() 来输出该对象信息。

    const person = parent.sayPerson(60, 'shenzhen');
    // {name: "parent", age: 60, addr: "shenzhen"}
    

    现在我们再创建一个对象 son

    const son = {
        name: 'son'
    }
    

    我们现在也想得到 son 的信息,但是 son 对象没有 sayPerson 函数怎么办?借助已有的 parent 对象和 call 方法,我们可以这样写

    const person = parent.sayPerson.call(son, 26, 'shenzhen');
    // {name: "son", age: 26, addr: "shenzhen"}
    

    可以看出,通过调用 call 函数,我们为 son 对象分配了 sayPerson 方法并进行调用。实现了一个对象可以调用不属于它自己的方法,并且函数内的 this 指向该对象。apply 方法的用法其实一样,只是传参有些区别

    const person = parent.sayPerson.call(son, [26, 'shenzhen']);
    // {name: "son", age: 26, addr: "shenzhen"}
    

    bind 函数则不直接调用函数,而是返回待调用函数

    const sayPersonFn = parent.sayPerson.bind(son, 26, 'shenzhen');
    
    const person = sayPersonFn();
    // {name: "son", age: 26, addr: "shenzhen"}
    

    以上就是三者的使用方法和区别,下面我们来看看它们是如何实现的

    实现

    call的实现

    实现原理就是为对象 obj 添加需要调用的方法,接着调用该方法(此时 this 指向 obj),调用过后再删除该方法

    简单版

    Object.prototype.callFn = function (...args) {
        // 第一个参数为目标对象
        const context = args[0];
    
        args.shift();
    
        // 为对象赋值需要调用的方法
        context.fn = this;
    
        // 调用该方法
        context.fn(...args);
    
        // 删除方法
        delete context.fn;
    }
    

    加上返回值

    Object.prototype.callFn = function (...args) {
        // 第一个参数为目标对象
        const context = args[0];
    
        args.shift();
    
        // 为对象赋值需要调用的方法
        context.fn = this;
    
        // 调用该方法
        const result = context.fn(...args);
    
        // 删除方法
        delete context.fn;
    
        return result;
    }
    

    在测试中发现,我们调用 call,如果第一个参数是 null 或者 undefined,那么 call 将以全局 window 来调用方法,此时 this 也指向 window。如果第一个参数不是对象类型,则以空对象 {} 来调用方法。

    Object.prototype.callFn = function (...args) {
        // 第一个参数为目标对象
        let context = args[0];
    
        // undefined 和 null 指向 window
        if (context === null || context === undefined) {
            context = window;
        }
    
        // 不是对象类型则创建空对象
        if (typeof context !== 'object') {
            context = {};
        }
    
        args.shift();
    
        // 为对象赋值需要调用的方法
        context.fn = this;
    
        // 调用该方法
        const result = context.fn(...args);
    
        // 删除方法
        delete context.fn;
        
        return result;
    }
    

    至此,我们实现了一个完整的 call 方法。

    apply的实现

    既然和 call 只存在传参的区别,那我们只需要简单修改下已实现的 call 方法即可。

    Object.prototype.applyFn = function (...args) {
        let context = args[0];
    
        if (context === null || context === undefined) {
            context = window;
        }
    
        if (typeof context !== 'object') {
            context = {};
        }
    
        args.shift();
    
        context.fn = this;
    
        // 和 call 存在差异的地方
        const result = context.fn(...args[0]);
    
        delete context.fn;
    
        return result;
    }
    

    bind的实现

    在实现了 apply 和 call 的前提下,bind 的实现也比较简单。

    Object.prototype.bindFn = function (...args) {
        // 实际就是多包了层待执行函数
        return () => {
            return this.applyFn(args[0], (args || []).slice(1));
        }
    }
    

    至于以 bind 方法返回的函数作为构造函数来创建对象会存在的问题请参考JavaScript深入之bind的模拟实现

    总结

    call apply bind 在工作中实际上是比较常见的函数,特别是在一些框架或库的源码中,但是经常有人会混淆它们的用法。希望大家通过此篇文章可以彻底弄清它们的作用与区别,并且知道其实现原理,知其然知其所以然。

    参考


    欢迎到前端学习打卡群一起学习~516913974

  • 相关阅读:
    第二章 关系模型介绍
    第一章 引言
    只要人人都献出一点爱......
    小算法
    题目:返回一个整数数组中最大子数组的和。
    题目:返回一个整数数组中最大子数组的和
    梦断代码读后感之开始篇
    电梯调度需求分析
    结对开发--求二维数组的最大子数组
    四则运算(3)--答题小系统
  • 原文地址:https://www.cnblogs.com/formercoding/p/12872999.html
Copyright © 2020-2023  润新知