我们先弄明白这三个问题 是什么?怎么做?为什么?
1、是什么?
call, apply, bind 这三个api是什么?它们有什么区别?
简单来说它们是用来强制改变this 指向问题的api。
那么什么是this呢?
- this是执行上下文中的一个属性
- this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。
- 这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。
它们有什么区别?
call
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
apply
apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数
bind
bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
其实 apply 和 call 基本类似,他们的区别只是传入的参数不同。
call 方法接受的是若干个参数列表,而 apply 接收的是一个包含多个参数的数组。
// call
function.call(thisArg, arg1, arg2, ...)
// apply
func.apply(thisArg, [argsArray])
bind 是创建一个新的函数,我们必须要手动去调用
接受数组或者类数组
function.bind(thisArg[, arg1[, arg2[, ...]]])
2、怎么做?
怎么去实现这三个 api?
让我们来试着一一去实现
MyCall
先看看call 做了什么
// call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。
var foo = {
value: 1
};
function bar() {
console.log(this.value);
}
bar() // undefined
bar.call(foo); // 1
我们直接调用bar时打印undefined 而当我们使用call 打印出了1,
直接调用 this 执行global,在global 中找不到value值,
而当我们使用call 时,this 指向了foo 对象
// call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
// var foo = {
// value: 1
// };
// function bar() {
// console.log(this.value);
// }
// bar() // undefined
// bar.call(foo); // 1
// 那么call的功能有哪些呢?
// 1、改变this指向
// 2、执行函数
// 那么我们试着来实现这两个功能
Function.prototype.myCall = function (context) {
// call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
// 根据定义我们先进行判断,如果不是函数的话我们直接抛出错误
if (typeof this !== "function") {
throw new TypeError("error this is not function!");
}
// 首先 context 为可选参数,如果不传的话默认上下文为 window
context = context || window;
// 接下来给contexnt创建一个fn 属性,并将值设置为需要调用的函数 (也就是this)
context.fn = this;
// 因为 call 可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
const args = [...arguments].slice(1);
// 然后调用函数,并将这个创建的fn属性删除
const result = context.fn(...args);
delete context.fn;
return result;
};
let obj = {
value: "call",
};
function getValue(name, age) {
console.log(name, age); // '清风' 18
console.log(this.value); // call
}
getValue.myCall(obj, "清风", 18);
MyApply
apply 的实现跟 call 类似,只在处理参数时不同
// apply() 方法调用一个具有给定 this 值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
Function.prototype.myApply = function (context) {
if (typeof this !== "function") {
throw new TypeError("error this is not function");
}
// context 不存在时指向window
context = context || window;
// 在context上创建属性fn 指向函数this
context.fn = this;
// 获取参数值
let result;
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 删除fn属性
delete context.fn;
return result;
};
let obj = {
value: "apply",
};
function getValue(name, age) {
console.log(name, age); // '清风' 18
console.log(this.value); //apply
}
getValue.myApply(obj, ["清风", 18]);
MyBind
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
bind 的实现对比其他两个函数略微地复杂了一点,因为 bind 需要返回一个函数,需要判断一些边界问题
1、bind返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过new的方式,我们先来说直接调用的方式
2、对于直接调用来说,这里选择了apply的方式实现,但是对于参数需要注意以下情况:因为bind可以实现类似这样的代码f.bind(obj, 1)(2),
所以我们需要将两边的参数拼接起来,于是就有了这样的实现args.concat(...arguments)
3、最后来说通过new的方式,在之前的章节中我们学习过如何判断this,对于new的情况来说,不会被任何方式改变this,
// bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
// bind 的实现对比其他两个函数略微地复杂了一点,因为 bind 需要返回一个函数,需要判断一些边界问题
// 1、bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式,我们先来说直接调用的方式
// 2、对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),
// 所以我们需要将两边的参数拼接起来,于是就有了这样的实现 args.concat(...arguments)
// 3、最后来说通过 new 的方式,在之前的章节中我们学习过如何判断 this,对于 new 的情况来说,不会被任何方式改变 this,
// 所以对于这种情况我们需要忽略传入的 this
Function.prototype.myBind = function (context) {
if (typeof this !== "function") {
throw new TypeError("error this is not function");
}
// 返回一个新的函数,这里我们就不用想call和apply一样创建fn属性了
const _this = this;
// 获取参数,可以一次行传,也可以在调用时候传参数
const args = [...arguments].slice(1);
// bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式,我们先来说直接调用的方式
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments);
}
return _this.apply(context, args.concat(...arguments));
};
};
let obj = {
value: "bind",
};
function getValue(name, age) {
console.log(name, age);
console.log(this.value);
}
const bind = getValue.myBind(obj, "清风")(18);
// bind('18')
// 清风 18 bind
3、为什么?
为什么一下出现三种类似的api?它们分别解决了什么问题?
this 指向问题在开发中容易造成一下问题,而使用call, apply, bind 进行显示绑定的话,能够方便理解以及解决this 指向不确定问题,而之所以会出现三种不同的api 当然也是为了应对不同的场景;也就是对应着三者的区别