JavaScript源码实现——call()、apply()、bind()

前端面试中,面试官经常会要求面试者手写一些常见函数的源码,而call、apply和bind出现频率非常高,本文参考了多篇优质博文对其进行归纳整理,欢迎讨论!

目录

一、call()

二、apply()

三、bind()

四、三者区别


一、call()

func.call( thisValue, arg1, arg2, ...);

//thisValue就是func中的this指向的对象,如果设为空、null、undefined,则指向全局对象
//arg1,arg2等参数则是函数调用时需要的参数,且这个参数是以散列形式传入

源码实现:

由于call()实现的功能是改变this的指向,因此只需变更调用者即可。

Function.prototype.myCall = function (thisValue, ...args) {
    thisValue = thisValue || window;  //若没有传入绑定对象,默认绑定window
    const fn = Symbol('fn');    //声明独有的fn,防止属性覆盖
    thisValue[fn] = this;       //改变call指向的对象
    const res = thisValue[fn](...args); //执行函数
    delete thisValue[fn];   //删除声明的fn属性
    return res;
}

关于thisValue[fn] = this; 的详细解读:

1. 一开始我只了解 f1.call( obj );是把f1的this指向obj,但对this指向的变化不清楚,但实际上 f1.call(obj);是把this指向从指向f1函数改变到指向obj对象,也就是说,thisValue[fn]=this;中的this最开始指的是call调用的函数对象。这也解开了我对 函数被赋值为对象?的疑惑

2.var that = this;这种写法的意义是什么呢?为什么不直接使用this关键字,而需要变量复制指向呢?

我们都知道,this代表的始终是属性或方法当前的所在对象,正因为this的“当前对象”特质,使得它随时都可能变化,因此我们需要一个变量去储存一份这时候的this指向,也就是说用变量that来保存这时候的指向

二、apply()

func.apply(thisValue, [arg1, arg2, ...]);

//thisValue就是func中的this指向的对象,如果设为null、undefined,则指向全局对象
//第2个参数是一个数组,该数组的成员依次作为参数传入原函数

源码实现:

apply与call类似,区别只在于apply()接收的是数组,只需在call基础上改变传参形式即可。(...args => args)

Function.prototype.myCall = function (thisValue, args) { 
    thisValue = thisValue || window;  //若没有传入绑定对象,默认绑定window
    const fn = Symbol('fn');    //声明独有的fn,防止属性覆盖
    thisValue[fn] = this;       //改变call指向的对象
    const res = thisValue[fn](...args); //执行函数,apply在调用原函数时,仍需要展开参数数组,依次传入
    delete thisValue[fn];   //删除声明的fn属性
    return res;
}

再次强调,apply虽然接收的是数组,但在给原函数传参时,仍需要将参数数组展开,依次传入!

三、bind()

//bind会返回一个全新的函数,需要调用才能执行
var fn = func.bind( thisValue[, arg1[, arg2[, ...]]] );
fn();
//thisValue就是func中的this指向的对象,如果设为null、undefined,则指向全局对象

源码实现:【参考柯里化】

1. bind()除了thisValue还可以接收函数调用时所需的部分或全部参数,bind()返回的新函数则接收函数调用时所需的剩下的参数。

2. 因为bind()返回的是新函数,所以可能会存在 new 新函数 来生成对象,因此需要考虑new绑定和bind()绑定的优先级,而new绑定 > 显示绑定,因此new绑定优先!

3.  需要继承原函数在原型链上的属性和方法

Function.prototype.myBind = function (thisValue, ...args) {
    if(typeof this != 'function'){  //如果提问参数类型
        throw Error('not a function');
    }

    //一般从这里开始写
    const self = this;  //储存this当前指向
    const resFn = function (){
        // this instanceof self:判断this是否是new生成的新实例对象,若是则根据new绑定优先原则,优先绑定this
        //args.concat:常见的绑定形式为bind(obj,arg1),可能只传入一部分函数参数,另一部分需要函数调用时传入,因此需要concat
        //外层函数和内层函数的arguments加起来刚好可以调用返回的新函数
        self.apply(this instanceof self ? this : thisValue , args.concat(Array.prototype.slice.call(arguments)));
    };
    resFn.prototype = Object.create(self.prototype); //继承原函数在原型链上的属性和方法
    return resFn;
}

四、三者区别

1. call、apply是立即执行函数fn,而bind是返回一个绑定了this的新函数,需要调用才能执行;

2. bind属于硬绑定,返回的新函数的this指向不能再通过call、apply或bind修改;如果多次绑定,也以第一次为准;而call、apply的绑定只适用当前调用,下次要改变this指向还要再次绑定;

【关于硬绑定的介绍,请参照JavaScript重点——this指向中显式绑定这一章】

3. call和apply的区别只在于参数的形式,call的参数是以散列形式,apply的参数是一个数组,因此call的性能高于apply,不需要解析数组
 

参考资料

1. 理解JavaScript Call()函数原理。

2. 2万字 | 前端基础拾遗90问

猜你喜欢

转载自blog.csdn.net/huaf_liu/article/details/115362063