谈谈JavaScript中的call、apply和bind

banner.jpg

JavaScript中,如果想要改变当前函数调用的上下文对象的时候,我们都会联想到call、apply和bind。比如下面👇

var name = 'window name';
var obj = {
    name: 'call_me_R'
};
function sayName(){
    console.log(this.name);
}
sayName(); // window name
sayName.call(obj); // call_me_R

那么,call, apply和bind有什么区别呢?

call,apply和bind的区别

在说区别之前,先简单的说下三者的共同之处吧:

  1. 都是用来改变函数的this对象的指向
  2. 第一个参数都是this要指向的对象
  3. 都可以利用后续参数进行传参

下面说下区别:

参数的传递

参考下MDN web docs -- Function:

call方法传参是传一个或者是多个参数,第一个参数是指定的对象,如开篇的obj

func.call(thisArg, arg1, arg2, ...)

apply方法传参是传一个或两个参数,第一个参数是指定的对象,第二个参数是一个数组或者类数组对象。

func.apply(thisArg, [argsArray])

bind方法传参是传一个或者多个参数,跟call方法传递参数一样。

func.bind(this.thisArg, arg1, arg2, ...)

简言之,callbind传参一样;apply如果要传第二个参数的话,应该传递一个类数组。

调用后是否立执行

call和apply在函数调用它们之后,会立即执行这个函数;而函数调用bind之后,会返回调用函数的引用,如果要执行的话,需要执行返回的函数引用。

变动下开篇的demo代码,会比较容易理解:

var name = 'window name';
var obj = {
    name: 'call_me_R'
};
function sayName(){
    console.log(this.name);
}
sayName(); // window name
sayName.call(obj); // call_me_R
sayName.apply(obj); // call_me_R
console.log('---divided line---');
var _sayName = sayName.bind(obj);
_sayName(); // call_me_R

在笔者看来,call, apply 和 bind的区分点主要是上面的这两点,欢迎有想法的读者进行补充~😊

手写call, apply, bind方法

这里是简单的实现下相关方法的封装,为了简洁,我这里尽量使用了ES6的语法进行编写,详细的参考代码可以直接戳airuikun大牛的airuikun/Weekly-FE-Interview issues

call方法实现

在上面的了解中,我们很清楚了call的传参格式和调用执行方式,那么就有了下面的实现方法:

Function.prototype.call2 = function(context, ...args){
    context = context || window; // 因为传递过来的context有可能是null
    context.fn = this; // 让fn的上下文为context
    const result = context.fn(...args);
    delete context.fn;
    return result; // 因为有可能this函数会有返回值return
}

我们来测试下:

var name = 'window name';
var obj = {
    name: 'call_me_R'
};

// Function.prototype.call2 is here ...

function sayName(a){
    console.log(a + this.name);
    return this;
}
sayName(''); // window name
var _this = sayName.call2(obj, 'hello '); // hello call_me_R
console.log(_this); // {name: "call_me_R"}

apply方法实现

apply方法和call方法差不多,区分点是apply第二个参数是传递数组:

Function.prototype.apply2 = function(context, arr){
    context = context || window; // 因为传递过来的context有可能是null
    context.fn = this; // 让fn的上下文为context
    arr = arr || []; // 对传进来的数组参数进行处理
    const result = context.fn(...arr); // 相当于context.fn(arguments[1], arguments[2], ...)
    delete context.fn;
    return result; // 因为有可能this函数会有返回值return
}

同样的,我们来测试下:

var name = 'window name';
var obj = {
    name: 'call_me_R'
};

// Function.prototype.apply2 is here ...

function sayName(){
    console.log((arguments[0] || '') + this.name);
    return this;
}
sayName(); // window name
var _this = sayName.apply2(obj, ['hello ']); // hello call_me_R
console.log(_this); // {name: "call_me_R"}

bind方法实现

bind的实现和上面的两种就有些差别,虽然和call传参相同,但是bind被调用后返回的是调用函数的指针。那么,这就说明bind内部是返回一个函数,思路打开了:

Function.prototype.bind2 = function(context, ...args){
    var fn = this; 
    return function () { // 这里不能使用箭头函数,不然参数arguments的指向就很尴尬了,指向父函数的参数
        fn.call(context, ...args, ...arguments);
    }
}

我们还是来测试一下:

var name = 'window name';
var obj = {
    name: 'call_me_R'
};

// Function.prototype.bind2 is here ...

function sayName(){
    console.log((arguments[0] || '') + this.name + (arguments[1] || ''));
}
sayName(); // window name
sayName.bind2(obj, 'hello ')(); // hello call_me_R
sayName.bind2(obj, 'hello ')('!'); // hello call_me_R!

美滋滋😄,成功地简单实现了call、apply和bind的方法,那么你可能会对上面的某些代码有疑问❓

疑惑点

1. 问:call中为什么说 context.fn = this; // 让fn的上下文为context 呢?

答:

我们先来看看下面这段代码--

var name = 'window name';
var obj = {
    name: 'call_me_R',
    sayHi: function() {
        console.log('Hello ' + this.name);
    }
};
obj.sayHi(); // Hello call_me_R
window.fn = obj.sayHi;
window.fn(); // Hello window name

嗯,神奇了一丢丢,操作window.fn = obj.sayHi;改变了this的指向,也就是this由指向obj改为指向window了。

简单来说:this的值并不是由函数定义放在哪个对象里面决定的,而是函数执行时由谁来唤起来决定的。

2. 问:bind中返回的参数为什么是传递(context, ...args, ...arguments), 而不是(context, ...args)呢?

答:

这是为了包含返回函数也能传参的情况,也就是bind()()中的第二个括号可以传递参数。

call和apply哪个好?

据调查--call和apply的性能对比,在分不同传参的情况下,call的性能是优于apply的。不过在现代的高版本浏览器上面,两者的差异并不大。

而在兼容性方面,两者都好啦,别说IE了哈。

在使用的方面还是得按照需求来使用call和apply,毕竟技术都在更新。适合业务的就是最好的~囧

后话

文章首发:https://github.com/reng99/blogs/issues/29

同步掘金:https://juejin.im/post/5cf648c45188253a2b01ccb1

更多内容:https://github.com/reng99/blogs

客官可以star下github的博文仓库否,欢迎提意见共同成长啊~😄

参考

MDN web docs -- Function

airuikun/Weekly-FE-Interview issues

《JavaScript高级程序设计》

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,362评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,577评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,486评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,852评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,600评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,944评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,944评论 3 447
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,108评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,652评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,385评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,616评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,111评论 5 364
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,798评论 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,205评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,537评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,334评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,570评论 2 379

推荐阅读更多精彩内容