likes
comments
collection
share

call,apply,bind的原理及手写

作者站长头像
站长
· 阅读数 3

是什么?

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实现

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;
}
  • 测试
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实现

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;
}
  • 测试
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实现

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);
    }
}
  • 测试
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

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