call,apply,bind的原理及手写

是什么?

callapplybind是JavaScript中用于改变普通函数 this指向(无法改变箭头函数this指向 )的方法,这三个函数实际上都是绑定在Function构造函数的prototype上,而每一个函数都是Function的实例,因此每一个函数都可以直接调用call,apply,bind

区别

  • call、apply直接调用,bind返回一个新函数并不立即执行。
  • call、bind接受多个参数,apply只接受两个参数:第一个参数是this要指向的对象,第二个参数是参数列表。

核心思想:借用方法

  • A对象有个方法,B对象因为某种原因也需要用到同样的方法,那么这时候我们是单独为 B 对象扩展一个方法呢,还是借用一下 A 对象的方法呢?

  • 当然是借用 A 对象的方法啦,既达到了目的,又节省了内存。

  • 这就是call/apply/bind的核心理念:借用方法

  • 借助已实现的方法,改变方法中数据的this指向,减少重复代码,节省内存。

手写call、apply、bind

call实现

js 复制代码
Function.prototype.myCall = function (context) {
    // 判断调用myCall的是否为函数
    if (typeof this !== 'function') {
        throw new TypeError("被call调用的对象必须是函数");
    }
    // 判断 context 是否传入,如果未传入则设置成 window
    context = context || window;
    // 获取参数,除了context其余都合并为参数列表
    let args = [...arguments].slice(1);
    // 用Symbol来创建唯一的fn,防止名字冲突
    let fn = Symbol("key");
    // this是调用myCall的函数,将函数绑定到上下文对象的新属性上
    context[fn] = this;
    // 传入myCall的多个参数
    const result = context[fn](...args);
    // 将增加的fn方法删除
    delete context[fn];

    return result;
}
  • 测试
js 复制代码
const ddl = {
    name: "zyzy",
    hello: function () {
        console.log(`hello,${this.name}!`);
    },
};

const obj = { name: "world" };
ddl.hello.myCall(obj); // hello,world!
ddl.hello.call(obj);// hello,world!

apply实现

js 复制代码
Function.prototype.myApply = function (context) {
    // 判断调用myApply的是否为函数
    if (typeof this !== 'function') {
        throw new TypeError('被apply调用的对象必须是函数');
    }
    // 判断myApply有传入第二个参数则判断其是否是数组
    if (arguments[1] && !Array.isArray(arguments[1])) {
        throw new TypeError('apply的第二个参数必须是数组');
    }
    // 判断 context 是否传入,如果未传入则设置成 window
    context = context || window;
    // 获取参数,本身传入的arguments[1]就是一个参数数组,如果不存在则传空数组
    let args = arguments[1] || [];
    // 用Symbol来创建唯一的fn,防止名字冲突
    let fn = Symbol("key");
    // this是调用myAppky的函数,将函数绑定到上下文对象的新属性上
    context[fn] = this;
    // 传入myApply的多个参数
    const result = context[fn](...args);
    // 将增加的fn方法删除
    delete context[fn];

    return result;
}
  • 测试
js 复制代码
const ddl = {
    name: "zyzy",
    hello: function (params) {
        console.log(`hello,${this.name}!I am ${params}`);
    },
};

const obj = { name: "world" };
ddl.hello.myApply(obj,['yqyq']); // hello,world!I am yqyq
ddl.hello.apply(obj,['yqyq']);// hello,world!I am yqyq

bind实现

js 复制代码
Function.prototype.myBind = function (context) {
    // 判断调用myBind的是否为函数
    if (typeof this !== 'function') {
        throw new TypeError('被bind调用的不是函数');
    }
    // 判断 context 是否传入,如果未传入则设置成 window
    context = context || window;
    // 获取参数,除了context其余都合并为参数列表
    let args = [...arguments].slice(1);
    // 用Symbol来创建唯一的fn,防止名字冲突
    let fn = Symbol("key");
    // this是调用myBind的函数,将函数绑定到context对象的新属性上
    context[fn] = this;
    // 返回一个新的函数
    return function func() {
        // 判断返回出去的函数有没有被new,this指向中构造器调用的优先级最高
        if (this instanceof func) {
            return new context[fn](...args, ...arguments);
        }
        // 作为context对象的方法调用
        return context[fn](...args, ...arguments);
    }
}
  • 测试
js 复制代码
const ddl = {
    name: "zyzy",
    hello: function (a,b,c) {
        console.log(`hello,${this.name}!`,a + b + c);
    },
};

const obj = { name: "world" };

let hello1 = ddl.hello.myBind(obj, 1);
let hello2 = ddl.hello.bind(obj, 1);
hello1(2, 3);// hello,world! 6
hello2(2, 3);// hello,world! 6
new hello1(4, 5);// hello,undefined! 10
new hello2(4, 5);// hello,undefined! 10

如有问题欢迎在评论区讨论,一起进步!

相关推荐
你我约定有三5 分钟前
面试tips--JVM(3)--类加载过程
jvm·面试·职场和发展
袁煦丞27 分钟前
DS file文件管家远程自由:cpolar内网穿透实验室第492个成功挑战
前端·程序员·远程工作
用户0137412843728 分钟前
九个鲜为人知却极具威力的 CSS 功能:提升前端开发体验的隐藏技巧
前端
永远不打烊31 分钟前
Window环境 WebRTC demo 运行
前端
风舞32 分钟前
一文搞定JS所有类型判断最佳实践
前端·javascript
coding随想33 分钟前
哈希值变化的魔法:深入解析HTML5 hashchange事件的奥秘与实战
前端
一树山茶40 分钟前
uniapp在微信小程序中实现 SSE进行通信
前端·javascript
coding随想40 分钟前
小程序中的pageshow与pagehide事件,HTML5中也有?揭秘浏览器往返缓存(BFCache)
前端
萌萌哒草头将军1 小时前
Rspack 1.5 版本更新速览!🚀🚀🚀
前端·javascript·vue.js
程序员清风1 小时前
为什么Tomcat可以把线程数设置为200,而不是2N?
java·后端·面试