Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

谈谈JavaScript中的call、apply和bind #29

Open
reng99 opened this issue Jun 4, 2019 · 0 comments
Open

谈谈JavaScript中的call、apply和bind #29

reng99 opened this issue Jun 4, 2019 · 0 comments
Labels
blog a single blog javascript javascript tag

Comments

@reng99
Copy link
Owner

reng99 commented Jun 4, 2019

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

参考

MDN web docs -- Function

airuikun/Weekly-FE-Interview issues

《JavaScript高级程序设计》

@reng99 reng99 added blog a single blog javascript javascript tag labels Jun 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blog a single blog javascript javascript tag
Projects
None yet
Development

No branches or pull requests

1 participant