DrillLab
第 38 / 105 道38 / 105 · #304

call、apply、bind 的区别

What are the differences between call, apply & bind

先自己答,再往下看Answer it yourself first

一句话:三个都是改thiscallapply立即执行,bind 返回一个新函数call 参数一个个传,apply 传数组。

是否立即执行参数形式
fn.call(ctx, a, b)立即逐个(Comma)
fn.apply(ctx, [a, b])立即数组(Array)
fn.bind(ctx, a)不执行,返回新函数逐个,且可以只绑一部分

记法:Apply 收 Array,Call 用 Comma。

bind 的两个额外性质(追问点):

  • 能预置参数—— 所以它就是偏函数 (见 #299)。
  • 绑过一次就锁死了—— 再 bindcall都改不回来。new 能突破它, 因为 new 优先级最高。

还会追问:apply 现在还有用吗?」—— 展开语法出来后大部分被fn(...args) 取代了。但转发不定参数时还常用fn.apply(this, args)(防抖里就是这么写的,因为要同时转发thisargs)。

In one line: all three change this. call and apply run the function right away, bind hands you back a new one; call takes its arguments one by one, apply takes an array.

Runs immediately?Argument form
fn.call(ctx, a, b)YesOne by one (Comma)
fn.apply(ctx, [a, b])YesAn array (Array)
fn.bind(ctx, a)No — you get a new functionOne by one, and you may bind only some of them

How to remember it: Apply takes an Array, Call takes Commas.

Two extra properties of bind they will probe:

  • It can preset arguments — which makes it partial application (see #299).
  • Bind once and it is locked — another bind or a call cannot change it back. But new breaks through, because new has the highest priority.

Another follow-up: “Is apply still useful?” — spread syntax replaced most of it with fn(...args). It is still the normal way to forward an unknown argument list: fn.apply(this, args) — that is how debounce is written, because you have to forward this and args at the same time.

JavaScript三者对比与手写 bindThe three compared, and bind written by hand示意Illustrative
1function greet(greeting, mark) {
2 return `${greeting}, ${this.name}${mark}`;
3}
4const who = { name: "小明" };
5
6greet.call(who, "你好", "!"); // "你好, 小明!"
7greet.apply(who, ["你好", "!"]); // 同上,参数是数组
8const hi = greet.bind(who, "你好"); // 返回新函数,还预置了第一个参数
9hi("?"); // "你好, 小明?"
10
11// 手写一个 bind(高频现场题)
12Function.prototype.myBind = function (ctx, ...preset) {
13 const fn = this;
14 return function bound(...args) {
15 // new 调用时 this 是新对象,此时不该用 ctx —— 这是 bind 的规范行为
16 const isNew = this instanceof bound;
17 return fn.apply(isNew ? this : ctx, [...preset, ...args]);
18 };
19};
1function greet(greeting, mark) {
2 return `${greeting}, ${this.name}${mark}`;
3}
4const who = { name: "小明" };
5
6greet.call(who, "你好", "!"); // the greeting, then the name, then the mark
7greet.apply(who, ["你好", "!"]); // the same, but the arguments arrive as an array
8const hi = greet.bind(who, "你好"); // returns a new function with the first argument preset
9hi("?"); // the same string, ending in a question mark
10
11// Write bind by hand (a very common live-coding question)
12Function.prototype.myBind = function (ctx, ...preset) {
13 const fn = this;
14 return function bound(...args) {
15 // With new, this is the new object and ctx must be ignored —— the spec requires it
16 const isNew = this instanceof bound;
17 return fn.apply(isNew ? this : ctx, [...preset, ...args]);
18 };
19};
手写 bind 的加分项就是那个 isNew 判断:规范规定 new 一个 bound 函数时,绑定的 this 应该被忽略。多数人会漏。What earns extra credit when you write bind by hand is that isNew check: the spec says that when a bound function is called with new, the bound this must be ignored. Most people leave it out.