原文:How to Use the Call, Apply, and Bind Functions in JavaScript – with Code Examples,作者:Keyur Paralkar
在本文中,我将通过简单的示例来解释如何在JavaScript中使用call、apply和bind。
我们还将使用apply函数创建自己的map函数。
话不多说,让我们开始吧!
目录
前提
想要充分理解本文,你需要先了解以下概念:
定义
让我们仔细地研究一下这几个函数的作用:
Call 函数可以改变函数调用的上下文。直白讲,就是将函数内部this值改变成任意你想要的值。
Apply 函数和call函数类似,唯一的区别在于apply允许将数组作为函数参数列表。
Bind 函数创建一个稍后执行的函数,这个新函数的执行上下文由 this 提供。
让我们先来看看call、apply和bind函数的例子,然后我们将来创建一个类似map的函数。
如何在JavaScript中使用call函数
call函数更改一个函数内部this的值,并且将传入的参数作为这个函数的执行参数。
call函数的语法如下:
func.call(thisObj, args1, args2, ...)
其中:
- func 是通过不同
this对象调用的函数 - thisObj 是用来替换函数
func内部this关键字的对象或者值 - args1, args2 args1, args2是参数,与改变后的
this对象一起传递给调用的函数。
注意如果在不传入thisObj参数的情况下调用函数,JavaScript默认this值为全局对象。
现在我们已经了解了call函数的背景,让我们通过一些示例来进一步了解它。
JS中如何在不同的上下文调用函数
考虑下面的例子。这个例子中有三个类 – Car、Brand1和Brand2:
function Car(type, fuelType){
this.type = type;
this.fuelType = fuelType;
}
function setBrand(brand){
Car.call(this, "convertible", "petrol");
this.brand = brand;
console.log(`Car details = `, this);
}
function definePrice(price){
Car.call(this, "convertible", "diesel");
this.price = price;
console.log(`Car details = `, this);
}
const newBrand = new setBrand('Brand1');
const newCarPrice = new definePrice(100000);
仔细看,你会发现我们在两个场景下通过call函数调用Car函数:一次是在setBrand函数调用;一次是在在 definePrice函数调用。
在这两个函数中, 我们都在this对象内调用Car函数,this对象分别代表了这两个函数。 例如在setBrand函数中,我们在代表函数上下文的this对象调用了Car函数,definePrice一样。
在JS中如何在不传入参数的情况下调用call函数
考虑下面的例子:
const newEntity = (obj) => console.log(obj);
function mountEntity(){
this.entity = newEntity;
console.log(`Entity ${this.entity} is mounted on ${this}`);
}
mountEntity.call(); //输出: Entity (obj) => console.log(obj) is mounted on [object Window]
在这个例子中,调用mountEntity时,thisObj参数为空。 这时,JavaScript会指向全局对象。
如何在JavaScript中使用apply函数
Apply和Call函数类似。call和apply函数唯一的不同是传入的参数。
在apply中,参数可以是一个数组的字面量或者一个新的数组对象。
apply函数的语法如下:
func.apply(thisObj, argumentsArray);
其中:
- func 是通过不同
this对象调用的函数 - thisObj 是用来替换函数
func内部this关键字的对象或者值 - argumentsArray 可以是参数数组、数组对象或者arguments关键字本身
如你所见,apply函数有不同的语法。
第一种语法很简单,你可以传入一个参数数组:
func.apply(thisObj, [args1, args2, ...]);
第二种语法可以传入一个新的数组对象:
func.apply(thisObj, new Array(args1, args2));
第三种语法可以传入arguments关键字:
func.apply(thisObj, arguments);
arguments是函数中的一个特殊对象,包含传入函数的参数的值。你可以将这个关键字与apply函数一起使用,以接受任何数量的任意参数。
apply最棒的地方在于我们不需要关心传递给调用函数的参数的数量。由于动态性和多功能的特点,apply可以被应用到复杂情况。
我们用apply函数改写上文的例子:
function Car(type, fuelType){
this.type = type;
this.fuelType = fuelType;
}
function setBrand(brand){
Car.apply(this, ["convertible", "petrol"]); //使用数组字面量的语法
this.brand = brand;
console.log(`Car details = `, this);
}
function definePrice(price){
Car.apply(this, new Array("convertible", "diesel")); //使用数组构建函数的语法
this.price = price;
console.log(`Car details = `, this);
}
const newBrand = new setBrand('Brand1');
const newCarPrice = new definePrice(100000);
下面是使用arguments关键字的例子:
function addUp(){
//使用参数捕获任意数量的输入
const args = Array.from(arguments);
this.x = args.reduce((prev, curr) => prev + curr, 0);
console.log("this.x = ", this.x);
}
function driverFunc(){
const obj = {
inps: [1,2,3,4,5,6]
}
addUp.apply(obj, obj.inps);
}
driverFunc(); //输出: this.x = 21
如何在JavaScript中使用bind函数
bind函数创建一个函数副本,并改变调用函数内部this的值。
bind函数的语法如下:
func.bind(thisObj, arg1, arg2, ..., argN);
其中:
- func 是通过不同
this对象调用的函数 - thisObj 是用来替换函数
func内部this关键字的对象或者值 - arg1, arg2…argN – 和
call函数类似,你可以传入一个或多个参数
bind函数返回一个新的函数,在这个函数中包含新的被调用函数内部this的值:
func(arg1, arg2);
然后函数func根据参数被执行
让我们一起来看一看如何在React类组件中使用bind函数:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
}
handleCode() {
console.log("HANDLE CODE THIS = ", this.state);
}
render() {
return <button onClick={this.handleCode}>Click Me</button>;
}
}
考虑上述App组件,有以下几个组成部分:
constructor调用类的函数,通过new关键字实例化render是执行或渲染JSX的函数handleCode是打印组件的类方法
如果我们点击Click Me按钮,会得到报错Cannot read properties of undefined (reading 'state')。
这为什么会发生? 🤔🤔
因为handleCode是类的方法,所以你可能认为我们可以访问类的状态(state),但是这里存在的问题是:
handleCode中的this并不等同于类中的this- 在类中
this是一个普通的对象,并且有非静态类方法作为属性, 但是handleCode中的this指代另一个上下文 - 在这里
this的值取决于函数被调用的位置,handleCode是在onClick事件中被调用 - 调用时
handleCode函数内部的this被设置为undefined - 我们尝试调用undefined的
state属性,就导致了上文的报错
我们可以通过给handleCode方法的this指定上下文来解决这个问题,bind方法就派上用场了:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 1
};
this.handleCode = this.handleCode.bind(this); //绑定this函数
}
handleCode() {
console.log("HANDLE CODE THIS = ", this.state);
}
render() {
return <button onClick={this.handleCode}>Click Me</button>;
}
}
bind会创建一个新函数,并且存储this在对象中,这个函数包含handleCode这个新属性。Bind确保类的this上下文被应用到 handleCode函数的this。
如何自定义map函数
在了解所有必要知识之后,让我们来自己创建一个map函数,我们先来看看自定义map函数需要了解什么:
map函数的语法如下:
arr.map(func)
其中:
- arr是map调用的数组
- func 是数组上每一个元素需要执行的函数
map函数的基本功能很简单:
map函数遍历数组的每一个元素,并在每一个元素上调用传入的参数。返回值的类型是一个数组,数组的每一个元素都是应用func后的结果。
我们已经知道这个函数的要求了,就可以着手创建自己的map函数了,以下是新的map函数:
function newMap(func){
let destArr = [];
const srcArrLen = this.length;
for(let i = 0; i < srcArrLen; i++){
destArr.push(func.call(this, this[i]));
}
return destArr;
}
让我们来一点一点解释上面的例子:
- 函数接受名为
func的参数。 这个参数就是需要在数组的每一个元素上调用的函数。 - 代码的其他部分不言自明。我们主要聚焦在
destArr.push(func.call(this, this[i])); - 这行代码做了两件事:
1. 将变化推入destArr
2. 通过call方法执行func,call方法(如上文解释的那样)会执行func方法,并使用func方法内部this对象的新值。
让我们来看看newMap函数是如何执行的。不推荐下面这种给原始数据类型添加新方法的做法,我们这么做仅出于教学目的。
注: 不要在你的代码中使用下面的方法,不然会对你的工作造成影响。
Object.defineProperty(Array.prototype, 'newMap', {
value: newMap
});
defineProperty在Array.prototype创建了新的方法。
设定完毕后,我们就可以使用自己的函数了:
const arr = [1,2,3];
const newArr = arr.newMap(item => item + 1);
console.log(newArr); //输出:[2, 3, 4]
总结
本文通过示例展示了call、apply和bind的用法。
简单概括一下:
- Call、apply和bind可以改变调用函数内部
this关键字的上下文 - 每个例子的调用方式不同 –
apply通过一组数组执行,call执行结果类似但是参数由逗号隔开 - 在React的类组件中,这些方法十分管用
感谢阅读!