三者的异同点?

使用方法

函数.call(对象,arg1,arg2….)

函数.apply(对象,[arg1,arg2,…])

函数.bind(对象,arg1,arg2,….)

相同点

都可以改变this的指向,且第一个参数都是this将要指向的对象。

不同点

call和apply在使用时会自动执行函数,而bind不会执行。

你可以自己手动实现三种方法吗?

bind方法

概念:返回一个原函数的拷贝(也称绑定函数),在调用时设置this关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。

1
function.bind(thisArg[, arg1[, arg2[, ...]]])
  • thisArg:调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用bindsetTimeout中创建一个函数(作为回调提供)时,作为thisArg传递的任何原始值都将转换为object。如果bind函数的参数列表为空,执行作用域的this将被视为新函数的thisArg
  • arg1, arg2, ...:当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。

举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 window.value = 3;
var foo = {
value:1
};
function bar() {
console.log(this.value);
}
bar(); // 3
bar.call(foo); //1

//指定函数this绑定为foo, 产生一个新函数,之后再运行的时候,内部的this就是被绑定的对象
var bindFoo = bar.bind(foo);
setTimeout( function() {
bindFoo();
},2000)
// 2秒后打印 1

这个例子可以很好的理解bind的运用:

  • bar()直接调用函数,其中的value指的是全部变量value = 3
  • bar.call(foo)这里使用call立刻改变了bar中的this指向为foo
  • bind 常用于异步,在setTimeout中,设置的时间内,barthis保留着指向foo,所以两秒后打印1,不是3。

前置知识:

MDN:绑定函数也可以使用 new 运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略,但前置参数仍会提供给模拟函数。

特别说明:绑定函数被new实例化之后,需要继承原函数的原型链方法,且绑定过程中提供的this被忽略(继承原函数的this对象),但是参数还是会使用

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const wang = {
age: 18,
love: 'coding',
hello: function (age, love) {
if (age) this.age = age;
if (love) this.love = love;
console.log("hello world, i am ghostwang," + this.age + "," + this.love);
}
};
const ye = {
age: 19,
love: 'sleeping'
};

// wang.hello.myCall(ye, 0);

Function.prototype.myBind = function () {
if (typeof this !== 'function') {
throw new TypeError(this + 'must be a function');
}
const target = Array.from(arguments)[0]; // 第一个参数是this
const args = Array.from(arguments).slice(1);
const self = this;
let fBound = function () {
const _this = this instanceof self ? this : target; // 检测是否使用new创建
return self.apply(_this, args);
}
/** 假设我们将调用bind的函数称为C,
将fBound的prototype原型对象指向C的prototype原型对象(上例中就是self),
这样的话如果将fBound作为构造函数(使用new操作符)实例化一个对象,
那么这个对象也是C的实例,this instanceof self就会返回true。
这时就将self指向新创建的对象的this上就可以达到原生bind的效果了(不再固定指定的this)。
否则,才使用oThis,即绑定指定的this。**/
if (this.prototype) {
fBound.prototype = this.prototype
}
return fBound;
}

const foo = wang.hello.myBind(ye);
const foo2 = wang.hello.myBind(ye, 10, 'mom love u');
const nFoo = new foo2(); // hello world, i am ghostwang,10,mom love u
foo(); // hello world, i am ghostwang,19,sleeping

call方法

概念:function.call(thisArg, arg1, arg2, ...)

  • thisArg:在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为nullundefinedthis值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
  • arg1, arg2, ...:指定的参数列表。
1
2
3
4
5
6
7
var obj = { name:'segmentfault' };
function fn() {
// console.log(this);
console.log(this.name);
}
fn(); // undefined
fn.call(obj); // segmentfault

理解:首先寻找call方法,通过原型链的查找,在Function.prototype上找到call方法;然后,改变fn函数中的this指向,将fn执行。

1
2
3
4
5
var obj = { name:'wc' };
function fn(age, country) {
console.log(this.name + '-' + age + '-' + country);
}
fn.call(obj, 18, 'China'); // wc-19-China

理解:带参数传入,参数需要展开,这也是唯一于apply方法不同的地方。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Function.prototype.myCall = function () {
const argumentsArr = Array.from(arguments);
const target = argumentsArr[0];
if (!target) { // 判断是node还是浏览器环境
target = typeof window === undefined ? global : window;
}
target.fn = this; // 这里的this指向的是.前面的对象,也就是被调用的函数的原型对象
// 现在taget的fn属性就是缓存了被调用的函数, 需要改变这个函数的内部this,.前面是谁,this就指向谁,所以现在this是taget
const res = target.fn(argumentsArr.slice(1));
delete target.fn;
return res;
}
const wang = {
age: 18,
love: 'coding',
hello: function () {
console.log("hello world, i am zhou," + this.age + "," + this.love);
}
};
const ye = {
age: 19,
love: 'sleeping'
};

wang.hello.myCall(ye); // hello world, i am ghostwang,19,sleeping

总结:

  • call方法的第一个参数用于改变调用call方法的函数内,this的指向,但是如果传入null/undefined值,此this会指向window
  • call方法需要把实参按照形参的个数传进去
  • call方法最后会使用参数去执行call函数体内this所指向的函数,一般是指向调用call的函数

apply方法

概念:调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

1
func.apply(thisArg, [argsArray])
  • thisArg: 可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。
  • argsArray:可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const wang = {
age: 18,
love: 'coding',
hello: function () {
console.log("hello world, i am zhou," + this.age + "," + this.love);
}
};
const ye = {
age: 19,
love: 'sleeping'
};

Function.prototype.myApply = function () {
const argumentsArr = Array.from(arguments);
const target = argumentsArr[0];
if (!target) { // 判断是node还是浏览器环境
target = typeof window === undefined ? global : window;
}
target.fn = this; // 这里的this指向的是.前面的对象,也就是被调用的函数的原型对象
// 现在taget的fn属性就是缓存了被调用的函数, 需要改变这个函数的内部this,.前面是谁,this就指向谁,所以现在this是taget
const res = target.fn(...argumentsArr.slice(1));
delete target.fn;
return res;
}

wang.hello.myApply(ye); // hello world, i am zhou,19,sleeping