call、bind 和 apply 的介绍

三者的作用都很类似,都是改变函数的 this 指向
但是 call 和 apply 用来改变 this 并立即调用函数;bind 用来返回一个永久绑定 this 的新函数

实例:

展开 / 收起示例代码
1
2
3
4
5
6
function greet(greeting, punctuation) {
console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };
const anotherPerson = { name: 'Bob' };

call 改变 this 并立即调用

1
2
3
greet.call(person, 'Hello', '!');
// 输出:Hello, Alice!

apply 改变 this 并立即调用(调用参数不同)

1
2
3
greet.apply(person, ['Hi', '!!!']);
// 输出:Hi, Alice!!!

bind 改变 this,但不会立即调用 → 返回一个新函数

1
2
3
4
const greetBob = greet.bind(anotherPerson, 'Hey');
greetBob('?');
// 输出:Hey, Bob?

手写call

接下来将会手写 call applybind

1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.mycall = function (ctx, ...args) {
// 保持key的唯一性
const key = Symbol()
// 确定this指向
// globalThis 指向全局对象,因为宿主环境不同,指向不同。例如浏览器和node环境
ctx = ctx === undefined || ctx === null ? globalThis : Object(ctx)
ctx[key] = this
var result = ctx[key](...args);
delete ctx[key]

return result
}

整体看下来其实很简单,就是将调用函数的this指向放到ctx里。接下来进行简单测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fn(a, b) {
// console.log(this);

return a + b
}

const res1 = fn.mycall({}, 2, 3)
const res2 = fn.mycall(null, 2, 3)
const res3 = fn.mycall(undefined, 2, 3)
const res4 = fn.mycall(1, 2, 3)
console.log("res1", res1);
console.log("res2", res2);
console.log("res3", res3);
console.log("res4", res4);

下面是测试结果

1
2
3
4
res1 5
res2 5
res3 5
res4 5

手写apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.myapply = function (ctx, args) {
const key = Symbol()
ctx = ctx === undefined || ctx === null ? globalThis : Object(ctx)
ctx[key] = this
let result

// 这里分出区别,如果args为空,则直接调用函数,没有则传入args
if (args) {
args = Array.prototype.slice.call(args)
result = ctx[key](...args);
} else {
result = ctx[key]()
}
delete ctx[key]

return result
}

applycall 比较类似。主要区别就是传入参数的不同。从此区别来更改 call 的写法就可以实现

测试

1
2
3
4
5
6
7
8
9
10
const res5 = fn.myapply({}, [2, 3])
const res6 = fn.myapply(null, [2, 3])
const res7 = fn.myapply(undefined, [2, 3])
const res8 = fn.myapply(1, [2, 3])
const applyempty = fn.myapply({}, [2, 3])
console.log("res5", res5);
console.log("res6", res6);
console.log("res7", res7);
console.log("res8", res8);
console.log("applyempty", applyempty);

结果为:

1
2
3
4
5
res5 5
res6 5
res7 5
res8 5
applyempty 5

手写bind

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Function.prototype.myBind = function (context) {
// 获取参数
// arguments 为传入的参数,值为传入的参数。注意:这里从 slice(1) 开始。因为这里第一个参数是context
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.myapply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};

测试

1
2
3
4
5
6
7
8
9
10
11
12
13

function testbind(a, b, c) {
console.log(a, b, c);

// console.log(this);
return a + b + c


}

const newfn = testbind.myBind({}, 1)
console.log("newfn2", newfn(2, 5));
console.log("newfn2", newfn(3, 7));

结果为:

1
2
1 3 7
newfn2 11