call、bind、和apply的使用
在 JavaScript 中,call
、apply
和 bind
是三个非常常见的方法,用来显式地指定函数的 this
绑定,它们都可以用来改变函数的执行上下文(即函数内部的 this
指向)。尽管它们的功能相似,但在使用时有一些区别。
开启use strict this 的值为 undefined 而不是全局对象
文章目录
1. call
方法
call()
方法调用一个函数,并允许你在调用时指定 this
的值,同时以逗号分隔的参数列表形式传递参数。
语法:
func.call(thisArg, arg1, arg2, ...);
person1.fn.call(person2)
thisArg
: 调用该函数时绑定的this
值。arg1, arg2, ...
: 传给函数的参数。
示例:
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}
const person = { name: "Alice" };
// 使用 call 调用函数并指定 this
greet.call(person, "Hello", "!");
// 输出: "Hello, Alice!"
在这个例子中,greet
函数的 this
指向了 person
对象,通过 call
方法传递了 this
以及函数的参数。
2. apply
方法
apply()
与 call()
类似,但它接受的是数组形式的参数,而不是一个个分开的参数。
语法:
func.apply(thisArg, [argsArray]);
thisArg
: 调用该函数时绑定的this
值。argsArray
: 传给函数的参数数组。
示例:
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}
const person = { name: "Bob" };
// 使用 apply 调用函数并指定 this
greet.apply(person, ["Hi", "."]);
// 输出: "Hi, Bob."
apply()
通常在你已经有一个数组或类数组对象需要作为参数传入时更为有用。
使用场景:求数组中的最大值
你可以用 apply
将数组传给 Math.max()
函数。
const numbers = [1, 2, 3, 4, 5];
const max = Math.max.apply(null, numbers);
console.log(max); // 输出: 5
这里 apply
将数组中的元素作为参数传递给了 Math.max()
,而 null
则是因为 Math.max()
不依赖 this
。
3. bind
方法
bind()
与 call
和 apply
不同,它不会立即调用函数,而是返回一个新的函数,该函数的 this
永远绑定为你指定的 thisArg
值。你可以在以后调用这个新函数。
语法:
const boundFunc = func.bind(thisArg, arg1, arg2, ...);
thisArg
: 调用该函数时绑定的this
值。arg1, arg2, ...
: (可选)调用时预置的参数。
示例:
function greet(greeting, punctuation) {
console.log(greeting + ", " + this.name + punctuation);
}
const person = { name: "Charlie" };
// 创建一个绑定了 this 的新函数
const boundGreet = greet.bind(person, "Hey");
boundGreet("!"); // 输出: "Hey, Charlie!"
在这个例子中,greet.bind(person, "Hey")
返回了一个新函数 boundGreet
,它的 this
永远绑定为 person
,并且预置了第一个参数为 "Hey"
。调用 boundGreet("!")
时,实际上是调用了 greet("Hey", "!")
。
4. call
、apply
和 bind
的区别总结
方法 | 什么时候调用? | 参数形式 | 返回值 |
---|---|---|---|
call | 立即调用函数 | 逐个参数传递 | 调用函数的结果 |
apply | 立即调用函数 | 通过数组传递参数 | 调用函数的结果 |
bind | 返回一个新函数 | 逐个参数传递(预置参数可选) | 绑定了 this 的新函数 |
5. 实际应用场景
1. 借用其他对象的方法:
你可以通过 call
或 apply
借用其他对象的函数。例如,使用数组的 slice
方法来将类数组对象转换为数组。
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
// 使用 call 借用 Array.prototype.slice
const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray); // 输出: ['a', 'b', 'c']
2. 函数柯里化:
bind()
可用于函数柯里化(将部分参数固定,返回一个新函数),例如:
function multiply(x, y) {
return x * y;
}
const double = multiply.bind(null, 2); // 固定 x 为 2
console.log(double(5)); // 输出: 10
在这个例子中,double
函数实际上是 multiply(2, y)
,bind()
将第一个参数固定为 2
。
3. 设置定时器
function fn(name){
console.log('hello'+name)
}
const delayFn=fn.bind(null,'key')
//this指向widow
setTimeout(delayFn,2000)
call、bind、和apply的手写
1. call方法:
Function.prototype.myCall = function(context, ...args) {
// 判断调用 myCall 的是否是函数
if (typeof this !== 'function') {
throw new TypeError('被调用的对象必须是函数');
}
// 如果没有传入上下文对象,默认为全局对象
context = context || globalThis;
// 用 Symbol 来创建唯一的 fn,防止名字冲突
let fn = Symbol('key');
context[fn] = this;
// 传入 myCall 的参数 args 并执行函数
const result = context[fn](...args);
// 删除临时添加的 fn 方法
delete context[fn];
// 返回结果(对有返回值的函数生效)
return result;
}
// 定义对象和函数
var obj = {
name: 'allen',
fn() {
console.log(this.name);
},
add(a, b) {
return a + b;
}
}
var obj1 = {
name: 'bob'
}
// 测试 myCall 和 call
obj.fn.myCall(obj1); // 输出: "bob"
obj.fn.call(obj1); // 输出: "bob"
console.log(obj.add.myCall(null, 1, 2)); // 输出: 3
console.log(obj.add.call(null, 1, 2)); // 输出: 3
2. apply方法:
原理:apply的实现思路和call类似,就是apply传入参数是以数组的形式传入,所有多了一步判断传入的参数是否为数组以及在调用方法的时候使用扩展运算符…将传入的参数数组argsArr展开。
Function.prototype.myApply = function(context, argsArr) {
// 判断调用 myCall 的是否是函数
if (typeof this !== 'function') {
throw new TypeError('被调用的对象必须是函数');
}
//判断传递的参数类型是否为数组
if(argsArr && !Array.isArray(argsArr)){
throw new TypeError('第二个参数必须是数组')
}
// 如果没有传入上下文对象,默认为全局对象
context = context || globalThis;
// 用 Symbol 来创建唯一的 fn,防止名字冲突
let fn = Symbol('key');
context[fn] = this;
// 传入 myApply 的参数 args 并执行函数
const result=Array.isArray(argsArr)? context[fn](...argsArr):context[fn]()
// 删除临时添加的 fn 方法
delete context[fn];
// 返回结果(对有返回值的函数生效)
return result;
}
const arr=[2,3,4,5,6]
console.log(Math.max.myApply(null,arr));
3. bind方法:
Function.prototype.myBind = function(context, ...args) {
// 判断调用 myCall 的是否是函数
if (typeof this !== 'function') {
throw new TypeError('被调用的对象必须是函数');
}
// 如果没有传入上下文对象,默认为全局对象
context = context || globalThis;
//保存原始函数(调用 myBind 的函数)的引用
//保证了在新函数中继续调用该原函数。
const _this=this
//返回的 fn 函数支持继续传入额外的参数,并且还会判断是否作为构造函数使用。
return function fn(...innerArgs){
//判断返回出去的函数是不是作为构造函数
if(this instanceof fn){
/// 如果函数作为构造函数调用,this 指向实例对象
return new _this(...args,...innerArgs)
}
//使用apply方法将原函数绑定的指定的上下文对象,//正常调用函数,将原函数的 this 绑定到指定的 context 上
return _this.apply(context,args.concat(innerArgs))
}
}
const test={
name:'allen',
hello:function(a,b,c){
console.log(`hello,${this.name}!`,a+b+c);
}
}
const obj={
name:'world'
}
var h1=test.hello.myBind(obj,1)
var h2=test.hello.bind(obj,1)
h1(2,3)
h2(2,3)
console.log(new h1(2,3));
console.log(new h2(2,3));
new 操作创建了一个新实例,这个实例没有 name 属性
总结:
call
和apply
都用于立即调用函数,但传递参数的方式不同:call
使用逗号分隔的参数列表,而apply
使用数组。bind
返回一个新函数,不会立即调用,可以用于创建带有固定this
值和部分参数的函数。