谈谈JavaScript中的call、apply和bind

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

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对象的指向


  1. 第一个参数都是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,毕竟技术都在更新。适合业务的就是最好的~囧



相关文章
|
23天前
|
JavaScript 前端开发
JS高级—call(),apply(),bind()
【10月更文挑战第17天】call()`、`apply()`和`bind()`是 JavaScript 中非常重要的工具,它们为我们提供了灵活控制函数执行和`this`指向的能力。通过合理运用这些方法,可以实现更复杂的编程逻辑和功能,提升代码的质量和可维护性。你在实际开发中可以根据具体需求,选择合适的方法来满足业务需求,并不断探索它们的更多应用场景。
8 1
|
1月前
|
JavaScript 前端开发
js 中call()和apply()
js 中call()和apply()
27 1
|
2月前
|
自然语言处理 JavaScript 前端开发
JS中this的应用场景,再了解下apply、call和bind!
该文章深入探讨了JavaScript中`this`关键字的多种应用场景,并详细解释了`apply`、`call`和`bind`这三个函数方法的使用技巧和差异。
|
2月前
|
JavaScript 前端开发
this指向的几种情况以及js简单实现call、apply、bind___六卿
本文讨论了JavaScript中`this`的指向规则,并提供了`call`、`apply`和`bind`方法的简单实现,用于改变函数的`this`指向。
17 0
this指向的几种情况以及js简单实现call、apply、bind___六卿
|
JavaScript 前端开发
JavaScript深入之bind的模拟实现
JavaScript深入系列第十一篇,通过bind函数的模拟实现,带大家真正了解bind的特性
127 0
JavaScript深入之bind的模拟实现
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
95 2
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
118 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物援助平台附带文章源码部署视频讲解等
81 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的宠物交易平台附带文章源码部署视频讲解等
72 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的大学生入伍人员管理系统附带文章源码部署视频讲解等
92 4